Wheelhouse docs

Troubleshooting#

You will end up knowing which of four things is wrong: the agent, the agent's connection to the router, the router's configuration, or the network. Work in that order. Almost every confusing symptom is the second one dressed up as the third.

The four-question triage#

Answer these in order. Each one tells you which section to jump to.

1. Does the agent answer?

bash
curl -sk https://127.0.0.1:8443/health

{"status":"ok","version":"…"} means the agent is alive. No answer means the agent is not running.

2. Can the agent reach the router?

bash
R=https://<router>:8443
T=wh_...
curl -sk -H "Authorization: Bearer $T" "$R/api/system"

A 502 with cannot reach the router means the API key or URL is wrong. Every page reporting the router unreachable while the login screen works fine is this, and it is the single most common confusing failure.

3. Is the configuration what you think?

bash
show configuration commands | match <the thing>

4. Is the kernel doing what the configuration says?

bash
show interfaces
show ip route
show firewall statistics

Steps 3 and 4 disagreeing is drift, and several pages flag it for you: Interfaces flags anything the configuration does not declare, Static routes flags a route declared but not installed, and QoS shows the attached policy beside the qdisc the kernel installed.

Symptom to cause#

The agent#

SymptomCause and fix
Nothing answers on 8443The agent is not running, the address is not what you think, or the firewall. Locked out of the UI.
The agent will not start; the journal says is mode 644; it must not be readable by group or otherA secret file is too permissive. sudo chmod 600 it — the agent refuses to read a loose secret. Applies to the API key, the break-glass token, the licence key, the OIDC secret and the fleet file.
The agent will not start; missing --api-url or --api-keyThe unit lost its flags, or /config/wheelhouse/api-key is gone. Restore from a backup.
The address in a document does not answerThe appliance is 8443; the developer install is 8090. They are different products on the same repository. The developer install.
The UI is blankThe agent was started without --ui-dir, or the assets are missing. After an agent upgrade, hard-reload once: index.html carries no cache policy yet, so a stale copy can ask for an asset hash that no longer exists.
A page looks broken right after an upgradeCached assets. One hard reload.
The browser warns about the certificateThe self-signed certificate is self-signed by definition. Trust it once, or install a real one.
SymptomCause and fix
Every page reports the router unreachable, but the login screen worksThe agent cannot authenticate to VyOS. The API key exists in two places — config.boot under service https api keys, and /config/wheelhouse/api-key — and both must match. This is what happens when a restore took one and not the other. Take a backup.
502 with cannot reach the routerThe same thing, or VyOS' own API is not listening. It is pinned to 127.0.0.1 on first boot, deliberately.
Pages are slow, and the router's load is highEvery call into the VyOS API forks a shell on the router. The agent caches and primes to keep that down; something calling the API in a loop undoes that. Check wheelhouse_vyos_calls_totalScrape the agent.

Authentication and authorisation#

SymptomCause and fix
401 on everythingNo principal: a missing or wrong token.
Every mutation answers 401, reads workA browser session that is not echoing X-Wheelhouse-CSRF. Use an API token for scripts — Issue a token.
Every write answers 402No usable licence. Administration → Licence, or --license-key-file. Reads keep working, deliberately.
403 on one routeYour role. Recorded in the audit log as denied <method> <path> with the actor. The three roles.
Forgotten the first-run passwordjournalctl -u wheelhouse-agent \| grep "shown once", or sudo cat /config/wheelhouse/initial-password.
Locked out entirelyLocked out of the UI.
Lost the second-factor deviceThere is no admin reset and no recovery codes. Console, agent stopped, state.json edited by hand. Locked out of the UI.

Committing#

SymptomCause and fix
The router rebooted after a commitA commit-confirm window expired unconfirmed. That is the feature working — the router is back on the previous configuration. Commit-confirm.
POST /api/stage answers 400An op that is not set or delete, a path with no elements, or an empty element in a path. The message names the index.
A rollback answers 502The archive file for that revision is missing. ls /config/archive, and check system config-management commit-revisions. Undo the last commit.
A commit fails on a firewall groupA rule references a group that the same commit deletes. Remove the references first. Use groups instead of literals.
The Commit Bar has operations nobody stagedThe reconcile loop in stage mode shares the working set with people. That is by design; the log says what it staged.
Committing nat static stops the router's APIKnown: on this platform build, committing nat static through the HTTP API crashes the API process. The CLI accepts the same lines, and the 1:1 NAT editor is switched off for this reason. 1:1 NAT, and the console.

Network#

