Tiny Linux VPN. One TUN device, one TLS stream, one small frame format.
EasyPN is built for people who want a private network that is easy to inspect, easy to run, and hard to leave messy after it exits. It supports IPv4, IPv6, mTLS, PSK authentication, route cleanup, DNS leak protection, NAT/NAT66, MSS clamping, structured logs, and network namespace integration tests — without a large dependency stack or a custom crypto layer.
IP packet from TUN -> 2 byte length -> TLS HTTP/2 stream -> TUN
That is the protocol. Everything else is careful Linux plumbing.
- Small protocol:
uint16 length + raw TUN packet - Secure transport: TLS 1.3, optional mTLS, mandatory strong PSK
- IPv4 + IPv6: same tunnel, same frame, separate Linux routes
- Clean exit: routes, DNS, NAT, NAT66, MSS and sysctl changes are rolled back
- DNS control: opt-in
systemd-resolvedper-link DNS for route-all mode - MSS handling: TCPMSS clamp-to-PMTU for forwarded traffic
- Safe defaults: no proxy-from-env unless explicitly enabled, no weak PSK
- Real tests: Linux network namespace integration suite
- No Go dependencies: standard library only
- MIT licensed: small project, friendly license
EasyPN is a minimal production candidate for small self-hosted deployments. It is not trying to replace WireGuard, become a VPN platform, or manage many clients. The goal is narrower: a readable, testable, single-client private link that behaves predictably on Linux.
make test
make buildBinaries appear in bin/:
./bin/easypn-server -version
./bin/easypn-client -versionBuild metadata can be embedded at compile time:
make build VERSION=0.1.0 COMMIT=$(git rev-parse --short HEAD)Generate a local CA plus server/client certificates:
./scripts/gen-certs.sh certsThe script refuses to overwrite existing keys. To regenerate deliberately:
EASYPN_CERT_FORCE=1 ./scripts/gen-certs.sh certsFor a real server name or IP:
EASYPN_CERT_SERVER_NAME=vpn.example.com \
EASYPN_CERT_SERVER_IP=203.0.113.10 \
EASYPN_CERT_CLIENT_NAME=laptop \
./scripts/gen-certs.sh certsFiles created:
certs/ca.crt
certs/ca.key
certs/server.crt
certs/server.key
certs/client.crt
certs/client.key
Keep ca.key private. Copy only the needed cert/key files to each machine.
Use a long random PSK. EasyPN rejects PSKs shorter than 32 characters. Prefer an environment variable; command-line arguments can be visible in process lists.
export EASYPN_PSK='replace-with-at-least-32-random-characters'Start the server:
sudo -E ./bin/easypn-server \
-listen :443 \
-cert certs/server.crt \
-key certs/server.key \
-client-ca certs/ca.crt \
-nat=trueStart the client:
sudo -E ./bin/easypn-client \
-server vpn.example.com \
-ca certs/ca.crt \
-cert certs/client.crt \
-key certs/client.key \
-route-all-server accepts either a host or the full stream URL:
vpn.example.com
https://vpn.example.com/api/v1/stream
EasyPN does not change DNS silently. For full-tunnel mode, pass DNS servers explicitly:
sudo -E ./bin/easypn-client \
-server vpn.example.com \
-ca certs/ca.crt \
-cert certs/client.crt \
-key certs/client.key \
-route-all \
-dns 1.1.1.1,9.9.9.9 \
-dns6 2606:4700:4700::1111This uses resolvectl to bind DNS to the TUN interface and reverts it on exit.
DNS options require -route-all.
Default MTU is 1400:
-mtu 1400For difficult networks, use the conservative fallback:
-mtu 1280The server enables MSS clamping by default:
-mss-clamp=trueIt adds cleanup-tracked TCPMSS --clamp-mss-to-pmtu rules for forwarded TCP
traffic leaving the EasyPN TUN interface.
Server:
-listen :443
-tun easypn0
-tun-addr 10.0.0.1/24
-tun-addr6 fd00:4550::1/64
-mtu 1400
-cert server.crt
-key server.key
-client-ca ca.crt
-nat=true
-nat6=false
-mss-clamp=true
-out-iface eth0
-version
Client:
-server vpn.example.com
-tun easypn0
-tun-addr 10.0.0.2/24
-tun-addr6 fd00:4550::2/64
-mtu 1400
-ca ca.crt
-cert client.crt
-key client.key
-route-all
-dns 1.1.1.1,9.9.9.9
-dns6 2606:4700:4700::1111
-insecure-skip-verify
-proxy-from-env=false
-version
Logs are single-line key=value records:
ts=2026-07-08T20:26:41Z level=info component=server event=server_ready listen=:443 tun=easypn0 mtu=1400 nat=true nat6=false
ts=2026-07-08T20:26:42Z level=info component=client event=tunnel_established server_version=1
They are readable in a terminal and friendly to journald, grep, Loki, Vector, or other collectors.
Fast checks:
make testReal tunnel tests:
sudo make integration-testThe integration suite creates temporary Linux network namespaces, veth links, certificates, server/client processes, and checks IPv4, IPv6, mTLS, wrong PSK rejection, route cleanup, NAT/NAT66 cleanup, MSS cleanup, and the audit-critical stream path.
make release VERSION=0.1.0 COMMIT=$(git rev-parse --short HEAD)
cat dist/SHA256SUMSThe release archive contains:
bin/easypn-server
bin/easypn-client
README.md
LICENSE
go.mod
scripts/gen-certs.sh
The short list before a stronger production label:
/healthz and /metrics
heartbeat / idle stream watchdog
systemd units with hardening
MIT. See LICENSE.