Kituwa IT · Self-hosting guides

Install WireGuard (wg-easy) with Docker Compose

Self-hosted VPN for remote access

VPNremote accessprivacy

WireGuard is a modern VPN that's fast, small, and has no configuration ceremony. wg-easy wraps it in a web UI so you can hand out per-device configs without touching the kernel. This is how you reach 192.168.1.x services from outside the house without opening a port per app.

Category
Network & proxy
License
MIT (wg-easy)
Needs
/dev/net/tun + NET_ADMIN
Image
wg-easy/wg-easy

What it is

A virtual private network that tunnels your devices into your own network. Unlike port forwarding a dozen applications, one VPN connection makes every LAN service — including ones with no authentication at all — safely reachable from anywhere.

Before you start

  • The container needs /dev/net/tun and the NET_ADMIN capability. Without them it starts and then every connection silently fails, which is the classic symptom of a missing TUN device.
  • Forward UDP 51820 on your router to the host. If you're behind CGNAT you won't get an inbound connection no matter what you configure.
  • If you're already running Tailscale on the same host, you probably don't need this. Tailscale does the same job with less configuration and no port forwarding.

1 — Check the TUN device exists

Command / configuration
ls -l /dev/net/tun
# crw-rw-rw- 1 root root 10, 200 ... /dev/net/tun

If that file is missing, load the module or reboot. A container can't create a device that isn't there.

2 — Write the compose file

Command / configuration
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:latest
    container_name: wg-easy
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    sysctls:
      net.ipv4.ip_forward: 1
      net.ipv4.conf.all.src_valid_mark: 1
    environment:
      LANG: en
      WG_HOST: vpn.example.com
      WG_PORT: 51820
      WG_DEFAULT_DNS: 192.168.1.1,1.1.1.1
      WG_ALLOWED_IPS: 0.0.0.0/0, ::/0
      WG_PERSISTENT_KEEPALIVE: 25
      # let the UI manage wg0.conf itself
      WG_QUICK_USER: node
      WG_QUICK_USER_PASSWORD: CHANGE_ME
    volumes:
      - ./config:/etc/wireguard
    ports:
      - "51820:51820/udp"
      - "127.0.0.1:51821:51821/tcp"
    cap_drop:
      - ALL
    cap_add:
      - NET_ADMIN
      - SYS_CHROOT
      - NET_RAW
WG_HOST must be the address clients can reachIf it's set to localhost or a LAN IP, the QR codes it generates will be useless from a phone on mobile data. It needs your public DNS name or public IP.

3 — Start it

Command / configuration
mkdir -p ~/services/wg-easy/config
cd ~/services/wg-easy
docker compose up -d
docker exec wg-easy wg show    # should list the interface

4 — First-run setup

  1. Open the UI at http://127.0.0.1:51821 — only from the LAN, since that port isn't meant to be public.
  2. Set WG_HOST and the admin password if you didn't via env, then click Finish.
  3. Create a client. Scan the QR code with the WireGuard mobile app, or download the .conf for desktop.
  4. Test from outside the network. Inside your own LAN the tunnel will appear to work even when it's misconfigured, so a phone on mobile data is the honest test.
  5. Once clients work, firewall inbound traffic to your LAN services from the wg0 interface only, and drop everything else.

5 — Manage configs

Command / configuration
# list clients and their endpoints
docker exec wg-easy ls -la /etc/wireguard

# server stats
docker exec wg-easy wg show wg0 transfer

# config is plain text on disk - back it up
tar -czf wg-config.tgz ~/services/wg-easy/config

Help when you need it

Want help getting this running?

We can help with a supported Linux host, application setup, migration or troubleshooting. Contact us to confirm the software, scope and scheduling before ordering.

Related guides

← Browse all 30 guides · Back to top