wg-easy Setup: Secure the UI, Manage Peers, and Restore

wg-easy Setup: Secure the UI, Manage Peers, and Restore

Kevin Wu
October 11, 2026· 12 min read

wg-easy setup adds a management interface to WireGuard; it does not replace host security, routing, or a recovery plan. This guide keeps the UI behind an SSH tunnel, gives each device its own identity, and treats revocation as part of restoration.

Key Takeaways

  • Publish the tunnel port; keep the management port on the host loopback address.
  • A downloaded device configuration is a secret, including its QR representation.
  • Check fresh traffic after revocation and restart, rather than trusting a green indicator.
  • Restore backups behind a closed business ingress and replay later revocations first.

Is a wg-easy setup the right maintenance choice?

Use the self-hosting responsibility checklist to identify the owner of updates, credentials, firewall rules, and retirement. A WireGuard web UI helps you create devices consistently, but an administrator can also expose every device secret through an unsafe backup or export. Understand the public-key peer model before deciding who may administer it.

If you want a daily internet exit without maintaining a server, compare renting a VPS with using a managed VPN. If your actual task is managing Xray inbounds, the restricted 3x-ui workflow has different credentials and recovery data; it is not another WireGuard frontend.

Scope and evidence: Ubuntu 24.04 LTS, Linux Docker Engine with the Compose plugin, wg-easy v15.4.0, and an authorized separate WireGuard client. This is a source-based procedure, not an author end-to-end deployment or performance test. Screenshots and command outputs are deliberately not presented as observations. The pinned release is the baseline, not a claim to be the newest release.[1]

Step 1: Prepare the host and an independent way back

Choose a dedicated host with a public endpoint, or an authorized upstream UDP mapping. Confirm the CPU architecture is supported by the selected image and that the Linux kernel supports WireGuard. Docker Desktop on a workstation is outside this gateway procedure. Install Engine and Compose from Docker's official distribution instructions, then check their versions before proceeding.

Privately record existing interfaces, routes, DNS, firewall ownership, published Docker ports, and provider security-group rules. Test a new SSH connection and a provider recovery console. Do not change the route used for administration through the tunnel you are about to create.

Reserve UDP 51820 and host TCP 51821; stop if either is already occupied. Check the Compose bridge subnets against host, client, and LAN routes. Its default private IPv6 bridge is not evidence of a working public IPv6 exit. For this bounded IPv4 reader check, use a controlled client without a usable IPv6 default route; otherwise design and verify dual-stack coverage separately before proceeding. Do not disable IPv6 on a shared machine.

Step 2: Restrict the WireGuard web UI before startup

Download the selected version's Compose file, read it, and keep its volumes, capabilities, network, and forwarding settings together. Do not combine v14 environment variables with v15 configuration. The upstream file publishes both service ports, so it needs an intentional management-port change.[2]

sudo install -d -m 700 /etc/docker/containers/wg-easy
sudo curl --fail --show-error --location \
  https://raw.githubusercontent.com/wg-easy/wg-easy/v15.4.0/docker-compose.yml \
  -o /etc/docker/containers/wg-easy/compose.yaml
sudoedit /etc/docker/containers/wg-easy/compose.yaml

Inside the existing wg-easy service, replace the corresponding fields with this fragment; this is not a complete Compose file. Preserve the original indentation and the other required settings. Keep NET_ADMIN and the documented module handling; do not add privileged or mount the Docker socket.

image: ghcr.io/wg-easy/wg-easy:15.4.0
environment:
  - INSECURE=true
ports:
  - "51820:51820/udp"
  - "127.0.0.1:51821:51821/tcp"
restart: "no"

INSECURE=true permits HTTP on this restricted UI. It does not turn off WireGuard authentication, and it does not authorize public plaintext administration. Here the browser-to-server path is protected by SSH; never browse a public HTTP management address. A future public web entrance needs a separate authenticated HTTPS design.[4]

Keep the project directory root-only. The following commands pass its Compose file to sudo explicitly, so an ordinary sudo administrator does not need to enter that directory.[7]

Allow UDP 51820 through the provider firewall and the actual host/Docker policy owner. Do not allow public TCP 51821, including IPv6. Docker-published traffic can bypass the UFW rules people expect to protect ordinary host services, so a UFW deny alone is insufficient. Do not disable Docker's firewall programming or flush tables.[3]

sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml config --quiet
sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml pull
sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml up -d
sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml ps
sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml port wg-easy 51821

