DOCS · PRIVATE VPN

WireGuard, relayed.

A WireGuard VPN reachable from anywhere — no port-forward, no public IP. On Linux, one command does it all: mytunnel wg up <network> brings up the WireGuard server and the relay that makes it reachable, and keeps them alive. Either way, the thing to understand is that the WireGuard server runs on your own box — 21tunnel only relays. Free on all plans.

The mental model (read this first)

21tunnel does not host a WireGuard server for you. You run the WireGuard server on your own machine; 21tunnel reserves a public UDP endpoint and relays datagrams to it — so remote clients reach your box even though it has no public IP and no forwarded port. Your private keys never leave your device; we store only public keys.

  WG client (phone / laptop)                    YOUR box
  official WireGuard app                        two things run here:
        │                                         (1) wg-quick up wg0   ← the WG server
        │  Endpoint =                             (2) mytunnel udp 51820 --public-port 31099
        │  agent.21tunnel.com:31099                       ▲   ← the relay (MUST stay running)
        └───────────────►  21tunnel relay  ──────────────┘
                           (reserved UDP port 31099)

⚠ You run TWO processes — and the relay must stay alive

wg-quick up wg0 starts WireGuard locally. That alone does nothing reachable — your box still has no public address. The VPN is only reachable while mytunnel udp 51820 --public-port 31099 is also running and connected to 21tunnel.

If you skip the relay, or it stops (you closed the terminal, logged out of SSH, the box slept), the symptom is exactly what most people hit: the WireGuard interface looks "up," packets go out, but nothing comes back — one-way / dead traffic.

On Linux you don't have to juggle this yourself: mytunnel wg up <network> runs both pieces and keeps the relay reconnecting (see Bring it up). The two-process model above is what it does under the hood.

1 Create a VPN

In the dashboard at login.21tunnel.com/vpn, click New VPN. Pick a name and a private subnet — any IPv4 range, prefix /8/30 (e.g. 10.99.0.0/24). The server takes .1; each client gets the next free address.

2 Bring it up (one command on Linux)

On a Linux box, install the CLI and run one command. It generates the server keypair (the private key stays on the box), registers it, brings the WireGuard interface up, starts the relay, and keeps it reconnecting — the whole thing, so there's no two-process dance to get wrong.

Linux — one command

# on the box that will host the VPN server:
curl -fsSL https://login.21tunnel.com/install.sh | sh
mytunnel login                       # authenticate this machine once

# bring the whole VPN up (root needed for wg-quick):
sudo -E mytunnel wg up "my-vpn"      # the network's name (or id) from the dashboard

-E keeps your login/credentials for the sudo'd process. Leave it running — Ctrl+C tears the interface back down. It reconnects on its own if the network blips, and re-running reuses the same server key so existing clients keep working. Requires mytunnel v0.3.2+; the dashboard badge for the network turns green once the relay is live. For reboot-persistence, run it as a service (see Keep it alive).

macOS / Windows / manual — the two pieces by hand

