Troubleshooting
Common issues with routing, WebSockets, LAN discovery, and TLS.
The browser cannot connect to the Relay (/ws rejected)
ALLOWED_ORIGINS must exactly match the browser origin (scheme + host + port),
e.g. https://nodus.example.com. The browser opens
NEXT_PUBLIC_RELAY_URL (wss://relay.example.com/ws), so also confirm
SESSION_COOKIE_DOMAIN is the shared parent domain (.example.com); otherwise
the Relay receives no session cookie and closes with 4001.
In local HTTP runs the defaults allow both http://localhost and
http://127.0.0.1 and leave NEXT_PUBLIC_RELAY_URL pointing at the same
origin.
Auth cookies not sticking locally
For plain-HTTP local development, set SESSION_COOKIE_SECURE=false on the
Relay. The web client only marks cookies Secure in production.
Mobile or CLI cannot reach the Relay
Non-browser clients call the Relay origin directly. Ensure PUBLIC_RELAY_URL
and the device's configured server URL are both https://relay.example.com
(the Relay subdomain, not the web host), and that RELAY_HOST resolves to the
VM with a valid certificate.
"Local transfer unavailable" on mobile
Path A needs the local-network permission, mDNS to find _nodus._tcp, and
WebRTC — all foreground-only (ADR-0004). If denied or unavailable, the app falls
back to Relay paths. Grant local-network permission, or use a manual host probe.
Mixed content blocks direct P2P in the browser
Serving the web app over HTTPS while a node is only reachable over cleartext HTTP prevents direct P2P. The UI shows a banner and falls back to the Relay buffer path. Use TLS for nodes, or access the app over HTTP on a trusted LAN.
Pairing code rejected
- Codes are one-time and expire — mint a fresh one.
- Pairing to a non-loopback host requires HTTPS/WSS; plain HTTP is refused.
- Verify the node's
--relayURL matches the account's Relay.
Storage Node won't start
- Check the data directory is writable and
~/.nodus/identity/exists or can be created. - A stale
~/.nodus/migration.jsonindicates an interrupted data-dir move; the node completes it on boot. - Confirm SQLite can take its lock (
busy_timeoutis 5s).
Factory reset refuses to run
Another client is still connected to the database. Close it, or pass
--factory-reset-force if the only connection is yours. A rejected
BUFFER_DIR is deliberate — pick a dedicated directory such as
/var/lib/nodus/buffer.