A lightweight, self-hosted VPN tunnel for developers and teams. One binary, one open port — every application on the machine routes through an encrypted TLS 1.3 tunnel automatically.
Built for the common case: a team that needs secure access to services running in Docker containers on a cloud VM, without exposing ports to the internet.
VM (Docker containers) ← auth-vpn server (configurable port, TLS)
│
│ encrypted TLS 1.3 tunnel
│
├── Dev laptop (auth-vpn client)
├── QA laptop (auth-vpn client)
├── Test VM (auth-vpn client)
└── GitHub Actions (auth-vpn action — ephemeral token per job)
The server creates a TUN interface at 10.8.0.1 and assigns each connecting client an IP from 10.8.0.2–254. All IP traffic to the 10.8.0.0/24 subnet is routed through the tunnel at the OS level — no per-app configuration needed.
Every service on the server is reachable at 10.8.0.1:<port> — databases, internal APIs, dashboards, anything running on that machine.
Internet speed is not affected. auth-vpn is a split-tunnel — only traffic to
10.8.0.0/24goes through the tunnel. All other traffic uses your normal connection.
curl -fsSL https://github.com/adishM98/auth-vpn/releases/latest/download/install.sh \
| sudo bash -s -- --serverNo Go, no Git, nothing to install first. The script detects your platform, downloads the right binary, generates a self-signed TLS cert, creates an initial access token, and registers a systemd service.
At the end you'll see:
─────────────────────────────────────────────
Connect with:
auth-vpn connect <server-ip>:7777 --token abc123xyz
Web dashboard: http://localhost:9100/ui
API key: <generated-key>
─────────────────────────────────────────────
Save that token — it's the first team member's token. Create one per person, one for CI, one for each external service. Revoke any individual token without affecting anyone else.
macOS / Linux:
curl -fsSL https://github.com/adishM98/auth-vpn/releases/latest/download/install.sh \
| sudo bashThen connect:
auth-vpn connect <server-ip>:7777 --token abc123xyz
# Background + auto-reconnect
auth-vpn connect <server-ip>:7777 --token abc123xyz --background --reconnect
auth-vpn status # check tunnel
auth-vpn disconnect # disconnectSave a profile so you never type the token again:
auth-vpn profile save staging \
--host <server-ip>:7777 \
--token abc123xyz
auth-vpn connect staging --background --reconnectSharing with your team? docs/dev-qa-guide.md is a standalone onboarding doc for developers and QA — covers install, connect, save a profile, and troubleshooting. Send it to anyone who needs access.
- TLS 1.3 — all traffic is encrypted in transit
- Token auth — SHA-256 hashed tokens stored on server, raw token never persisted
- SSH key auth — connect the embedded SSH server using an RSA key pair; generate server-side or register your own; system
authorized_keysautomatically trusted - Rate limiting — 5 failed auth attempts = 60 second IP ban
- Ephemeral tokens —
--github-actionflag auto-mints a unique per-job token so parallel CI jobs never share credentials; token is revoked automatically on exit - Token controls — permanent, expiring, or one-time tokens; revoke any token instantly without restarting the server
- ACL rules — per-device allow/deny lists enforced at the packet level (optional)
- API key — Bearer token required for the Web UI and HTTP API (optional)
- Single port — close all container ports from the internet; only the tunnel port (default
7777, configurable at install or viaserver change-port) needs to be open - IP whitelist — static IPs/CIDRs (VMs, PaaS) can connect without a token; managed from the dashboard
- Direct forwards — expose backend ports to whitelisted IPs with no auth-vpn client required
- SSH tunnel — tools like your app or BI tools connect via standard SSH port forwarding; no auth-vpn binary needed on the client side
Full command reference: docs/cli-reference.md
Key commands at a glance:
auth-vpn connect <host:port> --token <token> # connect
auth-vpn connect <host:port> --token <token> --background --reconnect
auth-vpn status / disconnect
auth-vpn server tokens add --name alice # token management
auth-vpn server tokens revoke --name alice
auth-vpn profile save <name> --host <h> --token <t>After server install, a dashboard is available at http://localhost:9100/ui:
- Live stats, connected clients, token management, IP whitelist, SSH keys, and direct forwards
- Access remotely via
ssh -L 9100:localhost:9100 user@<vm-ip>
Managing multiple auth-vpn servers means juggling multiple dashboards. The hub gives you a single place to manage all of them.
auth-vpn hub serve # starts on http://127.0.0.1:9200
auth-vpn hub serve --port 9300 --bind 0.0.0.0Open http://127.0.0.1:9200 and add servers from the UI — no YAML editing. Every change you make in the hub (tokens, clients, whitelist, forwards) goes directly to the selected server. There is no hub-side state for managed resources.
Hub (127.0.0.1:9200)
├── prod-db → https://10.0.0.1:9100 ● online 3 clients
├── staging → https://10.0.0.2:9100 ● online 1 client
└── eu-west → https://10.0.0.4:9100 ○ offline
See docs/hub.md for setup, TLS fingerprint handling, adding servers, and the hub API reference.
Detailed docs for each server-side feature: docs/server-features.md
| Feature | What it does |
|---|---|
| IP whitelist | Static IPs/CIDRs connect without a token; managed from dashboard or API |
| Direct forwards | Expose backend ports to whitelisted IPs — no auth-vpn client on the other side |
| SSH tunnel | Embedded SSH server on port 2222 — any SSH-capable tool can reach backend services |
| ACL rules | Per-device allow/deny lists enforced at the packet level |
| HTTP API | Full REST API at :9100/api/ for tokens, clients, whitelist, forwards, and SSH keys |
curl http://localhost:9100/metricsauth_vpn_uptime_seconds auth_vpn_active_connections
auth_vpn_connections_total auth_vpn_auth_failures_total
auth_vpn_bytes_in_total auth_vpn_bytes_out_total
auth_vpn_dropped_packets_total
# Server — prompts for tunnel port (default 7777)
curl -fsSL https://github.com/adishM98/auth-vpn/releases/latest/download/install.sh \
| sudo bash -s -- --server
# Server on a specific port (skips the prompt)
curl -fsSL https://github.com/adishM98/auth-vpn/releases/latest/download/install.sh \
| sudo bash -s -- --server --port=8888
# Client
curl -fsSL https://github.com/adishM98/auth-vpn/releases/latest/download/install.sh \
| sudo bash
# Pin to a specific version
curl -fsSL https://github.com/adishM98/auth-vpn/releases/latest/download/install.sh \
| sudo bash -s -- --version=v1.2.3Non-interactive installs (piped curl, CI scripts): the port prompt is automatically skipped when stdin is not a terminal. Pass
--port=<n>explicitly, or set theTJ_VPN_PORTenvironment variable.
git clone https://github.com/adishM98/auth-vpn && cd auth-vpn
make deploy VM=azureuser@<vm-ip> # server
make deploy-client VM=azureuser@<vm-ip> # client
make deploy VM=azureuser@<vm-ip> PORT=8888 # custom portgit clone https://github.com/adishM98/auth-vpn && cd auth-vpn
sudo ./install.sh # client
sudo ./install.sh --server # serverContainers must bind to 0.0.0.0, not 127.0.0.1:
# docker-compose.yml
services:
postgres:
image: postgres:16
ports:
- "5432:5432" # ✅ accessible through tunnel
# not: "127.0.0.1:5432:5432" — tunnel can't reach thisRun auth-vpn as a pod inside your cluster so your laptop (or CI) can reach every ClusterIP service directly — databases, dashboards, internal APIs — without a public LoadBalancer IP for each one.
Your laptop ──TLS──► auth-vpn LoadBalancer ──► ClusterIP services
(one public IP) (stay private)
Quick start:
# 1. Build your image from the included Dockerfile and push to your registry
docker build -t <your-registry>/auth-vpn:latest .
docker push <your-registry>/auth-vpn:latest
# 2. Set your image in k8s/deployment.yaml (the only line that requires a real value)
# Replace: image: <your-registry>/auth-vpn:latest
# With: image: myacr.azurecr.io/auth-vpn:latest (or your actual tag)
# 3. Set your namespace across all three manifests (default: "default")
sed -i '' 's/namespace: default/namespace: your-namespace/g' k8s/*.yaml
# Linux: sed -i 's/namespace: default/namespace: your-namespace/g' k8s/*.yaml
# 4. Apply
kubectl apply -f k8s/pvc.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
# 5. Get the admin token from first-boot logs
kubectl logs -n your-namespace deploy/auth-vpn
# 6. Connect — namespace-scoped (only services you explicitly list)
auth-vpn connect <LB-IP>:7777 --token <token> --route 10.0.x.x/32 --route 10.0.y.y/32
# Or route the entire cluster service CIDR
auth-vpn connect <LB-IP>:7777 --token <token> --route <service-cidr>See docs/k8s-deployment.md for the full guide — namespace setup, image registry options, connecting from a laptop or CI, token management, and troubleshooting.
auth-vpn ships a ready-made GitHub Action. Add two steps to any job and every service on the server VM becomes reachable at 10.8.0.1. Tokens are created and revoked automatically per job run.
steps:
- name: Connect to VPN
uses: adishM98/auth-vpn@v2
with:
server: ${{ secrets.VPN_SERVER }}
api-key: ${{ secrets.VPN_API_KEY }}
# use 10.8.0.1 as the host for any service on the VM
- name: Disconnect VPN
if: always()
uses: adishM98/auth-vpn/disconnect@v2Secrets to add in Settings → Secrets and variables → Actions:
| Secret | Value |
|---|---|
VPN_SERVER |
<vm-public-ip>:7777 |
VPN_API_KEY |
The api_key from /etc/auth-vpn/server.yaml |
See docs/github-actions.md for parallel matrix jobs, proxy mode, action inputs reference, non-GitHub CI, and troubleshooting.
Requires Go 1.22+.
git clone https://github.com/adishM98/auth-vpn && cd auth-vpn
go build -o auth-vpn ./cmd # current platform
make build-linux # Linux amd64
make build-mac-arm # macOS Apple Silicon
make build-mac-intel # macOS Intel
make build-all # all three platformsauth-vpn status # tunnel status
ping 10.8.0.1 # ping the VM through the tunnel
psql -h 10.8.0.1 -p 5432
mysql -h 10.8.0.1 -P 3306 -u root -p
redis-cli -h 10.8.0.1 -p 6379
curl http://10.8.0.1:8080/health| Target | Description |
|---|---|
make build-linux |
Build Linux amd64 → dist/ |
make build-mac-arm |
Build macOS arm64 → dist/ |
make build-mac-intel |
Build macOS amd64 → dist/ |
make build-all |
All three platforms |
make install |
Build + install on this machine (client) |
make install-server |
Build + install + configure server on this machine |
make deploy VM=user@host |
Build + deploy + configure server on remote VM |
make deploy-client VM=user@host |
Build + deploy client on remote VM |
make release |
Build all + publish GitHub release |
make clean |
Remove dist/ |
sudo auth-vpn updateThe update command checks the latest GitHub release, downloads the right binary, atomically replaces the running file, and restarts the systemd service if active (Linux only). No config files are touched.
Re-running the installer on an already-configured server is also safe — preserves server.yaml, skips TLS cert regeneration, keeps all tokens.
sudo auth-vpn server change-portUpdates server.yaml and the systemd service, then restarts. Remember to update your firewall/NSG rules and any saved client profiles.
auth-vpn version # check current versionauth-vpn disconnect
sudo rm /usr/local/bin/auth-vpn
rm -rf ~/.auth-vpnsudo systemctl stop auth-vpn && sudo systemctl disable auth-vpn
sudo rm /usr/local/bin/auth-vpn
sudo rm -rf /etc/auth-vpn
sudo rm -f /etc/systemd/system/auth-vpn.service
sudo systemctl daemon-reload
sudo rm -f /var/run/auth-vpn.sockSee docs/architecture.md for component map, packet flow, wire protocol, and key design decisions.