Self-host

Run your own relay.

The SecureChat relay is a single Fastify container. It stores sealed, signed packets for at most 24 hours, has no access to plaintext, and is small enough to run on a $5/month VPS. About 20 minutes if DNS and the firewall are ready.

Before you start

Production checklist. Do not skip these or you will be running an insecure relay that leaks metadata and is trivial to spoof.
  • A VPS reachable on TCP 443 and TCP 80 (for ACME HTTP-01).
  • A DNS A record pointing at the VPS (e.g. relay.example.com).
  • Docker + Docker Compose installed.
  • A reverse proxy in front of the container (Caddy is the recommended choice — see caddyserver.com).
  • Two random tokens, generated with openssl rand -base64 48 — one for RELAY_AUTH_TOKEN and one for RELAY_ADMIN_TOKEN.
  • An OPS_TOKEN for the /healthz/internal endpoint — also openssl rand -base64 48.

1. Clone the repo

git clone https://github.com/bigbadboy1010/SecureChat.git
cd SecureChat/RelayServer

2. Configure the environment

Copy the example file and edit it:

cp .env.example .env
$EDITOR .env

The minimum required values are:

NODE_ENV=production
HOST=0.0.0.0
PORT=8080

STORE_TYPE=file
DATA_DIR=/data

RELAY_AUTH_TOKEN=<output of openssl rand -base64 48>
RELAY_ADMIN_TOKEN=<output of openssl rand -base64 48>
MIN_AUTH_TOKEN_LENGTH=32

REQUIRE_AUTH_IN_PRODUCTION=true
REQUIRE_HTTPS_IN_PRODUCTION=true
TRUST_PROXY_HEADERS=true
ENABLE_CLIENT_PURGE=false
SECURITY_AUDIT_LOG=true

OPS_TOKEN=<output of openssl rand -base64 48>

MAX_PACKET_BYTES=131072
MAX_TTL_SECONDS=86400
MAX_CLOCK_SKEW_SECONDS=300
MAX_TOTAL_PACKETS=10000
MAX_PACKETS_PER_RECIPIENT=500
RATE_LIMIT_MAX=120
RATE_LIMIT_WINDOW=1 minute
Don't leave RELAY_AUTH_TOKEN empty. The relay refuses to start in production with the default placeholder. Tokens must be at least MIN_AUTH_TOKEN_LENGTH characters.

3. Build and start

docker compose build
docker compose up -d

Watch the logs:

docker compose logs -f relay

You should see Server listening at http://0.0.0.0:8080 within a few seconds.

4. Reverse proxy with Caddy

Put Caddy in front to terminate TLS and forward to the relay:

relay.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:8080
}

Reload Caddy:

sudo systemctl reload caddy

5. Verify

Public healthcheck (no auth):

curl -s https://relay.example.com/healthz
# {"status":"ok","uptimeSeconds":...,"version":"v0.1.0+<git-sha>"}

Operator healthcheck (requires OPS_TOKEN):

curl -s https://relay.example.com/healthz/internal \
     -H "X-Securechat-Ops-Token: $OPS_TOKEN"
# {"status":"ok","uptimeSeconds":...,"version":"...","peers":0,"packetCount":0,"nodeEnv":"production"}

Public security policy:

curl -s https://relay.example.com/v1/relay/security/policy

6. Point the iOS app at it

In the iOS app, open Settings → Relay, set Custom relay URL to https://relay.example.com, paste the RELAY_AUTH_TOKEN value, and tap Test. The app will run a handshake against the relay; if it returns a 200, you are good to go.

Upgrades

Pull the latest source, rebuild, and restart:

git pull
docker compose build
docker compose up -d

The relay does not have a database; the file store lives in the /data volume and survives restarts.

What you are agreeing to

By running the relay you are committing to keeping it on a supported version, rotating RELAY_AUTH_TOKEN at least annually, and applying security advisories within 30 days of release. The relay's threat model assumes a network-only adversary; if your VPS is compromised, the attacker can still disrupt the relay (denial of service, packet injection up to the rate limit, dropping the volume) but cannot read message bodies.