Quick start
Get a working tunnel between two hosts in a few minutes. You need a server host with a public (or otherwise reachable) UDP port, and a client host.
1. Install the binaries
The fastest path (Linux & macOS) — a one-liner per host that installs the latest release to /usr/local/bin and drops an example config at /etc/shadowvpn/:
curl -fsSL https://raw.githubusercontent.com/madeye/shadowvpn/main/scripts/install.sh | sudo bash -s -- servercurl -fsSL https://raw.githubusercontent.com/madeye/shadowvpn/main/scripts/install.sh | sudo bash -s -- clientOne-shot server setup
On a Linux server, sudo bash -s -- server --setup does the whole server side in one command — config with a random password, systemd service enabled
- started, forwarding + NAT, host firewall — and prints the matching client config. See Server: one-line full setup.
Or build from source with a recent stable Rust toolchain (edition 2021):
cargo build --releaseThis produces target/release/shadowvpn-server and target/release/shadowvpn-client. See Installation for the installer's options, release packages, Windows, and cross-compiling. The examples below use the source-build paths — with the one-line install, the binaries are just shadowvpn-server / shadowvpn-client on your PATH.
2. Start the server
Creating a TUN device requires elevated privileges (root on Linux, sudo on macOS, Administrator on Windows).
sudo ./target/release/shadowvpn-server \
--listen 0.0.0.0:8388 \
--password "correct horse battery staple" \
--cipher chacha20-poly1305 \
--tun-ip 10.9.0.1 \
--peer-ip 10.9.0.23. Start the client
sudo ./target/release/shadowvpn-client \
--server vpn.example.com:8388 \
--password "correct horse battery staple" \
--cipher chacha20-poly1305 \
--tun-ip 10.9.0.2 \
--peer-ip 10.9.0.1Note how tun_ip and peer_ip are mirror images: the server's local tunnel IP is the client's peer, and vice versa.
4. Verify
From the client, ping the server's in-tunnel address:
ping 10.9.0.1A successful ping exercises the entire path: TUN → encrypt → UDP → server → decrypt → TUN, and the reply all the way back.
5. Route traffic through it
The tunnel itself is up, but nothing uses it until you route traffic into the TUN device. Pick one:
Full tunnel — route everything through the tunnel (and NAT it out on the server). See Routing & IP forwarding.
Split tunnel — let the client decide per destination with its built-in policy routing (
gfwlist/chinadnsmodes), no external tooling:sh# tunnel only the domains in a gfwlist file sudo ./target/release/shadowvpn-client -c client.json \ --mode gfwlist --gfwlist /etc/shadowvpn/gfwlist.txt # or: tunnel everything that isn't a China IP, from a GeoLite2 database sudo ./target/release/shadowvpn-client -c client.json \ --mode chinadns --geoip /etc/shadowvpn/GeoLite2-Country.mmdbSee Policy routing.
Either way, for tunneled traffic to reach the internet the server must enable IP forwarding and NAT — see Routing & IP forwarding.
Using config files instead
Both binaries accept -c, --config <PATH> pointing at a JSON file; CLI flags override JSON values. The same setup as above:
{
"server": "0.0.0.0:8388",
"password": "correct horse battery staple",
"cipher": "chacha20-poly1305",
"tun_ip": "10.9.0.1",
"tun_netmask": "255.255.255.0",
"peer_ip": "10.9.0.2",
"mtu": 1400
}{
"server": "vpn.example.com:8388",
"password": "correct horse battery staple",
"cipher": "chacha20-poly1305",
"tun_ip": "10.9.0.2",
"tun_netmask": "255.255.255.0",
"peer_ip": "10.9.0.1",
"mtu": 1400
}sudo ./target/release/shadowvpn-server -c server.json
sudo ./target/release/shadowvpn-client -c client.jsonSee the configuration guide for every field.