Pre-deployment Checklist¶
Run through this list before exposing Signal Fish Server to the internet. Every item names the real configuration key (or command) involved — see the configuration reference for defaults, Configuration recipes for per-feature snippets, and Run Modes for the run commands.
Start by letting the server validate itself: it exits non-zero on an insecure or malformed config, so this is the single best gate.
Bash
cargo run -- --validate-config
# or against your built image:
docker run --rm -v ./config.json:/app/config.json:ro \
ghcr.io/ambiguous-interactive/signal-fish-server:latest --validate-config
Then confirm the resolved posture (secrets are redacted):
Authentication¶
- WebSocket auth enabled —
security.require_websocket_auth=true. - At least one real app registered in
security.authorized_appswith a meaningfulapp_id/app_name. - CORS locked down —
security.cors_originsset to your origin(s), not*. -
security.max_connections_per_ipset to a sane ceiling (default24).
Secrets changed¶
- No default secrets remain. The example
app_secret(CHANGE_ME_BEFORE_PRODUCTION) inconfig.example.jsonis intentionally insecure — replace everyauthorized_apps[*].app_secret. -
turn.static_auth_secret(if TURN is enabled) is a strong random value, supplied viaSIGNAL_FISH__TURN__STATIC_AUTH_SECRET, not committed to the repo. -
security.metrics_auth_token(if metrics auth is enabled) is a strong random value (openssl rand -hex 32). -
--print-configshows every set secret as<redacted>and no plaintext secret leaks into logs or CI output.
TLS or reverse proxy¶
- Signaling is served over
wss://— mandatory for WebRTC, because DTLS fingerprints travel through the signaling channel (see signaling must run over wss://). - Either built-in TLS is on (
security.transport.tls.enabled=truewithcertificate_pathandprivate_key_path) or TLS terminates at a reverse proxy (see reverse proxy setup). - If using mTLS:
security.transport.tls.client_auth="require"with a validclient_ca_cert_path.
Metrics auth¶
-
/metrics,/v1/metrics, and/metrics/promare not openly reachable — eithersecurity.require_metrics_auth=truewith asecurity.metrics_auth_token, or the endpoints are restricted at the reverse proxy (IP allowlist). - The metrics token is at least 32 characters (the server warns below 16).
- Prometheus is configured with the bearer token if auth is on (see the metrics-auth recipe).
Rate limits sane¶
-
rate_limit.*values fit your traffic —max_room_creations,max_join_attempts,max_signals,max_signal_errors, andtime_window(see when to adjust). - Per-app overrides (
authorized_apps[*].rate_limit_per_minute,max_rooms,max_players_per_room) set where an app needs different limits. -
security.max_signal_bytesandsecurity.max_message_sizeleft at sane caps (max_signal_bytesmust be> 0and≤ max_message_size).
TURN secret shared and rotated¶
- If TURN is enabled (
turn.enabled=true):turn.urlslists at least one reachableturn:/turns:URL andturn.static_auth_secretis set. - The same
static_auth_secretis configured on coturn and the signaling server (the composeturnprofile readsTURN_STATIC_AUTH_SECRET). -
turn.credential_ttl_secscomfortably exceeds your longest expected session (default3600). - A rotation procedure is in place (see rotating the shared secret).
- coturn's relay port range is published/reachable (see the TURN quick start).
Log rotation¶
- File logging configured for production —
logging.enable_file_logging=true,logging.dir,logging.filename. -
logging.rotationset (daily,hourly, ornever) to bound disk usage. -
logging.formatchosen (jsonfor log pipelines,textfor humans) and a level set vialogging.levelorRUST_LOG.
Room cleanup intervals¶
-
server.room_cleanup_intervalis> 0(default60) so the cleanup task actually runs. -
server.empty_room_timeoutandserver.inactive_room_timeouttuned to your session lengths so stale rooms are reclaimed. - If reconnection is enabled (
server.enable_reconnection=true):server.reconnection_windowandserver.event_buffer_sizematch how long and how much you want to buffer for replay.
Deployment topology¶
- One active Signal Fish process serves each routing domain; no generic round-robin or cookie-sticky load balancer fronts interchangeable processes.
- If operating several isolated deployments, an application-owned directory assigns the room home before the WebSocket upgrade and routes every initial connection and reconnect for that room to the same process.
-
server.room_code_prefixandserver.region_id, if set, are treated as routing/observability metadata rather than shared-state coordination. - The load balancer stops sending new connections before
SIGTERM, and its timeout allows at leastserver.drain_grace_secsfor the close boundary. - Review the single-instance deployment contract and scaling architecture notes.
Final smoke test¶
-
--validate-configpasses. - Health check responds:
curl https://your-host/v2/healthreturns200. - A real client can authenticate, create/join a room, and (for v3) negotiate
its
SessionPlan.
See also¶
- Run Modes — how to run each mode
- Configuration recipes — per-feature config
- Configuration reference — every key and default
- Deployment — Docker, reverse proxies, cloud providers
- Authentication — client handshake and error codes
- TURN Deployment — self-hosted coturn and rotation