VPNSmith
self-host-vpnINFO

wg-easy (2026): the web-UI way to self-host WireGuard in minutes

wg-easy gives WireGuard a browser dashboard: create clients, scan QR codes, watch live traffic. The full 2026 setup — docker-compose, the v14 password change, locking down the admin panel, and when to pick it over the CLI.

By Eric Gerard · Founder · VPNSmith — Self-host VPN & GDPR VPS specialist7 min readPhoto via Pixabay

Affiliate disclosure — This guide links to Contabo, the VPS we run our own self-hosted WireGuard on. If you order through our link we earn a commission at no extra cost to you. Every command below is documented from the official wg-easy project and written to be reproducible on your own machine.

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 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.

A laptop on a desk showing lines of source code in a dark editor, with a smartphone resting beside the keyboard
A laptop on a desk showing lines of source code in a dark editor, with a smartphone resting beside the keyboard

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. We run ours on Contabo for the 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.

Get a Contabo VPS to run wg-easy →

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

This is the modern wg-easy deployment. Replace WG_HOST with your server's public IP or hostname, and set a strong admin password (the next section explains the v14+ hashing requirement):

services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:latest
    container_name: wg-easy
    environment:
      - WG_HOST=your.server.public.ip
      - PASSWORD_HASH=$2a$12$REPLACE_WITH_YOUR_BCRYPT_HASH
      - WG_DEFAULT_DNS=1.1.1.1
    volumes:
      - ./etc_wireguard:/etc/wireguard
    ports:
      - "51820:51820/udp"   # WireGuard data — open to the internet
      - "51821:51821/tcp"   # web UI — firewall this to YOUR IP only
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
    restart: unless-stopped

Three things are doing the real work and are the usual failure points:

  • cap_add: NET_ADMIN lets the container manage the WireGuard interface. Without it the container starts but can't bring the tunnel up.
  • sysctls: net.ipv4.ip_forward=1 turns the box into a router. Without it the tunnel connects and then routes nothing — the classic "connected but no internet" symptom.
  • volumes persists keys and clients on the host, so docker compose down && up doesn't wipe every device.

Bring it up with docker compose up -d, then open http://YOUR_SERVER_IP:51821 and log in.

The password change that breaks upgrades

This is the single biggest gotcha in 2026. Older wg-easy used a plain PASSWORD environment variable. Newer releases (v14 and later) removed plain PASSWORD and require a bcrypt hash in PASSWORD_HASH instead. If you copy an old tutorial's PASSWORD=secret line into a current image, the container will refuse the login or fail to start, and you will swear the docs are wrong.

Generate the hash with the image itself so you never paste a plaintext password into a shell history:

docker run --rm ghcr.io/wg-easy/wg-easy wgpw 'your-strong-password'
# prints: PASSWORD_HASH=$2a$12$....

Copy the whole $2a$12$... value into the compose file. Note that the $ characters need escaping (double them as $$) if you put the hash in a .env file that Compose interpolates — that escaping bug is the second-most-common upgrade complaint after the variable rename itself.

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.

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:

  1. 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.
  2. Use a strong, hashed password. The PASSWORD_HASH requirement exists for this reason. Treat it like a server root password.
  3. 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.

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 PiVPNPiVPN 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.conf yourself 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.

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 WG_DEFAULT_DNS 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.

★ Nuremberg GDPR datacenter · ✓ Dedicated IPv4 included · 200+ Mbps guaranteed

Self-host your VPN on your own VPS → ContaboFull root access · public IPv4 · pick your region

Frequently asked questions

What is wg-easy?
wg-easy is an open-source project that wraps WireGuard in a small web interface. You run it as a single Docker container; it manages the WireGuard server for you and gives you a browser dashboard to add and remove clients, download or QR-scan their configs, and watch live upload/download per peer. The encryption is still plain WireGuard running in the host kernel — wg-easy only handles peer management and the UI, so there is no performance penalty versus a hand-written config.
Is wg-easy safe to expose to the internet?
Only the WireGuard data port (UDP 51820 by default) should face the internet. The admin web port (51821) must NOT be open to the world — anyone who reaches it and guesses or brute-forces the password can mint themselves a VPN client. Lock the web port to your own IP with a firewall rule, put it behind a reverse proxy with TLS, or reach it over the tunnel itself. Recent wg-easy versions require a hashed admin password (PASSWORD_HASH) precisely because an unprotected panel is the main risk.
What is the difference between wg-easy and PiVPN?
Both make WireGuard easy, but differently. PiVPN is an installer script that sets up WireGuard (or OpenVPN) directly on the host and gives you CLI commands (pivpn add, pivpn -qr) to manage peers — no container, no web UI. wg-easy runs as a Docker container and its whole point is the browser dashboard. Choose PiVPN if you like a bare-metal, terminal-driven setup on a Raspberry Pi or VPS; choose wg-easy if you want a clickable panel and live stats.
Do I still need a VPS for wg-easy?
If you want a VPN you can connect to from anywhere with a stable public IP, yes — wg-easy still needs a host with a routable address, exactly like any WireGuard server. A small VPS (4–6 €/month) is the standard choice and saturates its uplink with WireGuard easily. For remote access to a home network only, you can run wg-easy on a home server behind router port-forwarding instead, but you lose the clean static IP.
Why does wg-easy connect but route no traffic?
Almost always the host firewall or IP forwarding. The container needs the NET_ADMIN capability and the host must forward packets (net.ipv4.ip_forward=1) — wg-easy sets these in its compose file, but a cloud provider's separate security group can still block UDP 51820. Open that port in the provider's firewall, not just on the host. If the handshake completes but pages still hang, the next suspect is MTU rather than wg-easy itself.