WireGuard server with an embedded admin console

Go backend that drives kernel WireGuard over netlink (wireguard-go as the
fallback), nftables NAT with MSS clamping, forwarding and buffer sysctls,
SQLite for peers, users, sessions, traffic history and the audit log.

React console: dashboard with live rates and usage history, peer management
with QR codes and .conf downloads, disconnect, session reset, key rotation,
expiry, client-supplied keys, settings, users with admin and viewer roles,
two-factor authentication with recovery codes, audit log.

Docker image on Alpine with compose files for bridged and host networking,
CI and GHCR publish workflows, performance notes.
This commit is contained in:
jcoffey
2026-09-12 19:56:08 -07:00
commit 6c006e1d4d
72 changed files with 11675 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
# WGX: a WireGuard server with a web admin UI, in one container.
#
# Start it, open http://<host>:51821, create the first administrator, add a
# peer, scan the QR code. The container needs NET_ADMIN to create the tunnel
# interface and its NAT rules, and the sysctls below to forward packets.
#
# For the highest throughput see docs/performance.md: it explains when to use
# docker-compose.host.yml (host networking) and which host sysctls matter.
services:
wgx:
image: ghcr.io/coffey-labs/wgx:latest
container_name: wgx
restart: unless-stopped
cap_add:
- NET_ADMIN
# Only needed if the host has not loaded the wireguard module yet and
# you want the container to load it. Usually unnecessary on any kernel
# from 5.6 on: the module loads itself when the interface is created.
# - SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
# Loose reverse-path filtering; strict drops replies arriving on the
# tunnel. WGX would set this itself but /proc/sys is read-only in a
# container, so it has to come from here.
- net.ipv4.conf.all.rp_filter=2
- net.ipv4.conf.default.rp_filter=2
# Uncomment with WGX_SUBNET6 for IPv6 inside the tunnel.
# - net.ipv6.conf.all.forwarding=1
# - net.ipv6.conf.all.disable_ipv6=0
environment:
# The public hostname or IP clients connect to. Asked for at setup too.
WGX_ENDPOINT: vpn.example.com
# UDP port WireGuard listens on; must match the port mapping.
WGX_PORT: "51820"
# Tunnel network. The server takes the first address.
WGX_SUBNET: 10.8.0.0/24
# WGX_SUBNET6: fd42:42:42::/64
# DNS handed to clients by default.
WGX_DNS: 1.1.1.1, 1.0.0.1
# Admin UI. Put a TLS-terminating proxy in front of it, or enable the
# built-in self-signed certificate, before exposing it anywhere but
# localhost or your LAN.
WGX_HTTP_LISTEN: ":51821"
# WGX_TLS_SELF_SIGNED: "true"
# WGX_TRUSTED_PROXIES: 172.16.0.0/12
# WGX_METRICS_TOKEN: change-me
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
volumes:
- wgx-data:/data
volumes:
wgx-data: