A new mark: a padlock on a shield with the W as its keyhole, in the console's teal on a dark shield. It replaces the placeholder W tile in the sidebar, on the sign-in and setup pages, and as the favicon, with app icons and a web manifest. The SVG sources, a single-colour variant, a wordmark and a social preview image live in docs/brand. The console now has a theme choice: dark by default, light, or follow the system, switched from the sidebar or the Account page and remembered per browser. The light palette is a first-class theme rather than a media query, so both are laid out the same way. The README opens with the mark and gains a History section about the 2025 WGX installer this replaces.
WGX
A WireGuard server with a secure web console, in one container.
Start it, open the console, create a peer, scan the QR code. WGX runs the tunnel on the kernel's WireGuard module, keeps the NAT rules and forwarding sysctls in order, and gives you a dashboard that shows who is connected, how much they are moving, and a button to cut them off.
What it does
- Peers. Create, edit, disable, delete. The server generates the key
pair (and a preshared key) and shows a QR code and a
.confdownload; or the client brings its own public key and the private key never leaves the device. Pin a tunnel address or let WGX allocate one. Set an expiry and the peer is disconnected on time. Rotate keys in one click. - Who is connected. Live status from the interface counters every two seconds: endpoint, last handshake, session length, current rate, total transfer. Usage history in five-minute buckets, per peer and overall, kept for 90 days.
- Disconnect them. Disconnect removes the peer from the interface and keeps it off until you enable it again. Reset session drops the current session and lets the client handshake afresh.
- Fast. Kernel data plane over netlink, no user-space hop. Tuned
sysctls, TCP MSS clamping, optional host networking. Falls back to
wireguard-goon hosts without the module and tells you so. See docs/performance.md. - Locked down. argon2id passwords, two-factor authentication with recovery codes, viewer and administrator roles, rate-limited login, same-origin enforcement, strict CSP, built-in TLS if you want it, and an audit log of every change (including every time a peer's configuration is viewed). See SECURITY.md.
- Observable.
/api/healthfor a liveness probe and/metricsin Prometheus format, guarded by a bearer token. - Self-contained. One static Go binary, one SQLite file under
/data, no other services. Multi-arch image for amd64 and arm64.
Screenshots
The dashboard: who is connected, live throughput, traffic history and the server's details.
Peers, with live rates and totals. Connected peers sort to the top.
A peer: status, endpoint, handshake, keys and usage, with disconnect, session reset and key rotation a click away.
The same peer's configuration: scan the QR code with the WireGuard app, copy
the text, or download the .conf.
Settings: endpoint, client defaults, peer isolation and MSS clamping. Changes apply without a restart.
Quick start
curl -O https://raw.githubusercontent.com/Coffey-Labs/WGX/main/docker-compose.yml
# edit WGX_ENDPOINT (your public hostname or IP), then:
docker compose up -d
Open http://localhost:51821, create the first administrator, and add a peer. Point the WireGuard app on your phone at the QR code.
The console is bound to localhost in the compose file on purpose. To reach
it from elsewhere, either set WGX_TLS_SELF_SIGNED: "true" and bind to the
address you need, or put a TLS-terminating reverse proxy in front of it and
list the proxy in WGX_TRUSTED_PROXIES.
For the fastest configuration, docker-compose.host.yml runs on the host
network; docs/performance.md says when that is worth
it and which host sysctls to set.
Requirements
- Docker (or Podman) on a Linux host with a kernel from 5.6 on. Older kernels
work with
wireguard-dkmsinstalled on the host, or fall back to the slower user-space data plane automatically. - The container needs
NET_ADMINand the forwarding sysctls in the compose file.SYS_MODULEis not needed unless the host has never loaded the module and cannot autoload it. - UDP port 51820 (or whatever you choose) reachable from the internet.
Configuration
Infrastructure is configured through the environment; everything an administrator might change while the server runs lives in the database and is edited in the console under Settings (endpoint, DNS, default client routes, MTU, keepalive, peer isolation, MSS clamping, preshared keys).
| Variable | Default | Meaning |
|---|---|---|
WGX_ENDPOINT |
Public hostname or IP for client configs. Also asked for at first-run setup. | |
WGX_PORT |
51820 |
UDP listen port. |
WGX_SUBNET |
10.8.0.0/24 |
IPv4 tunnel network; the server takes the first address. |
WGX_SUBNET6 |
IPv6 tunnel network, e.g. fd42:42:42::/64. Off when empty. |
|
WGX_DNS |
1.1.1.1, 1.0.0.1 |
Resolvers handed to clients on first run. |
WGX_INTERFACE |
wg0 |
Interface name. |
WGX_EGRESS_INTERFACE |
auto | Interface to masquerade on. Auto uses the default route. |
WGX_HTTP_LISTEN |
:51821 |
Console listen address. |
WGX_TLS_SELF_SIGNED |
false |
Serve HTTPS with a certificate generated into /data. |
WGX_TLS_CERT, WGX_TLS_KEY |
Serve HTTPS with your own certificate. | |
WGX_SECURE_COOKIES |
false |
Mark cookies Secure when TLS terminates at a proxy. |
WGX_TRUSTED_PROXIES |
CIDRs whose X-Forwarded-For is believed. |
|
WGX_METRICS_TOKEN |
Bearer token for /metrics. A signed-in session works too. |
|
WGX_SESSION_IDLE |
12h |
Sign out after this much inactivity. |
WGX_SESSION_MAX |
168h |
Sign out after this long regardless. |
WGX_TRAFFIC_RETENTION |
2160h |
How long usage history is kept (90 days). |
WGX_POLL_INTERVAL |
2s |
How often the interface counters are read. |
WGX_BACKEND |
auto |
kernel, userspace or mock. Auto prefers the kernel. |
WGX_MANAGE_FIREWALL |
true |
Set to false if the host owns the NAT rules. |
WGX_MANAGE_SYSCTL |
true |
Set to false if the host has tuned itself. |
WGX_DATA_DIR |
/data |
Where the database and TLS files live. |
WGX_LOG_LEVEL, WGX_LOG_JSON |
info, false |
Logging. |
Locked out?
docker exec -it wgx wgx reset-password admin
sets a new password for that user, clears their second factor and ends their sessions. It runs against the same database, so no restart is needed.
Client setup
Any WireGuard client works: the official apps on iOS, Android, macOS and
Windows, wg-quick on Linux, and routers that speak WireGuard. Scan the QR
code from the peer's Configuration tab, or download the .conf. The
default configuration routes everything through the tunnel; change Client
routes on the peer (or the default under Settings) to the tunnel subnet
alone for split tunnelling.
API
Everything the console does goes through /api/… with the session cookie.
GET /api/peers, POST /api/peers, GET /api/peers/{id}/config,
POST /api/peers/{id}/disable and friends are stable enough to script
against; the shapes are in internal/server/api.go. A cross-site request
without a same-origin Sec-Fetch-Site or Origin header is refused, so
call it from the same origin or from a non-browser client.
Building from source
cd web && npm ci && npm run build && cd ..
go build ./cmd/wgx
The UI is embedded in the binary. docker build -t wgx . does both steps.
See CONTRIBUTING.md for the development loop against the
mock data plane, which needs no privileges.
History
This is a complete, ground-up rewrite of an earlier WGX, "WireGuard eXtended", which Coffey Labs published in October 2025 and later dropped. That one was an installer: a collection of Bash scripts behind a text-mode menu that set up and hardened a WireGuard stack on Debian 13 around a third-party web UI. The original announcement is still up. Nothing from it was carried over; this WGX is its own server and its own console, in one container, with the name kept because the intent is the same.
Licence
AGPL-3.0-or-later. See LICENSE.





