Nodus
Self-hosting

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 --relay URL 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.json indicates an interrupted data-dir move; the node completes it on boot.
  • Confirm SQLite can take its lock (busy_timeout is 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.

On this page