wg-easy is a free Docker image that runs WireGuard with a web admin panel on your own server: you add devices, download their configs and show QR codes from the browser instead of editing wg0.conf. Version 15 is current; it needs a VPS with UDP 51820 open.
WireGuard is fast and modern, but the stock setup is files and the command line: generate keys, hand-edit wg0.conf, run wg set to add each device, copy configs around. For one device that is fine. For a family, a few laptops and a couple of phones, it gets tedious - and tedious is where mistakes (and leaked keys) happen. wg-easy solves exactly that: it keeps the real WireGuard underneath, untouched, and bolts a clean web dashboard on top. Add a client in two clicks, show a QR code for a phone, watch who is connected and how much they are transferring. This is the full 2026 setup, including the password change that trips up everyone upgrading.
What you need: a Linux VPS with a public IPv4 address and Docker, with UDP 51820 open. The lowest monthly price we read for such a box is HostPapa Unmanaged VPS Lite (affiliate link), 2.95 $ a month on a 3-year term, then the regular rate; this guide uses a Contabo Cloud VPS as its example host; the host requirements are detailed below.
Still choosing the server? Our cheapest-VPS ranking for WireGuard compares what each provider really bills for a usable endpoint.
Tip: if the tunnel refuses to connect, the cause is almost always the port. See our guide to WireGuard's default port (UDP 51820) and how to open, forward or change it.
Skip the copy-paste: the free wg-easy docker-compose generator writes the v15 docker-compose.yml for your setup in the browser: admin panel on localhost only or HTTPS on your own domain through Caddy, and an optional unattended first start.
Generate your wg-easy v15 docker-compose.yml here
Enter your server address and how you reach the panel: you get the compose file, the Caddyfile if needed and the install commands.
01Describe your server
Everything is generated in your browser. Nothing you type, including the password, is sent to our servers.
The address WireGuard clients connect to.
An A record for this name must point to the server, with ports 80 and 443 open.
Without unattended setup, open the panel after the first start: the wizard asks for the admin account, then the host and port. Enter the same UDP port as above.
02Copy the files
# wg-easy v15 - generated on vpnsmith.com/en/tools/wg-easy-compose-generator
# Host: vpn.example.com - WireGuard port: 51820/udp - panel: https://wg.example.com
volumes:
etc_wireguard:
caddy_data:
caddy_config:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
environment:
- PORT=80
- TRUSTED_PROXIES=10.42.42.43,fdcc:ad94:bacf:61a3::2b
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp" # WireGuard: the only port clients need
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
caddy:
image: caddy:2-alpine
container_name: caddy
ports:
- "80:80/tcp"
- "443:443/tcp"
- "443:443/udp"
networks:
wg:
ipv4_address: 10.42.42.43
ipv6_address: fdcc:ad94:bacf:61a3::2b
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64
wg.example.com {
reverse_proxy wg-easy:80
}
03Run the install commands
# 1. Firewall: SSH first, then the WireGuard port
sudo ufw allow OpenSSH
sudo ufw allow 51820/udp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw enable
# Note: ports published by Docker bypass ufw. Also open/close them in your provider firewall.
# 2. Create the folder and paste the generated file(s)
sudo mkdir -p /etc/docker/containers/wg-easy
cd /etc/docker/containers/wg-easy
sudo nano docker-compose.yml
sudo nano Caddyfile
# 3. Start wg-easy
sudo docker compose up -d
# Watch the logs (Ctrl+C to quit)
sudo docker compose logs -f wg-easy
# 4. Open the panel: https://wg.example.com
This file needs a server with a public IP
A Contabo Cloud VPS 4 (4 vCPU, 8 GB RAM, dedicated IPv4, unlimited traffic) costs 5,50 € a month on a 24-month term (price checked 17 Oct 2026), which covers all wg-easy needs.
Affiliate link: we earn a commission at no extra cost to you.
Card declined, or prefer a US host? Ultahost VPS Basic is 5.52 $/mo on 24 months (read on 21 October 2026) and also takes Bitcoin, Alipay, WeChat Pay and Qiwi; check its terms for your country. See Ultahost VPS
What wg-easy actually is (and isn't)
wg-easy is a single Docker container that runs a WireGuard server and a small web application to manage it. The important part to understand up front: it does not replace or re-implement WireGuard. The tunnel is still the same kernel WireGuard, with the same keys and the same protocol on the wire. wg-easy is management, not a new VPN. That has two consequences:
- No speed penalty. Throughput and latency are identical to a hand-written config, because the data path never touches the web app.
- The web UI is the attack surface. The dashboard can create clients, so whoever controls it controls your VPN. Protecting that panel is the whole security story (more below).
What you get for that: a browser page listing every client, one-click create/disable/delete, a QR code per client for phones, and a live per-peer transfer counter. For onboarding a handful of devices without touching a terminal, nothing is faster.

Before you start: the host you need
wg-easy is a container, so you need a Linux host that can run Docker and has a public IP. For a VPN you reach from anywhere, that means a small VPS - WireGuard is so light that a 4-6 €/month instance saturates its network port long before the CPU notices. Contabo is the example host here for its price-to-bandwidth ratio, and the bare-metal walkthrough is in our Contabo + WireGuard self-host guide if you ever want to compare the manual route.
If price is what decides, the lowest sticker we found is HostPapa's Unmanaged VPS Lite (affiliate link), read on its VPS page on 28 October 2026: 1 CPU core, 1 GB of RAM, 25 GB of NVMe, 4 TB of monthly transfer and root SSH access, 2.95 $ a month with a 3-year term, then the regular rate, with a 30-day money-back guarantee. Root access means you install Docker yourself, and 1 GB holds Docker plus the wg-easy container for a personal VPN. HostPapa does not publish the port speed, so confirm the public IPv4 and the renewal price at checkout.
If you would rather have more headroom than the lowest price, Contabo is the host this guide was written against.
Get a Contabo VPS to run wg-easy →
A second option if you would rather have a 30-day refund window: Ultahost's VPS Basic, read on ultahost.com on 21 October 2026 at 5.52 $ a month billed for 24 months (list 9.19 $), with 1 CPU core, 1 GB of DDR5, 30 GB of NVMe, a dedicated IPv4 and a 30-day money-back guarantee. 1 GB is enough for Docker plus the wg-easy container on a personal VPN. The plan publishes no port speed or traffic allowance, so check those before you rely on it for heavy use.
On the host you need Docker and the Compose plugin installed, the WireGuard kernel module available (every modern distro, Linux 5.6+, ships it), and the ability to open UDP 51820 in both the host firewall and your provider's security group.
The docker-compose file (wg-easy v15)
Version check (24 October 2026): the current release is v15.5.0 (24 October 2026). v15 is a full rewrite:
WG_HOST,PASSWORD_HASHand most other v14 variables are gone, and their settings now live in the web UI. A v14 tutorial pasted onto the:latestimage starts a container that silently ignores your host and password. Everything below is the v15 way, taken from the project's own compose file and docs.v15.5.0 in one minute: it ships the October 2026 Node.js security fixes, so update even if nothing is broken:
sudo docker compose pull && sudo docker compose up -d(the:15tag used below follows the new release, and your clients and keys stay in the volume). It also moves its built-in AmneziaWG support to version 3.1, addsWG_INTERFACE(defaultwg0) to run two instances on one host, and addsTRUSTED_PROXIESfor a panel behind a reverse proxy (see the lock-down section). Source: the v15.5.0 release notes and the project's optional configuration docs.
This file follows the official one, with one deliberate change: the web UI is published on 127.0.0.1 only, so the admin panel is never reachable from the internet and you open it through an SSH tunnel. Want an HTTPS panel on your own domain through Caddy, or a fully unattended first start? Our wg-easy docker-compose generator writes that variant for you.
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
environment:
- INSECURE=true # panel over plain HTTP, reached only through an SSH tunnel
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp" # WireGuard data: open to the internet
- "127.0.0.1:51821:51821/tcp" # web UI: bound to localhost only
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64
volumes:
etc_wireguard:
What each part is doing, and where it usually breaks:
127.0.0.1:51821:51821/tcpkeeps the panel off the public interface. This matters more than it looks: ports that Docker publishes bypass UFW, so a plain"51821:51821/tcp"is open to the world even ifufw statussays otherwise.INSECURE=trueis required because the panel is served over plain HTTP here (the encryption is the SSH tunnel). Without it, v15 marks its login cookieSecure, the browser drops it over HTTP, and you land back on the login page after every attempt.cap_add: NET_ADMINandsysctls: net.ipv4.ip_forward=1let the container build the WireGuard interface and route for your clients. Missing either gives you a tunnel that connects and then routes nothing.- The named volume
etc_wireguardholds the database with every key and client, sodocker compose downfollowed byupkeeps your devices.
Put it in place and start it, using the directory layout of the official docs:
sudo mkdir -p /etc/docker/containers/wg-easy
cd /etc/docker/containers/wg-easy
sudo nano docker-compose.yml # paste the file above
sudo docker compose up -d
sudo docker compose logs -f wg-easy # Ctrl+C once it is listening
Then reach the panel from your own machine through the tunnel:
# on your own computer, not on the server
ssh -L 51821:127.0.0.1:51821 user@YOUR_SERVER_IP
# then open http://localhost:51821 in your browser
First start: the setup wizard (no more PASSWORD_HASH)
The bcrypt PASSWORD_HASH and the wgpw command that every v14 tutorial spends a section on are gone. On the first visit, v15 opens a setup wizard instead:
- Create the admin account: a username and a password. wg-easy stores the password itself as an argon2 hash, so there is nothing to generate or escape in a
.envfile. - "Do you have an existing setup?" Answer No for a fresh install (Yes is the v14 import, below).
- Host and port: the public IP or domain your clients will dial (an IPv6 address goes in brackets, such as
[2001:db8::1]) and the UDP port,51820unless you changed the mapping. Clients get this exact value as theirEndpoint.
DNS, the client address range and the default allowed IPs are now set in the Admin Panel, not in environment variables. To skip the wizard entirely (Ansible, cloud-init), v15 reads INIT_ENABLED=true with INIT_USERNAME, INIT_PASSWORD, INIT_HOST and INIT_PORT on the very first start only; remove them once the container is up so the password does not sit in the file. The generator can write those lines for you.
Locked out? Reset the admin password from the server:
cd /etc/docker/containers/wg-easy
sudo docker compose exec -it wg-easy cli db:admin:reset
Coming from v14
There is no in-place upgrade. In the old panel, click Backup to download wg0.json and note your old environment variables, run docker compose down, start the v15 file above, then answer Yes in the wizard and upload wg0.json: keys and clients come across, the old variables do not, so set DNS and allowed IPs again in the Admin Panel. One hard limit from the migration guide: armv6 and armv7 hosts (older Raspberry Pi) cannot move to v15 and have to stay pinned on :14.
Staying on v14, or leaving the panel altogether? The same backup file can be pasted into WireGuard Manager, our hosted dashboard (free for 1 server and 3 peers): since wg0.json holds each client's private key, every device's .conf and QR code are available again at once, and it writes a plain wg0.conf for wg-quick if you retire wg-easy. Nothing on the server changes when you import.
Add your first client
Inside the dashboard, click New Client, give it a name (e.g. laptop, pixel-phone), and wg-easy generates the keypair and a ready config. For a computer, download the .conf and import it into the WireGuard app or config templates; for a phone, click the QR icon and scan it with the WireGuard mobile app - no typing, no file transfer. The per-client toggle disables a device instantly without deleting it, which is the right way to revoke a lost phone. More on this in PiVPN Client Management.
Client imported but nothing works? Three causes cover almost every case, and each has a tool. Status stays at "no handshake": UDP 51820 is not reaching the container, so run the WireGuard port checker, which writes the firewall rule for your host (ufw, firewalld, nftables, iptables) and a tcpdump test that shows whether the packets arrive. Handshake fine but pages hang or load half-way: an MTU problem, which the MTU calculator sizes for your link (set the result in the client config). Want only some traffic in the tunnel, or keep your home LAN reachable: the AllowedIPs calculator gives the exact list to paste into wg-easy's default allowed IPs.
Locking down the admin panel - do this, not later
The web UI can mint VPN clients, so an exposed, weakly-protected panel is the real danger with wg-easy. Three layers, in order of preference:
-
Never publish 51821 to the internet. In your VPS provider's firewall, leave UDP 51820 open and keep TCP 51821 closed to the world. Reach the panel only from your own IP, or - cleanest - connect to the VPN first and browse to the dashboard over the tunnel, so the admin port is never internet-facing at all.
-
Use a strong password, then turn on 2FA. v15 has TOTP two-factor login: open the menu at the top right, then Account, enable two-factor authentication and scan the QR code with your authenticator app. Treat the panel login like a server root password.
-
Put TLS in front of it if you must reach it remotely: a reverse proxy (Caddy, Traefik, nginx) terminating HTTPS on a real certificate, ideally with an allow-list. Plain HTTP on a public port is the one configuration to avoid.
Since v15.5.0, also set
TRUSTED_PROXIESto your proxy's source address (for a Caddy container on the same Docker network, its IP on that network). wg-easy then readsX-Forwarded-HostandX-Forwarded-Forfrom that address only, and failed logins are logged with the real client IP, so a tool such as CrowdSec bans the attacker rather than your proxy. A malformed address stops the container from starting: checkdocker compose logsafter the change.
A self-hosted VPN that any passer-by can add themselves to is worse than no VPN. The panel is the keys to the kingdom - protect it accordingly.
wg-easy vs the alternatives
- vs PiVPN - PiVPN installs WireGuard straight on the host and manages peers from the terminal (
pivpn add,pivpn -qr). No container, no web UI. Pick PiVPN for a lean, scriptable, bare-metal box; pick wg-easy when you genuinely want a clickable dashboard and live stats. - vs a hand-written config - Editing
wg0.confyourself is the most transparent and lightest option, ideal for a single-purpose VPN box and infrastructure-as-code. wg-easy trades a little extra surface (the web app) for fast, no-CLI client management. - vs other self-host stacks - If you want a mesh (every device talking directly, NAT traversal handled for you) rather than a classic hub-and-spoke server, wg-easy is the wrong shape; that is Tailscale/Headscale/NetBird territory, compared in our best self-host VPN guide.
wg-easy also runs happily alongside other containers, which is why it pairs naturally with a broader WireGuard-in-Docker setup.
When wg-easy is the right call - and when it isn't
Use wg-easy when you onboard several people or devices, want QR-code phone setup, like seeing live connection status, or simply prefer a panel to a config file. It turns "set up a VPN client" from a five-minute CLI chore into two clicks.
Skip wg-easy when the box does nothing but serve one or two WireGuard peers (a hand-written config is simpler and has one less moving part), when you can't safely keep the admin port off the public internet, or when you actually want a mesh rather than a server. For everyone else, wg-easy is the fastest honest path from a fresh VPS to a working, manageable WireGuard VPN.
A VPN changes your IP. It does not change your browser.
A tunnel replaces the address a service sees. It changes nothing about what your browser announces on its own - fonts, screen, time zone, graphics stack - and that combination is often stable enough to recognise you across sessions, tunnel or no tunnel.
Measure your own browser fingerprint - passive, no questions, no account, no email.
The honest limitation
wg-easy makes WireGuard easier to manage, not more private or more secure than WireGuard already is. It shares the host kernel, runs with NET_ADMIN, and is exactly as locked-down as the host and the admin panel you put in front of it. It does not audit your clients, does not stop DNS leaks on its own (set DNS in the Admin Panel and verify), and does not protect you if you leave 51821 open. Treat it as a convenience layer over real WireGuard: deploy it, harden the host and the panel, keep the data port public and the admin port private - and you have a self-hosted VPN you can actually run for a household without living in the terminal.
Affiliate link
★ Nuremberg GDPR datacenter · ✓ Dedicated IPv4 included · 200 Mbit/s port
Self-host your VPN on your own VPS → ContaboCloud VPS 4 · 5,50 €/mo · Card or PayPal · orders from outside the EU accepted→Price checked 17 Oct 2026, 24-month term


