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
- 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 forRELAY_AUTH_TOKENand one forRELAY_ADMIN_TOKEN. - An
OPS_TOKENfor the/healthz/internalendpoint — alsoopenssl 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
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.