Skip to content
Commitpedia
Log in Join & edit

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?

Edit history of Set up a WireGuard VPN with wg-easy in Docker: 1 recorded edit
Edit history · 1 edit
Type
Article
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Reading
5 min

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 version works).
  • 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/udp is WireGuard itself; 51821/tcp is the admin web page.
  • NET_ADMIN and SYS_MODULE let the container create the wg0 interface and load the kernel module.
  • ip_forward=1 turns the server into a router for the VPN clients.
  • The etc_wireguard volume 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.conf on a Linux client and run sudo 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 -d from 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, a wg0.conf and wg-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

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Dominik +6,254 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?