Skip to content

Self-host wg-easy

updated Sep 2026prices checked · Jul 2026Install verified on Ubuntu 26.04 · Sep 2026 · how we test
We earn commissions when you shop through the links below. Full disclosure →

The easiest way to run your own WireGuard VPN server, with a clean web UI for adding and managing clients — a self-hosted alternative to Tailscale and commercial VPNs for private access to your network from anywhere.

Key facts

LicenseAGPL-3.0
StackTypeScript, Node.js
Min RAM256 MB
Official imageyes
Measured idle RAM172 MB
Difficulty
Our recommendation

Pick wg-easy when you want one WireGuard server with a web UI and nothing else to learn: add a client, scan the QR code, done. The v15 line (a full rewrite; v15.4.0 as of September 2026) lists one-time links, client expiry, 2FA, OIDC login, Prometheus metrics, IPv6 and per-client firewall rules among its features, all in one AGPL-3.0 container, and the documented requirements are just a host you manage, a domain or public IP, `curl`, and x86_64 or arm64. It is a hub-and-spoke VPN, not a mesh — every client routes through this one box — which is exactly why it is the simplest pick here.

Follow the wg-easy deploy guide →

What you need

  • Any VPS with at least 256 MB of RAM
  • A domain you control — most self-hosted setups need HTTPS in front of them
  • Under an hour if you've used Docker before
Where to host itaffiliate disclosure
Hetzner Cloudrun it on
From $23.59/mo · 2 vCPU / 4 GB / 80 GB · EU + US
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
From $4/mo · 1 vCPU / 1 GB / 20 GB · US + EU + Asia
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
From $6/mo · 1 vCPU / 1 GB / 25 GB · US + EU + Asia
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Install

Run these commands on your server:

# wg-easy — official docker run (docs → Examples → Docker Run); web UI on 51821/tcp, tunnel on 51820/udp
# 1. One-time IPv6-enabled bridge network the container gets fixed addresses on
docker network create -d bridge --ipv6 --subnet 10.42.42.0/24 --subnet fdcc:ad94:bacf:61a3::/64 wg
# 2. The container. The /lib/modules mount is required — without it wg0 never comes up.
docker run -d --net wg -e INSECURE=true --name wg-easy \
  --ip6 fdcc:ad94:bacf:61a3::2a --ip 10.42.42.42 \
  -v ~/.wg-easy:/etc/wireguard \
  -v /lib/modules:/lib/modules:ro \
  -p 51820:51820/udp -p 51821:51821/tcp \
  --cap-add NET_ADMIN --cap-add SYS_MODULE \
  --sysctl net.ipv4.ip_forward=1 --sysctl net.ipv4.conf.all.src_valid_mark=1 \
  --sysctl net.ipv6.conf.all.disable_ipv6=0 --sysctl net.ipv6.conf.all.forwarding=1 --sysctl net.ipv6.conf.default.forwarding=1 \
  --restart unless-stopped ghcr.io/wg-easy/wg-easy:15
# →  http://SERVER_IP:51821 — the first-run wizard creates the admin account. INSECURE=true allows plain HTTP;
#    for HTTPS put Caddy or Traefik in front (docs → Examples) and drop it.

What you take on

wg-easy's defaults are safe, which means a few of them will stop you until you read the docs:

non-negotiableThe web UI refuses plain HTTP unless INSECURE=true. The docs' position is that "This is insecure. You should use a reverse proxy to secure the connection.", and the no-proxy page adds "Make sure that the Web UI is not accessible from outside your local network." Our snippet publishes 51821/tcp directly and does not set INSECURE — put Caddy or Traefik in front for the first login, or set it deliberately for LAN-only access.
non-negotiablev15 is "a complete rewrite": configuration files and environment variables changed, most settings moved into the Admin Panel, and armv6/armv7 hosts "won't be able to migrate to v15". Moving from v14 means backing up wg0.json from the old UI and uploading it in the new setup wizard; old environment variables are not migrated.
non-negotiableIt needs the host kernel's WireGuard: upstream mounts /lib/modules:ro with NET_ADMIN and SYS_MODULE (Podman also wants NET_RAW), and both its compose file and its docker run example set net.ipv4.ip_forward=1, net.ipv4.conf.all.src_valid_mark=1 and the IPv6 forwarding sysctls. Our snippet has the mount and capabilities; add the sysctls if clients connect but cannot reach anything.
non-negotiableINIT_* variables "will only be used during the first start of the container. After that, the setup will be disabled" — later changes go through the UI, not the environment. They also come in groups: "If variables are in the same group, you have to set all of them", and skipping the setup wizard needs all of group 1 (INIT_USERNAME, INIT_PASSWORD, INIT_HOST, INIT_PORT). Our snippet sets only INIT_ENABLED and INIT_HOST, so expect the wizard on first visit unless you add the other three.
non-negotiableOnly UDP 51820 needs to be open on the firewall for clients; the 256 MB floor is our estimate — the docs publish no RAM figure.

An alternative to

Head-to-head

More self-hosted tools

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.