The management mapping should show 127.0.0.1:51821. If it shows a wildcard or another address, stop the container and correct the file before initialization. restart: "no" keeps automatic startup out of the initial acceptance sequence; enable a restart policy only after the checks below pass.

Step 3: Initialize through SSH and create one device

On your administrative workstation, replace the uppercase placeholders below. The trust file must contain a host key verified independently; a mismatch is a stop condition, not a reason to disable checking. Local port 51821 must also be free.

ssh -N -T -o ExitOnForwardFailure=yes -o StrictHostKeyChecking=yes \
  -o UserKnownHostsFile=/PATH/TO/VERIFIED_KNOWN_HOSTS \
  -L 127.0.0.1:51821:127.0.0.1:51821 ADMIN@SERVER_IP

Keep that terminal open and browse http://127.0.0.1:51821. Complete first-user setup with a unique administrator name and strong password. Choose the new-configuration route, enter the public tunnel endpoint and UDP 51820, and set the intended DNS and routes in the interface/client settings. Do not enter loopback as the peer endpoint. The setup guide distinguishes this new configuration from importing an older installation.[5]

Create one named device, such as an inventory label identifying its owner and purpose. Export its configuration privately to that device. Do not reuse one peer file for several people, paste it into chat, or publish a QR image. Record the public-key-to-device mapping separately from the private keys.

For an IPv4 internet exit, review the generated client's AllowedIPs for 0.0.0.0/0 and a reachable DNS resolver. A client route is not server-side access control; it also does not make the VPS reach arbitrary private LANs. Export only after confirming the endpoint, routes, address allocation, and DNS match the intended topology.

Step 4: Verify access, traffic, and rejection independently

Before connecting, record the client's ordinary exit privately. Connect the new peer and generate a fresh request. Use the visible exit-IP check in a browser actually using the WireGuard interface; the expected IPv4 address is the server's known egress, allowing for provider NAT.

sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml exec wg-easy wg show wg0
sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml restart wg-easy

Read handshake time and transfer counters after new traffic. A previous handshake is historical evidence, not proof the current request traversed the tunnel. Keep public keys and endpoint addresses out of shared logs. If authentication fails, compare the intended peer identity using the WireGuard key-mismatch diagnosis, without revealing either private key.

Reader checkExpected conditionIf it fails
External authorized host reaches the public UI addressNo management connection on TCP 51821Stop the container; inspect Docker bindings and both address families
Fresh browser through SSH, without a loginNo privileged panel operationsStop setup; investigate authentication and remove any anonymous access
Valid peer makes a fresh internet requestRecent handshake, increasing counters, intended exitSeparate endpoint/UDP reachability from forwarding, NAT, DNS, and routes
DNS and application UDP exerciseIntended resolver path and successful controlled UDP requestInvestigate those paths independently of a browser TCP success
Tunnel disconnectObserved fallback matches your explicit policyDo not claim a kill switch; add a separately verified failure policy if required

Use a DNS test under your control to inspect the actual resolver path. Test an authorized UDP application separately; HTTPS browsing is not its substitute. This guide supplies no client kill switch. A disconnected full-tunnel profile may allow the ordinary connection again, so do not use sensitive traffic to discover failure behavior.

Step 5: Revoke a device and make a wg-easy backup

Keep a second authorized device as a control. Disable or remove the first device in the panel, save the change, and confirm its public key is no longer an active accepted peer. Force fresh traffic from its retained old configuration: no new accepted handshake or forwarded request should succeed. Check the control device still works, restart the container, and repeat both checks.

Record the revoked public key and date in a private ledger outside the backup being replaced. Merely deleting a local client file cannot revoke the copy already on another device. Conversely, a panel screenshot alone does not prove the running interface or a later restart respects the change.

Take a consistent backup while the container is stopped. The following dated destination must be new; choose a new date or label for another snapshot. Verify each command before moving on. If the copy fails, retain the source volume and restore service deliberately rather than treating the backup as usable.

sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml stop wg-easy
sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml cp wg-easy:/etc/wireguard /root/wg-easy-backup-20261011
sudo chmod -R go-rwx /root/wg-easy-backup-20261011
sudo cp /etc/docker/containers/wg-easy/compose.yaml /root/wg-easy-compose-20261011.yaml
sudo chmod 600 /root/wg-easy-compose-20261011.yaml
sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml start wg-easy

