Skip to content

Self-hosting the relay

Apex Relay forwards opaque, end-to-end encrypted payloads between your paired devices. It is content-blind by construction: it cannot read your vault or credentials, only route ciphertext. You can use the managed instance or run your own — both modes are content-blind, and self-hosting means you don’t have to trust anyone’s routing but your own.

Apex Relay is designed to run behind a TLS-terminating reverse proxy (NGINX, Caddy, Traefik, or a cloud load balancer) that forwards to the local process.

Internet clients
├─ HTTPS requests ─┐
└─ WSS connections ─┴─> TLS-terminating reverse proxy (public)
└─ HTTP/WS on a private network ─> Apex Relay (PORT=3000)

Set these environment variables in production:

  • NODE_ENV=production
  • CORS_ORIGINS — explicit HTTPS origins, comma-separated
  • REQUIRE_TLS_IN_PRODUCTION=true
  • TRUST_PROXY_HEADERS=true
  • STRICT_CORS_ORIGINS=true

If NODE_ENV=production and REQUIRE_TLS_IN_PRODUCTION=true, startup fails unless TRUST_PROXY_HEADERS=true — a guard against accidentally serving without TLS in front.

Configure your proxy to pass:

  • X-Forwarded-Proto: https
  • X-Forwarded-For: <client-ip>
  • X-Forwarded-Host: <public-hostname>
  • Host: <public-hostname>

For WebSocket upgrades, also forward:

  • Upgrade: websocket
  • Connection: upgrade

The image runs with:

  • NODE_ENV=production
  • PORT=3000
  • DATA_DIR=/data

It exposes port 3000 and reports liveness at GET /api/health.

In the Apex app, set the relay endpoint to your instance’s public HTTPS/WSS URL. Pairing and vault contents are unaffected — your vault still never leaves the phone; the relay only routes the encrypted messages between your devices.

Terminal window
curl -fsS https://relay.example.com/api/health

A healthy instance returns a success response. If it fails, check that your proxy terminates TLS, forwards the headers above, and that TRUST_PROXY_HEADERS=true is set.