Skip to content

Troubleshooting

The commands below assume the repository-based setup, a root-level .env, and the supplied Compose file at docker/docker-compose.yml.

Set a shell variable once if you are running several checks:

COMPOSE="docker compose --env-file .env -f docker/docker-compose.yml"

Then either use the complete commands shown below or run $COMPOSE ps, $COMPOSE logs, and similar commands in the same shell.

Container does not start

Check Compose validation, state, and logs:

docker compose --env-file .env -f docker/docker-compose.yml config
docker compose --env-file .env -f docker/docker-compose.yml ps
docker compose --env-file .env -f docker/docker-compose.yml logs --tail 200 wirebuddy

Common causes are:

  • missing or shorter-than-required WIREBUDDY_SECRET_KEY in .env;
  • missing /dev/net/tun or WireGuard host support;
  • a process already listening on the GUI, WireGuard, or DNS port;
  • a read-only/unwritable docker/data/ directory; or
  • unsupported Docker/Compose versions.

The container intentionally uses host networking, /dev/net/tun, and a capability set of NET_ADMIN, NET_BIND_SERVICE, SETUID, SETGID, CHOWN, and DAC_OVERRIDE. Dropping any of the last five leaves the web UI running but breaks DNS — see DNS does not resolve or block.

Its health check probes /ready on the scheme and port the container actually started with; the container records it in /run/wirebuddy/listener.env. An unhealthy container therefore points to the application, not to a port or HTTPS mismatch: check docker compose logs and /ready's errors list.

Web UI or reverse proxy

Test WireBuddy directly on the host:

curl --fail --show-error http://127.0.0.1:8000/health
ss -ltnp | grep ':8000'

If direct access works but the proxy returns 502, verify the upstream address and that Only listen on Localhost matches the deployment. For authentication or CSRF problems behind a proxy, verify:

  • WIREBUDDY_TRUSTED_PROXIES identifies only the trusted proxy (never *);
  • WIREBUDDY_PUBLIC_ORIGIN matches the browser-visible origin; and
  • WIREBUDDY_CSRF_ALLOWED_ORIGINS contains any additional trusted origins.

Clear stale site cookies after changing origins. Session lifetimes are fixed at one hour idle and 24 hours absolute; there is no session-timeout UI setting.

Built-in HTTPS

These apply when Settings → General → Serve GUI over HTTPS is enabled. The setting is read at startup, so every change needs a restart.

Browser warns the certificate is not trusted

WireBuddy is serving the self-signed fallback, because no valid Let's Encrypt certificate exists for the configured Server FQDN. Check which one is active:

echo | openssl s_client -connect 127.0.0.1:8000 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

An issuer of O=WireBuddy is the self-signed fallback. Request a certificate in Settings → Let's Encrypt and restart. The Settings page also shows which certificate would be served.

Certificate was issued but the old one is still served

Certificates are read only at startup. Restart WireBuddy. There is no automated renewal job — see Let's Encrypt.

Let's Encrypt validation fails while HTTPS is on

HTTP-01 always validates over plain HTTP on port 80. WireBuddy runs a plaintext listener for that, but it must be reachable from the internet:

curl -sS -o /dev/null -w '%{http_code}\n' \
  http://DOMAIN/.well-known/acme-challenge/test

404 is the healthy answer for an unknown token — it proves the listener is reachable. A timeout or TLS error means port 80 does not reach it. If the log shows Could not bind plaintext ACME listener, the port is taken or the container lacks NET_BIND_SERVICE; HTTPS still runs, only validation is affected. Set gui_acme_http_port to 0 if another service owns port 80.

Login is rejected with "requires HTTPS"

Expected: with HTTPS enabled, logins and node enrollments over plain HTTP are refused so credentials are never sent in clear text. Reach the GUI over https://. Behind a reverse proxy that terminates TLS, leave this setting off and let the proxy handle TLS.

Node cannot enroll over HTTPS

A node rejecting the master's self-signed certificate must either trust it or enroll against a master using a Let's Encrypt certificate. The master log shows the rejected enrollment.

Peer cannot handshake

Verify the interface and endpoint:

sudo wg show
sudo ss -lunp | grep ':51820'

Check that:

  1. the interface is active under Settings → WireGuard;
  2. the client configuration contains the current server FQDN/IP and UDP port;
  3. all host, router, and cloud firewalls allow that UDP port;
  4. the client and server clocks are synchronized; and
  5. the client has the current keys and preshared key.

After changing endpoint, routing, DNS, or PSK settings, download or scan the client configuration again.

Tunnel connects but has no internet

Check forwarding and NAT on the host:

sysctl net.ipv4.ip_forward
sudo iptables -t nat -L POSTROUTING -n -v
sudo iptables -L FORWARD -n -v

net.ipv4.ip_forward must be 1. Newly created interfaces receive generated masquerade and forwarding hooks when no custom hooks are supplied. If custom hooks were configured, verify that their outbound interface and VPN subnet match the current host. Also confirm that the peer's routing mode includes the intended destinations.

DNS does not resolve or block

