explaingit

kleimer/vpn_over_ssh

14GoAudience · ops devopsComplexity · 4/5ActiveSetup · hard

TLDR

Builds a VPN out of plain OpenSSH tun forwarding, with a Linux shell client, a Go-based Windows client, and a server installer that pools tun100-tun200.

Mindmap

mindmap
  root((vpn-over-ssh))
    Inputs
      SSH ed25519 keys
      Server port 65523
      Tunnel mode flags
    Outputs
      Virtual tun adapter
      Encrypted IP tunnel
      Routes and NAT
    Use Cases
      Self-hosted VPN
      Split tunnel routing
      Full tunnel exit node
    Tech Stack
      Go
      Bash
      OpenSSH
      Wintun
      systemd

Things people build with this

USE CASE 1

Stand up a self-hosted VPN using only OpenSSH tun forwarding, no dedicated VPN daemon

USE CASE 2

Connect Linux clients in full-tunnel or split-tunnel mode with route includes and excludes

USE CASE 3

Build the Windows client with the Wintun adapter and pinned server host key

USE CASE 4

Manage a pool of 100 tun devices and a /16 NAT block on the server side

Tech stack

GoBashOpenSSHWintunsystemdPowerShell

Getting it running

Difficulty · hard Time to first run · 1h+

Needs root on both sides, /dev/net/tun on Linux, administrator on Windows, Go 1.22+ to build the Windows client, and a reachable TCP port.

In plain English

This project builds a VPN out of plain SSH instead of a dedicated VPN server. It uses a feature of OpenSSH called tun forwarding, which lets two machines create virtual network adapters at each end of an SSH connection and pass IP packets through that encrypted tunnel. The README and code are in Russian, but the design is straightforward: the server runs a separate SSH instance configured to allow tunnels, prepares a pool of virtual network devices named tun100 through tun200, and clients connect, pick a free slot, and send their traffic through it. On the server side, an install_server.sh script does the setup. It installs packages, creates a system user called sshvpn, writes a dedicated sshd config in /opt/sshtun_pool, registers a systemd service for the SSH-tun daemon on port 65523, prepares the tun pool and NAT, turns on IP forwarding, and generates a host key. Each tunnel gets a small /30 IP block inside 10.250.0.0/16, so the client can compute its own address from the number of the tunnel it picks. There are two client implementations. One is a Linux shell script, sshtun_pool_client.sh, with subcommands like start, stop, status, doctor, and cleanup. It supports full-tunnel and split-tunnel modes, route includes and excludes, route lists loaded from files, DNS via resolvectl, an IPv6 blackhole to avoid leaks, locking against parallel runs, and passphrase-protected keys through ssh-agent. The other client is a Windows binary written in Go, built with a build.ps1 script that also fetches wintun.dll. It uses the Wintun virtual adapter, stores state and logs under C:\ProgramData, and supports the same modes plus host-key pinning by SHA-256. The README walks through quick-start steps: run the server installer, generate an ed25519 SSH key, drop the public key into /opt/sshtun_pool/authorized_keys with restrictive options, then start the client in full-tunnel, full-tunnel-with-exclusions, or split-tunnel mode. Full-tunnel mode is implemented by adding two half-default routes (0.0.0.0/1 and 128.0.0.0/1) so the original default route stays intact for a bypass to the real server IP. Requirements include root on both ends, /dev/net/tun on Linux, administrator rights on Windows, Go 1.22 or newer for building the Windows client, and a reachable TCP port on the server.

Copy-paste prompts

Prompt 1
Run install_server.sh for vpn_over_ssh on a fresh Debian box and walk me through what each step changes
Prompt 2
Configure the Linux client of vpn_over_ssh in split-tunnel mode for a specific list of CIDRs loaded from a file
Prompt 3
Build the Windows Go client of vpn_over_ssh with build.ps1 and show me where wintun.dll ends up
Prompt 4
Explain how the two half-default routes 0.0.0.0/1 and 128.0.0.0/1 keep the bypass to the real server IP working
Prompt 5
Add a doctor subcommand check that verifies systemd-resolved DNS routing inside the tunnel
Open on GitHub → Explain another repo

Generated 2026-05-22 · Model: sonnet-4-6 · Verify against the repo before relying on details.