mytunnel wg up is Linux-first. On other platforms (or if you'd rather manage the interface yourself), do the two pieces the managed command automates: click Set up server in the dashboard (your browser generates the keypair and hands you wg0.conf with the private key spliced in once), then on your box run both:

# 1) start the WireGuard server locally (or import wg0.conf into the WireGuard app)
wg-quick up wg0

# 2) relay its UDP port through 21tunnel — the exact command is printed on
#    your wg0.conf, with your reserved port already filled in:
mytunnel udp 51820 --public-port 31099

51820 is your WireGuard ListenPort; 31099 is the public UDP port 21tunnel reserved. Both must stay running — see Keep it alive. On macOS, mytunnel wg up prints these two steps for you with the ports already filled in. (Requires mytunnel v0.3.1+ for the udp subcommand.)

3 Add clients

Click Add client. Your browser generates the client keypair and gives you a .conf file and a QR code. Import the .conf into the official WireGuard app, or scan the QR on mobile. Clients never touch your box directly — their Endpoint points at the 21tunnel relay.

Private keys are generated in your browser and shown once — download or scan them immediately. 21tunnel stores only public keys.

Added a client after running mytunnel wg up? Re-run it (or restart the service) so the server picks up the new peer — wg up loads the client list when it starts.

Keep it alive

The VPN is only reachable while the relay runs. mytunnel wg up already reconnects on its own while it's running, but a foreground process dies on reboot — run it as a service for anything real.

Managed (systemd, Linux) — recommended

# stash your tunnel key where only root can read it
sudo mkdir -p /etc/21tunnel
echo 'MYTUNNEL_API_KEY=mtk_your_key_here' | sudo tee /etc/21tunnel/vpn.env >/dev/null
sudo chmod 600 /etc/21tunnel/vpn.env

sudo tee /etc/systemd/system/21tunnel-vpn.service >/dev/null <<'UNIT'
[Unit]
Description=21tunnel managed WireGuard VPN
After=network-online.target
Wants=network-online.target

[Service]
EnvironmentFile=/etc/21tunnel/vpn.env
ExecStart=/usr/local/bin/mytunnel wg up "my-vpn"
Restart=always
RestartSec=3
User=root

[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl enable --now 21tunnel-vpn

One unit brings up the interface and the relay and keeps both alive across reboots and network blips. Check it with systemctl status 21tunnel-vpn and journalctl -u 21tunnel-vpn -f.

Doing the manual (mytunnel udp …) flow instead? Keep that relay alive the same way:

Quick (survives SSH logout, not reboot)

nohup mytunnel udp 51820 --public-port 31099 > mytunnel-relay.log 2>&1 &

Proper (systemd — survives reboot)

sudo tee /etc/systemd/system/mytunnel-vpn.service >/dev/null <<'UNIT'
[Unit]
Description=21tunnel WireGuard relay
After=network-online.target wg-quick@wg0.service
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/mytunnel udp 51820 --public-port 31099
Restart=always
RestartSec=3
User=root

[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl enable --now wg-quick@wg0
sudo systemctl enable --now mytunnel-vpn

Restart=always means the relay reconnects on its own if the network blips. Pair it with wg-quick@wg0 so both the WireGuard server and the relay come back after a reboot.

Verify it's actually working

CheckWhat you should see
Dashboard network badge Green with the endpoint (agent.21tunnel.com:31099). If it's amber "relay offline — run relay," the relay isn't running — that's the fix, right there.
Relay process mytunnel udp … prints tunnel registered … public_url=udp://agent.21tunnel.com:31099 and stays running.
WireGuard handshake (client) wg show lists a recent latest handshake and non-zero transfer: … received. Zero received = the return path isn't up (relay not running).
Reachability From a client, ping 10.99.0.1 (your server's VPN IP) succeeds.

Split tunnel vs full tunnel

The generated client .conf defaults to a split tunnel — only traffic to the VPN subnet goes over WireGuard; everything else uses the client's normal internet.

Client AllowedIPsBehaviour
10.99.0.0/24 (default)Split tunnel — reach the VPN subnet (server + other clients), normal internet otherwise.
0.0.0.0/0, ::/0Full tunnel — all the client's traffic routes through your server box (which must NAT/forward it). Edit the client .conf to switch.

? Troubleshooting

SymptomCause & fix
Handshake works but no data / one-way traffic / packets go out, nothing returns The relay isn't running. The simplest fix is to run it managed: sudo -E mytunnel wg up <network> (it keeps the relay up for you). On the manual flow, wg-quick up wg0 alone isn't enough — also start and keep alive mytunnel udp 51820 --public-port 31099. The dashboard badge confirms: amber = no relay, green = live. See Keep it alive.
error: unrecognized subcommand 'udp' or 'wg' Old CLI. udp needs mytunnel ≥ 0.3.1 and wg needs ≥ 0.3.2 — re-run curl -fsSL https://login.21tunnel.com/install.sh | sh, then check mytunnel --version. (macOS and arm64 builds currently lag on /dl — use the manual udp flow there for now.)
Dashboard shows "relay offline — run relay" Working as intended — the network is provisioned but no relay is bound. Run the mytunnel udp … command shown under the network. The badge flips green within a second of the relay connecting.
It worked, then stopped after I closed my terminal / logged out The relay was a foreground process and got killed. Run it under nohup or systemd (see Keep it alive).
Client can't reach other hosts on my LAN (only the server) Split tunnel + no forwarding. Either widen the client's AllowedIPs and enable IP forwarding/NAT on your server box, or add those hosts' routes. See Split vs full.
Config download 409s You haven't set up the server yet — click Set up server first so a relay endpoint is reserved.

Next