SymptomCause and fix
A port forward does not work from outsideTwo rules are needed, not one. Compare the NAT rule's counter with the firewall accept's: NAT rising and firewall flat means the accept does not match. Publish a service.
A port forward works from outside, not from the LANNo hairpin, or the public address changed. Publish a service, or better, split-horizon DNS.
A new VLAN gets leases and reaches nothingThe installer's forward rule names the physical port, and eth0.30 is not eth0. Add a VLAN.
Everything works until a failover, then nothing doesThe second uplink has no source NAT rule of its own. Masquerade behind each uplink.
Failover never fails overAn uplink with no check is never withdrawn; the Uplinks page names any uplink in that state. A second uplink with health-checked failover.
A DHCP uplink's failover installs nothingdhcp-options no-default-route set on an uplink that has a dhcp-interface failover entry. The daemon flushes its routes and installs nothing, while the load balancer's tests on the same targets pass. Verified on the bench, 2026-09-02; the page flags it.
Two uplinks flapBoth WAN interfaces on the same layer-2 network. The failover daemon's per-interface probes interfere. Each uplink needs its own path.
A route is in the configuration and not in the tableThe next hop is not reachable on a connected interface, or something with a better distance won. The Static routes page flags exactly this.
A route is in the table and not in the configurationSomething else installed it: a routing protocol, a DHCP client, or the failover daemon.
IPv6 is configured and nothing shows itExpected. IPv6 is configurable and not observable — no route view, no uplink model. IPv6.
Large downloads stall over PPPoEMTU. PPPoE costs eight bytes. A PPPoE uplink.

VPN#

SymptomCause and fix
WireGuard never handshakesKeys, endpoint or the firewall. The router's peer entry holds the laptop's public key, not a private one. A WireGuard tunnel for a laptop.
WireGuard handshakes, no trafficallowed-ips, or the forward rule.
The WireGuard page shows no handshake timesIt cannot. Handshakes and transfer counters need wg show exposed by the agent, which it does not do. Use sudo wg show.
IPsec: no IKE at allThe firewall, or the peer address.
IPsec: IKE completes, no child SAMismatched ESP proposal or traffic selectors.
IPsec: up, no traffic passesTraffic selectors, source NAT applied to the tunnelled traffic, or the forward rule. An IPsec tunnel.
Tailscale is online, the LAN is unreachableThe subnet routes are not approved in the Tailscale admin console. Nothing on the router will tell you. Join a tailnet.

Services and apps#

SymptomCause and fix
An app install fails on the image pull with toomanyrequestsDocker Hub rate-limits anonymous pulls per source address. Retry, or pull the image by hand on the router. There is no registry mirror or credential setting.
A commit fails with Image "…" does not exist locallyThe pull step did not happen or did not finish. Install an app.
A commit fails with Volume "…" source path does not existThe directory step did not happen. Off-router, run the mkdir -p the agent handed you.
An app is declared and not runningIts own logs: the Logs tab, or sudo podman logs <name>.
A restart says the app is downThe second of the two commits failed. The message says down rather than restart failed because the difference matters. Update check and restart.
Restarting a stopped app answers 409There is nothing to bounce.
The health probe says not probed — this agent runs off-routerCorrect and honest: an off-router agent has no route to the container bridge, so a failed probe would say nothing about the app.
DNS stopped after installing a filterThe router's forwarder and the container both want port 53. Give the container its own address and forward to it. AdGuard Home.
A wildcard DNS record is refusedVyOS' forwarder answers names, not wildcards. dnsmasq.
printer.lan does not resolveThe forwarder does not register DHCP leases. A reservation plus a static host mapping, or dnsmasq.
Conntrack byte counters are missingnf_conntrack_acct is not enabled. Kernel tunables.

Installing and upgrading#

SymptomWhere to look
The install stopped with a message/var/log/wheelhouse-install.log, which the installer names in its own error
The installer never appeared, just a login promptA serial console, or the machine booted an installed system rather than the medium. wheelhouse-install refuses to run on an installed system and says so.
The banner says the agent is not activejournalctl -u wheelhouse-agent
No Web UI: line on the bannerThe machine has no address. ip -4 addr from the console.
A seed did not install anythingAn unattended install runs at most once per boot, and refuses a disk that already holds a system unless the seed says overwrite: true. Build a seed.
A seed URL was refusedPlain http:// and ftp:// are refused for a seed on the kernel command line. Use https://, or wheelhouse.insecure=1, or pin it with wheelhouse.autoinstall.sha256=<hex>.
An air-gapped router cannot be upgradedPOST /api/system/image accepts http(s) URLs only. Use add system image /path/to.iso at the console.
The agent went backwards after an image upgradeA .deb installs into /usr, which an image upgrade replaces. Upgrade the agent only.

Where the evidence is#

QuestionRead
What is the router's software saying?The journal — Logs, or journalctl
What changed, and when?The commit history — System — history
Who changed it?The audit log — System — audit
What is the agent itself doing?journalctl -u wheelhouse-agent, and /metrics
What is on the wire?Not from the UI: POST /api/capture answers 501 and hands back the monitor traffic command to run at the console.

What to collect before asking for help#

There is no support-bundle command. Four calls are the procedure:

bash
R=https://<router>:8443
T=wh_...                                    # a viewer token
curl -sk  "$R/health"
curl -sk -H "Authorization: Bearer $T" "$R/api/audit?limit=300"
curl -sk -H "Authorization: Bearer $T" "$R/api/history"
journalctl -u wheelhouse-agent -n 500

Where to send it: <TODO: owner>.

See also#


Checked against docs/deploy.md · docs/install.md · agent/main.go · agent/apps.go · docs/multi-wan.md · SUPPORT.md

Updated 2026-09-02 troubleshooting diagnostics