Set up a WireGuard VPN with wg-easy in Docker
Run a WireGuard server with a web admin page on one Linux host, open the UDP port, add a client from the browser or a QR code, and verify that your traffic goes through the tunnel.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
What you will build
A WireGuard VPN server running in a single Docker container, managed through the wg-easy web interface. By the end, your laptop or phone connects to the server over UDP port 51820 and all of its internet traffic, or just traffic to your home or office network, flows through the encrypted tunnel. WireGuard itself is part of the Linux kernel, so the container only needs to configure it.
Before you start
- A Linux server (Debian 12, Ubuntu 22.04 or newer, or a RHEL-family system) with a public IP address or a DNS name pointing at it. Behind a home router you must forward UDP 51820 to the server.
- Docker Engine with the Compose plugin installed from the official repository (
docker compose versionworks). - A user with sudo and an SSH session to the server.
- About 20 minutes.
Step 1: Download the official compose file
The project publishes a ready-made compose file. Keep it in its own directory:
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
sudo mkdir -p /etc/docker/containers/wg-easy
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml
cd /etc/docker/containers/wg-easy
cat docker-compose.yml
The interesting parts of that file:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
51820/udpis WireGuard itself;51821/tcpis the admin web page.NET_ADMINandSYS_MODULElet the container create thewg0interface and load the kernel module.ip_forward=1turns the server into a router for the VPN clients.- The
etc_wireguardvolume holds keys, client configs and the admin settings. Back it up if you want to move the server later.
Step 2: Decide how you will reach the admin page
wg-easy version 15 refuses to serve its admin page over plain HTTP unless you say so. The project recommends a reverse proxy with TLS (its documentation has Caddy and Traefik examples). For a first setup where you reach the page only from your own network or over SSH, allow plain HTTP by adding an environment section to the service:
services:
wg-easy:
environment:
- INSECURE=true
Then do not open 51821 in the firewall. Instead reach it through an SSH tunnel from your laptop: ssh -L 51821:127.0.0.1:51821 user@server and open http://localhost:51821. If you prefer not to rely on that, change the port line to "127.0.0.1:51821:51821/tcp" so Docker never exposes it publicly at all.
Step 3: Open the VPN port in the firewall
Careful: only add rules here; do not enable a firewall for the first time without allowing SSH, and keep your current session open while you test from a second one.
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
# Debian / Ubuntu with ufw
sudo ufw allow 51820/udp
sudo ufw status
# RHEL / Rocky / Alma with firewalld
sudo firewall-cmd --permanent --add-port=51820/udp
sudo firewall-cmd --reload
On a cloud server, also allow UDP 51820 in the provider’s security group. At home, forward UDP 51820 on the router to the server’s LAN address.
Step 4: Start the container
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -f # Ctrl-C to stop following
Open the admin page (through the SSH tunnel: http://localhost:51821). The first visit runs a short setup wizard: create the admin account, and enter the host clients should connect to (your public IP or DNS name) and the port 51820. Everything you enter here lands in the client configuration files, so a wrong hostname means clients cannot connect.
Step 5: Add a client
In the web interface click the button to create a client, give it a name such as laptop, and save. You get three ways to install it:
- A QR code: in the WireGuard app on iOS or Android choose “Create from QR code” and scan it.
- A .conf download: import it into the WireGuard app on Windows or macOS, or copy it to
/etc/wireguard/wg0.confon a Linux client and runsudo wg-quick up wg0. - The configuration text, if you want to edit it first.
By default the client config routes everything (AllowedIPs = 0.0.0.0/0, ::/0) through the VPN. If you only want to reach the server’s network, change the client’s allowed IPs in the web interface to that network, for example 10.8.0.0/24, 192.168.1.0/24, before downloading.
Check that it works
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
# on the server
sudo docker exec wg-easy wg show
# on a connected Linux client
sudo wg show
curl -4 https://ifconfig.me # should print the server's public address when all traffic is routed
ping 10.8.0.1 # the server end of the tunnel
wg show on the server lists each peer with a latest handshake time and transfer counters. A peer with no handshake means the client’s packets never arrived: wrong host or port in the client config, UDP 51820 not open, or a missing router forward. Transfer counters that grow only in one direction usually mean the server’s forwarding or NAT is not working; docker compose logs shows the iptables commands it ran.
Clean up
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
cd /etc/docker/containers/wg-easy
sudo docker compose down # stop; keys and clients stay in the volume
sudo docker compose down -v # also delete the volume with all keys and clients
sudo ufw delete allow 51820/udp # or firewall-cmd --permanent --remove-port=51820/udp && firewall-cmd --reload
Next steps
- Put the admin page behind Caddy or Traefik with a real certificate, following the wg-easy documentation, and remove
INSECURE=true. - Update with
sudo docker compose pull && sudo docker compose up -dfrom the compose directory. - Run Pi-hole on the same host and set it as the DNS server for VPN clients so phones get ad blocking on the road.
- If you later want to understand the moving parts, the same tunnel can be built by hand with
wg genkey, awg0.confandwg-quick; the WireGuard quick start linked below walks through it.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0103 Proxmox VE networking: Linux bridges (vmbr0) and VLANs Guests reach the network through Linux bridges such as vmbr0, defined in /etc/network/interfaces. Learn the default setup, VLAN-aware bridges, VLAN tags for… Intermediate · Step by step · Linux Article Installation & first steps · Networking & firewalls 15 Oct 2026
- 0167 Fix: Job for nginx.service failed because the control process exited with error code systemctl prints this when a service's start command exits with an error. The real reason is in journalctl; for nginx it is… Intermediate · Fixes an error · Linux Article Networking & firewalls · Services & systemd 15 Oct 2026
- 0173 Set a static IP address: nmcli on RHEL vs netplan on Ubuntu RHEL, Rocky and AlmaLinux configure networking with NetworkManager (nmcli); Ubuntu Server uses netplan YAML. Here is the same static IP setup in… Intermediate · Step by step · Linux Article Networking & firewalls 15 Oct 2026
- 0174 Network and DNS troubleshooting commands: ip, ss, dig, resolvectl A layer-by-layer cheat sheet for "the server cannot reach X": interfaces and routes with ip, listening ports with ss, and name resolution… Beginner · Cheat sheet · Linux Article Networking & firewalls 15 Oct 2026
- 0175 Open ports with firewalld and ufw firewalld (RHEL, Rocky, AlmaLinux) and ufw (Ubuntu) are friendly front ends to the kernel firewall. Allow SSH first, then open only the… Intermediate · Step by step · Linux Article Networking & firewalls 15 Oct 2026
- 0249 Set up and harden a fresh Linux server in one afternoon Take a new Ubuntu, Debian or Rocky/Alma server from first login to a safer baseline: sudo admin, SSH keys only, firewall, automatic… Beginner · Guided project · Linux Article Networking & firewalls · Packages & updates · Users, sudo & hardening 15 Oct 2026
Repos in the wild
- firewalld: zone-based firewall daemon firewalld/firewalld Upstream source of firewalld, the default firewall manager on RHEL, Rocky Linux, AlmaLinux and Fedora, including firewall-cmd and its service definitions. GitHub
- wg-easy: WireGuard VPN with a web admin interface wg-easy/wg-easy A Docker image that runs a WireGuard server and a web page to create clients, show QR codes and watch traffic. The quickest way to get a personal VPN running on one Linux host. GitHub
- Pi-hole: network-wide ad blocking with your own DNS server pi-hole/pi-hole A DNS server for your home or office network that answers queries for known advertising and tracking domains with nothing, so every device on the network is protected without installing anything on it. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +6,254 chars