Done — closing. Deployed and verified live: server
188.34.167.121, all 7 e2e checks pass repeatably.
feat(deploy): self-hosted WireGuard VPN on Hetzner Cloud via ts-cloud
Provision a Hetzner Cloud server, provisioned as a WireGuard VPN / exit node, using ts-cloud's Hetzner driver — with a fully-automated, e2e-verified deploy.
This complements the in-repo WireGuard implementation (#28): that's the client/library stack; this issue is the real-world server deployment story, using stock kernel WireGuard on the server so the setup is standard and interoperable (and doubles as a live interop target for our own client).
What gets built
deploy/hetzner-vpn/— scripts that drivets-cloud'sHetznerClient:deploy.ts— ensure SSH key, create firewall, create the server with a WireGuard cloud-init, wait for boot, add a client peer, emit a ready-to-use client config.verify.ts— e2e verification over SSH (thorough, repeatable, asserted).destroy.ts— tear the server + firewall down.README.md— usage + how the e2e test works.
Server setup (cloud-init)
- Ubuntu 24.04,
cpx11(2 vCPU / 2 GB), Falkenstein (fsn1). wireguard+wireguard-tools;wg0at10.8.0.1/24, listening UDP51820.- IPv4 + IPv6 forwarding (
sysctl),iptablesMASQUERADE → the server is an exit node (client traffic egresses via the Hetzner IP). wg-quick@wg0enabled + started; peers persisted towg0.conf.
Firewall (Hetzner Cloud firewall)
tcp/22(SSH),udp/51820(WireGuard),icmp(ping tests). Attached to the server.
E2E verification (the important part)
Run entirely on the server over SSH, fully controlled, no dependency on local WireGuard tooling — a network namespace acts as a real WireGuard client:
- Create a netns + veth pair; run a kernel WireGuard client inside it, endpoint = the host veth,
allowed-ips 0.0.0.0/0. - Register the test client as a peer on
wg0. - Handshake — assert
wg showreports a recent handshake and non-zero rx/tx. - Tunnel ping —
ping 10.8.0.1from the netns succeeds. - Exit routing —
curl https://api.ipify.orgfrom inside the netns returns the server's public IP (proves the full chain: handshake → encrypt → tunnel → decrypt → forward → NAT → internet → back). - Tear the test peer/netns down; assert cleanup.
Plus: server-side assertions (interface up, forwarding on, firewall attached, ports listening) and, when a WireGuard client is available locally, a real Mac↔server handshake.
Checklist
- Validate Hetzner credentials; confirm image/type/location
deploy.ts(firewall + server + WireGuard cloud-init + client peer/config)- Deploy the server for real; confirm
wg0up and cloud-init clean verify.ts— netns handshake + ping + exit-IP, all asserted (7/7)- Repeatable automated test harness (verify.ts cleans up + re-runs)
destroy.tsteardown- README / docs
- Optional: live Mac↔server WireGuard client test (client-lt.conf generated; import into the WireGuard app to use)
Cost / safety
cpx11 ≈ €0.007/hr. Credentials are read from ~/Code/Libraries/ts-cloud/.env at runtime and never committed. destroy.ts removes all created resources.