Userspace WireGuard VPN Server — Web Dashboard — SSH Gateway
TunGuard is a self-contained WireGuard server that runs entirely in userspace — no kernel modules, no apt install wireguard, no kernel configuration. It includes a web dashboard for managing peers (clients) and generating ready-to-use configuration files with QR codes for your phone.
sudo ./tanguard -web
# Open http://yourserver:9000 → add clients from your browsercurl -fsSL https://raw.githubusercontent.com/TunGuard/get/main/installer.sh | bashgit clone https://github.com/TunGuard/tanguard-binary.git
cd tanguard-binary
go build -o tanguard .
sudo ./tanguard -webhttp://YOUR_SERVER_IP:9000
On a fresh installation, use the default dashboard credentials:
Username: admin
Password: tanguard
You must change these credentials on your first login.
After logging in, TunGuard will immediately take you to the dashboard login setup screen. Choose a new username and password before continuing.
«Important: Do not leave the default "admin" / "tanguard" credentials in use on an Internet-facing server.»
Your new dashboard credentials are also used by the optional SSH Gateway.
You can connect a normal WireGuard device or provision a MikroTik router.
WireGuard device
- Open Peers.
- Click Generate Config.
- Enter a device name, such as "My Phone".
- Click Generate.
- Download or copy the ".conf" file, or scan the displayed QR code with the WireGuard mobile app.
For MikroTik routers, you can use MikroTik Provision to generate and apply the required RouterOS configuration.
Open:
- Enter your TunGuard server details and follow the provisioning instructions to configure the MikroTik router as a WireGuard client.
This is useful when you want to connect an entire network behind a MikroTik router instead of configuring individual devices.
sudo ./tanguard -web -ssh- The WireGuard VPN listens on UDP port 13231 (default).
- The web dashboard is disabled by default — start it explicitly with
-web(orWEB_ENABLED=true). - When enabled, the web dashboard is at http://yourserver:9000.
- Default web login:
admin/tanguard. On first login you will be required to set a new username and password.
- Open
http://yourserver:9000and log in. - Go to the Peers page, click Generate Config.
- Enter a device name (e.g. "My Phone"), click Generate.
- A complete
.conffile is created — Copy, Download, or scan the QR code with the WireGuard mobile app.
The config will look like this:
[Interface]
PrivateKey = <client-private-key>
Address = 10.100.0.2/32
DNS = 1.1.1.1
[Peer]
PublicKey = <server-public-key>
Endpoint = yourserver:13231
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25| Device | How to import |
|---|---|
| Windows / macOS / Linux | Open the WireGuard app → "Add Tunnel" → "Import from file" |
| iOS / Android | Use the WireGuard app to scan the QR code from the dashboard |
| Linux (wg-quick) | Copy the .conf to /etc/wireguard/wg0.conf, run wg-quick up wg0 |
Your device is now connected to the VPN. All traffic is routed through your server.
If you already have a WireGuard keypair (e.g. from a router or a device that generated its own keys), go to the Peers page → Add Existing Key and enter the public key and the IP you want to assign.
Click the Remove button next to any peer on the Peers page, or call:
curl -X POST http://localhost:9000/api/peer/remove \
-H "Content-Type: application/json" \
-d '{"public_key":"..."}'The dashboard runs on port 9000 (same port as the API). It is disabled by default — enable it with -web or WEB_ENABLED=true. It has three sections:
| Section | What you can do |
|---|---|
| Dashboard | Server status, peer count, data transfer totals |
| Peers | List peers, add/generate configs, view status, download configs, scan QR codes, remove peers |
| Settings | Reference of all configuration options |
The status page refreshes every 10 seconds. You'll see transfer stats, last handshake times, and online/offline status for each peer.
When you log in with the default admin / tanguard credentials for the first time, you are taken straight to a screen that requires you to set a new username and password before you can use the dashboard.
You can change the dashboard login again at any time under Settings → Dashboard Login. The new credentials are saved (hashed) in web_credentials.json inside your DATA_DIR and take effect immediately.
If you forget the dashboard password, reset it from the terminal:
sudo ./tanguard --resetThis removes the stored login and restores the default admin / tanguard. Start the server again and log in to set a new password.
Enable with -ssh or SSH_ENABLED=true. This turns TunGuard into an SSH jump host so you can SSH into any connected peer through the server:
ssh -J admin@yourserver:2222 root@10.100.0.2The SSH gateway authenticates with the same login as the web dashboard — if you change the username or password in Settings → Dashboard Login, the SSH access password changes with it. No separate SSH_USER / SSH_PASSWORD credentials are used.
You can also SSH directly from the web dashboard — click the SSH button next to any online peer to open a browser terminal.
sudo cp tanguard.service /etc/systemd/system/
sudo mkdir -p /var/lib/tanguard
sudo systemctl daemon-reload
sudo systemctl enable tanguard
sudo systemctl start tanguardRe-run the installer (curl -fsSL ...installer.sh | bash). On an existing install it only replaces the binary and keeps your service configuration untouched — your custom ports, subnet, and credentials stay exactly as you configured them. Your data in DATA_DIR is never modified, and the installer saves a safety backup of it to /var/backups/ first.
All settings are configured via environment variables.
| Variable | Default | Description |
|---|---|---|
WG_INTERFACE |
wg0 |
WireGuard interface name |
WG_LISTEN_PORT |
13231 |
WireGuard UDP port (the port clients connect to) |
WG_ADDRESS |
10.100.0.1/24 |
Server IP on the VPN subnet |
WG_SUBNET |
10.100.0.0/24 |
Subnet assigned to VPN clients |
WG_MTU |
1420 |
WireGuard MTU |
API_LISTEN |
:9000 |
Web dashboard + API address |
DATA_DIR |
. |
Directory for keys and peer data |
EXTERNAL_NIC |
auto | External network interface for NAT (auto-detected) |
WEB_ENABLED |
false |
Enable the web dashboard (or use -web) |
WEB_USERNAME |
admin |
Dashboard login username (only used until changed from the dashboard) |
WEB_PASSWORD |
tanguard |
Dashboard login password (only used until changed from the dashboard) |
SSH_ENABLED |
false |
Enable SSH gateway (or use -ssh) |
SSH_LISTEN |
:2222 |
SSH gateway address |
SSH_KEY_FILE |
auto | SSH host key path (auto-generated if missing) |
TUNGARD_API_KEY |
— | API key for PHP / script integration (also settable per-request) |
git clone <repo> && cd tanguard
go build -o tanguard .Requires Go 1.22+. The binary is statically linked — copy it to any Linux server.
Updating TunGuard only replaces the binary — your peers, server key, dashboard login, and SSH host key are stored in DATA_DIR and are never touched by the installer or an update. Still, keep a backup before major changes.
Settings → Backup & Restore → Download Backup saves a .tar.gz containing:
peers.json— all peers (including client private keys for generated configs)server_private.key— the server WireGuard keyweb_credentials.json— the hashed dashboard loginssh_host_key— the SSH gateway host keyapi_key.json— the API keymanifest.json— backup metadata
Use the same Restore Backup button in Settings. Restoring:
- Validates the archive before touching anything.
- Replaces the state files in
DATA_DIR. - Applies the restored key and peers to the running server immediately (no restart required).
If the restored server key differs from the current one, connected clients need to re-import their configs (the new server public key is shown after restore).
Restoring never touches the binary, the systemd service, or your firewall rules.
sudo tar -czf ~/tanguard-backup.tar.gz -C /var/lib/tanguard .- Change the default passwords (
WEB_PASSWORD) in production. The dashboard forces you to set a new web login on first use; usetanguard --resetif you ever lose it. The SSH gateway shares this login. - Protect the API. Every
/api/*endpoint (except/api/health) now requires the dashboard login or an API key. Generate your API key in Settings → API Key and pass it as theX-API-Keyheader. Regenerate it any time it may have leaked. - The web dashboard uses HTTP Basic Auth over plain HTTP by default. Put it behind a reverse proxy with TLS (e.g. Caddy, Nginx, or Traefik) for production use.
- The SSH gateway uses password auth by default. Consider key-based auth for production.
- Client private keys created by Generate Config are stored in
peers.json(mode 0600, insideDATA_DIR) so configs can be re-downloaded / re-scanned from the dashboard. Manually added peers (public key only) have no stored client key.
All /api/* endpoints (except /api/health) require authentication. You can
authenticate either with the dashboard login (-u admin:password) or with
an API key from Settings → API Key, sent as the X-API-Key header (or
Authorization: Bearer <key>).
Get / regenerate your API key from the dashboard at Settings → API Key, or from the terminal:
# Create / rotate the API key (dashboard login required)
curl -X POST -u admin:PASSWORD http://localhost:9000/api/key/regenerateThen call the API with the key:
API_KEY="REPLACE_WITH_YOUR_KEY"
# Server info
curl -H "X-API-Key: $API_KEY" http://localhost:9000/api/status
curl -H "X-API-Key: $API_KEY" http://localhost:9000/api/server_key
curl -H "X-API-Key: $API_KEY" http://localhost:9000/api/peers
# Add a peer with an existing key
curl -X POST http://localhost:9000/api/peer/add \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"public_key":"PEER_PUBKEY_HEX","allowed_ip":"10.100.0.2/32","device_name":"my-device"}'
# Auto-generate a new client config (server creates keypair)
curl -X POST http://localhost:9000/api/peer/generate-config \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"device_name":"my-phone","server_host":"vpn.example.com"}'
# Re-download / re-render the full config (with the client PrivateKey) for a
# peer created via generate-config
curl -X POST http://localhost:9000/api/peer/config \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"public_key":"PEER_PUBKEY_HEX","server_host":"vpn.example.com"}'
# Remove a peer
curl -X POST http://localhost:9000/api/peer/remove \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"public_key":"PEER_PUBKEY_HEX"}'/api/health stays open for uptime checks and exposes no sensitive data.
See INTEGRATION.md for the WebSocket SSH protocol and PHP integration.
MIT