The whole /etc/wireguard persistence directory matters, including database state and generated keys. Keep the Compose file, selected image reference, and firewall/recovery inventory with it. Encrypt an off-host copy, restrict backup access, and check that the archive can be read; neither copying files nor listing them proves restoration.

For a recovery exercise, close the tunnel ingress at the provider boundary first and use a dedicated replacement host or isolated instance. Recreate the same version, restore the stopped persistence directory with its original ownership and permissions, and keep the UI loopback-only. Do not merge files into a running database or overwrite the sole surviving backup.

Start while ingress remains closed, replay every revocation recorded after the snapshot, and verify the intended peer inventory. Then admit only the authorized test client long enough to repeat the valid/invalid device checks, restart, and repeat again. A revoked identity that returns after restoration is a failed recovery: close ingress, remove it from persistent and runtime state, and retest before reopening normal service.

Step 6: Recover management or retire the installation

If the SSH path works but the administrator password is lost, use the documented interactive reset on the authorized host. Avoid passing the new password in command arguments or recording the prompt.[6]

sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml exec -it wg-easy cli db:admin:reset

If the UI itself is broken, use the provider console to inspect only this Compose project's logs and configuration. Restore the last known configuration; do not expose the UI publicly as a workaround. If the management channel cannot be recovered safely, leave business ingress closed and stop changes.

To retire, close this deployment's UDP ingress, stop its container, and run sudo docker compose -f /etc/docker/containers/wg-easy/compose.yaml down without -v until backups and retention are resolved. Revoke exported identities, remove only its provider rules and reserved resources, and inspect for remaining listeners. Confirm a new SSH connection and any pre-existing services still work. Delete the data volume only after deliberate retention approval; stopping a container does not stop VPS billing.

Summary

  • Separate the admin route, tunnel route, device inventory, and revocation ledger.
  • Accept fresh traffic and negative checks before enabling automatic restart.
  • A recoverable backup preserves current denials as well as valid devices.

FAQ

Does the panel replace WireGuard keys?

No. Each device still needs a distinct WireGuard identity. The panel manages its configuration and makes exports easier, which increases the importance of protecting administrator access.

Can I expose the UI with a difficult password?

A strong password is useful but does not replace a restricted management entrance. This procedure permits HTTP only over the loopback-bound SSH path, with no public UI listener.

Why can UFW show deny while a container port is reachable?

Docker manages forwarding and published-port rules outside the path many ordinary host-service UFW policies cover. Inspect the actual Docker binding and the provider firewall instead of assuming a deny is sufficient.

Is a handshake enough to confirm an internet exit?

A handshake proves a peer exchange at a point in time. Verify new traffic, its visible egress, DNS, UDP, and disconnection separately before trusting the complete route.

Does restoring a backup restore revoked devices too?

It can if the snapshot predates their removal. Keep an independent revocation ledger and apply its later changes while ingress remains closed, then perform rejection checks again.

Can I use the same exported file on every device?

Use separate identities so one lost device can be revoked without replacing every other device's credentials. Shared files also make inventory and traffic attribution unreliable.

Was this deployment tested end to end by the author?

No end-to-end author test is claimed. The commands and expected conditions form a reader acceptance procedure based on the cited official sources; record your own authorized environment and results.

Sources:

  1. wg-easy v15.4.0 — https://github.com/wg-easy/wg-easy/releases/tag/v15.4.0
  2. wg-easy v15.4.0 Compose — https://github.com/wg-easy/wg-easy/blob/v15.4.0/docker-compose.yml
  3. Docker — Packet filtering and firewalls — https://docs.docker.com/engine/network/packet-filtering-firewalls/
  4. wg-easy — No Reverse Proxy — https://wg-easy.github.io/wg-easy/latest/examples/tutorials/reverse-proxyless/
  5. wg-easy v15.4.0 — Setup — https://github.com/wg-easy/wg-easy/blob/v15.4.0/docs/content/guides/setup.md
  6. wg-easy v15.4.0 — CLI — https://github.com/wg-easy/wg-easy/blob/v15.4.0/docs/content/guides/cli.md
  7. Docker — Compose CLI — https://docs.docker.com/reference/cli/docker/compose/

Sources checked 11 October 2026.


Related Articles:

Start your 3-day free trial

Sign up to experience all premium features at no cost.

*Available only to new users. Each user is limited to one trial.

wg-easy Setup: Secure the UI, Manage Peers, and Restore | AethoVPN