Check the DNS page and service state first. The Docker image includes Unbound; local source installations must install it separately.

docker compose --env-file .env -f docker/docker-compose.yml logs wirebuddy | grep -i unbound
dig @10.13.13.1 example.com

Verify the queried address is the selected interface's address, the peer has Use Ad-blocking DNS (WireBuddy) enabled, and at least one global blocklist is enabled under Settings → DNS. Select Update Blocklists after changing sources. Per-peer list selection and DNS logging are configured in the peer form.

For DNSSEC issues, verify that the image's /var/lib/unbound/root.key exists and that the upstream DNS-over-TLS hostnames and port 853 are reachable.

Permission denied: '/var/log/unbound/queries.log'

The container's capability set is incomplete. cap_drop: ALL also removes root's DAC_OVERRIDE, so normal file permissions apply to uid 0 — and /var/log/unbound is owned by unbound:unbound. Writing the config aborts, Unbound never starts, and the web UI comes up without DNS.

Related failures from the same cause, visible in the container log:

Log line Missing capability
PermissionError: ... '/var/log/unbound/queries.log' DAC_OVERRIDE
fatal error: unable to set group id of unbound SETUID, SETGID
Could not open logfile ... (Unbound runs, query log stays empty) CHOWN
can't bind socket on port 53 NET_BIND_SERVICE

Compare the running container against the shipped Compose file:

docker inspect wirebuddy --format '{{.HostConfig.CapAdd}}'

It must list all six capabilities. Recreate the container after correcting them; docker compose restart does not apply capability changes.

Port conflict: UDP <address>:53 is already in use

Unbound binds only the WireGuard gateway addresses, never 127.0.0.1, so systemd-resolved on 127.0.0.53 does not conflict with it. A conflict means another resolver holds the same address — or a wildcard bind on 0.0.0.0:53 that covers it. Identify the holder on the host:

ss -lntupn 'sport = :53'

Let ss filter by port rather than piping into grep -w ':53' — the -w flag never matches, because the character preceding :53 in an address such as 10.13.13.1:53 is a word character, so every match is rejected.

Host networking has no separate port namespace

With network_mode: host the container shares the host's network stack. Port 53 is the host's port 53; the EXPOSE 53/udp line in the image is documentation only and allocates nothing.

Traffic or GeoIP analytics are empty

WireGuard counters require an active interface and handshakes. Country and ASN destination analytics additionally require Settings → WireGuard → Country Traffic Analysis and conntrack byte accounting:

sudo sysctl -w net.netfilter.nf_conntrack_acct=1
cat /proc/sys/net/netfilter/nf_conntrack_acct
ls -lh docker/data/geolite2/

Sampling runs every 30 seconds, so new data is not instantaneous. Check GeoIP download/update messages if the databases are absent:

docker compose --env-file .env -f docker/docker-compose.yml logs wirebuddy | grep -i geoip

Retention is configured under Settings → Logs. There is no /api/metrics/* endpoint, metrics export, or configurable sampling interval in the current release.

Database checks and backups

The host database path for the supplied Compose file is docker/data/wirebuddy.db. Stop WireBuddy before running an offline SQLite integrity check so the result is unambiguous:

docker compose --env-file .env -f docker/docker-compose.yml stop wirebuddy
sqlite3 docker/data/wirebuddy.db 'PRAGMA integrity_check;'
docker compose --env-file .env -f docker/docker-compose.yml start wirebuddy

An ok result indicates that SQLite found no corruption. Restore configuration through Settings → Backup using a backup produced by the current backup format. Keep the same WIREBUDDY_SECRET_KEY; backup integrity verification is derived from it.

Warning

Do not delete, replace, or directly edit wirebuddy.db as a routine repair. Make a byte-for-byte offline copy before any expert-level recovery work.

Login, TOTP, and passkeys

TOTP depends on accurate time. Use one of the eight recovery codes if the authenticator is unavailable. An administrator can manage another account's password, TOTP requirement, and passkey requirement from Users.

The current release has no standalone app.utils.reset_password command and no supported unauthenticated admin-reset flow. If the only administrator is locked out, preserve docker/data/ and .env, then seek recovery help rather than running undocumented database updates.

Passkeys require a secure, stable browser origin: HTTPS for non-localhost hosts, with matching WebAuthn RP/origin settings when the automatic values are not suitable. Password login remains the fallback unless policy requires passkey setup.

Let’s Encrypt failures

HTTP-01 requires public DNS and inbound TCP/80. Ensure the reverse proxy forwards /.well-known/acme-challenge/ to WireBuddy. Use the staging checkbox while testing to avoid production rate limits.

Certificates are stored under docker/data/certs/. WireBuddy does not automatically renew them or configure/reload your reverse proxy; see Let’s Encrypt.

Collect information for a bug report

Include the WireBuddy version, Docker and Compose versions, host OS, browser (for UI issues), exact reproduction steps, and a focused log excerpt. Remove secrets, session cookies, passwords, private keys, preshared keys, enrollment tokens, and publicly sensitive addresses before posting logs.

Search or open an issue at the WireBuddy issue tracker.