Federation setup
This guide takes you from an empty keys directory to a working federated server. Read the Federation overview first for the trust model and endpoint surface.
Prerequisites
Section titled “Prerequisites”- Docker and Docker Compose (20.10+), Git, OpenSSL
- Minimum: 2 CPU cores, 4 GB RAM, 20 GB disk; recommended: 4+ cores, 8+ GB RAM, 100+ GB disk
- A public HTTPS URL for your server (production)
-
Generate a server identity.
Each federated server needs a unique Ed25519 keypair.
federation-keygengenerates one, encrypts it with your passphrase, and prints your server ID.Generate the server keypair mkdir -p keys && chmod 700 keys# Generate a strong passphrase and export itexport ZENTALK_SERVER_KEY_PASSPHRASE="$(openssl rand -base64 32)"cd zentalk-apigo run ./cmd/federation-keygen --key-path ../keys/server.keyfederation-keygenflags:Flag Default Description --key-path./keys/server.keyWhere to save the encrypted keypair --passphrase-envZENTALK_SERVER_KEY_PASSPHRASEEnvironment variable holding the passphrase --show-public-keyfalseAlso print the base64 public key The tool verifies the keypair loads back correctly. Record the printed server ID, back up the key file, and store the passphrase in a password manager or secret store — a lost keypair means a new server ID.
-
Configure the environment.
Set the federation variables on your API server:
.env.federation ZENTALK_FEDERATION_ENABLED=trueZENTALK_SERVER_ID=<your-server-id>ZENTALK_SERVER_KEY_PATH=./keys/server.keyZENTALK_SERVER_KEY_PASSPHRASE=<your-passphrase># Public URL — must be reachable by peer servers; HTTPS in productionZENTALK_PUBLIC_URL=https://zentalk.yourdomain.com# Peers to register with at startupZENTALK_FEDERATION_BOOTSTRAP_URLS=https://bootstrap1.zentalk.chat,https://bootstrap2.zentalk.chat# Refuse plaintext federation traffic (required in production)ZENTALK_REQUIRE_HTTPS=trueFull configuration reference:
Variable Default Description ZENTALK_FEDERATION_ENABLEDfalseEnable federation ZENTALK_SERVER_ID— Your server ID (from step 1) ZENTALK_SERVER_KEY_PATH./keys/server.keyPath to the encrypted keypair ZENTALK_SERVER_KEY_PASSPHRASE— Keypair passphrase ZENTALK_PUBLIC_URL— This server’s public URL ZENTALK_FEDERATION_BOOTSTRAP_URLS— Comma-separated peer/bootstrap URLs to register with at startup ZENTALK_FEDERATION_PEERS— Per-peer Ed25519 public-key fingerprint pinning (documented in the zentalk-webrepository,FEDERATION_SETUP.md; consumer code in review)ZENTALK_INBOX_PROCESSOR_INTERVAL5sInbox processing interval ZENTALK_OUTBOX_PROCESSOR_INTERVAL10sOutbox retry interval ZENTALK_HEALTH_CHECK_INTERVAL60sPeer health-check interval ZENTALK_MESSAGE_RETENTION168hFederated message retention ZENTALK_MAX_INCOMING_MESSAGES_PER_SECOND100Incoming rate limit ZENTALK_MAX_OUTGOING_MESSAGES_PER_SECOND100Outgoing rate limit ZENTALK_REQUIRE_HTTPSfalseRequire HTTPS for federation (set truein production)ZENTALK_ALLOWED_SERVER_IDS— Comma-separated allowlist of server IDs ZENTALK_BLOCKED_SERVER_IDS— Comma-separated blocklist of server IDs -
Exchange fingerprints and configure mTLS.
Federation peers are pinned two ways, and both are exchanged out of band with the peer operator:
- Ed25519 fingerprint pinning — share your server ID and public-key fingerprint with each peer; each side lists its peers in
ZENTALK_FEDERATION_PEERS. Requests from unlisted or mismatched keys are rejected. - Mutual TLS — both servers present certificates from the shared federation PKI; TLS 1.3 is required and certificates hot-reload on rotation. Plaintext HTTP between federation peers is refused in production.
Follow the PKI bootstrap runbook in the
zentalk-webrepository (docs/MTLS_BOOTSTRAP.md) to issue certificates; the CA helper scripts live inscripts/pki/in the same repository. - Ed25519 fingerprint pinning — share your server ID and public-key fingerprint with each peer; each side lists its peers in
-
Start the server (or the two-server demo).
For local testing, the repository ships a complete two-server federation environment — two full stacks (API server, relay, mesh node) plus a shared DHT bootstrap node and Redis:
Two-server federation demo # Required secrets — the compose file refuses to start without themexport ZENTALK_JWT_SECRET="$(openssl rand -base64 64)"export ZENTALK_SERVER_1_KEY_PASSPHRASE="$(openssl rand -base64 32)"export ZENTALK_SERVER_2_KEY_PASSPHRASE="$(openssl rand -base64 32)"docker compose -f docker-compose.federation.yml up -ddocker compose -f docker-compose.federation.yml logs -f api-server-1Host ports in the demo:
Service Server 1 Server 2 Shared Frontend 3000 3100 — API server 3001 3002 — Relay 9001 9002 — Relay metrics 9091 9092 — Mesh P2P 9101 9102 — Mesh Storage API 8081 8082 — DHT bootstrap — — 9000 Redis — — 6379 Each API server is configured with the other as its federation bootstrap (
ZENTALK_FEDERATION_BOOTSTRAP_URLS), so they register with each other automatically at startup. -
Verify registration and health.
Verify federation # Federation enabled, server ID loadedcurl http://localhost:3001/api/federation/status# Peer reachablecurl http://localhost:3001/api/federation/health/statusshould report"enabled": truewith your server ID; once peers have discovered each other, the known-server count rises. In the demo, watch the logs of both API servers for message routing and delivery lines, then send a message between users registered on different servers.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Check |
|---|---|
| Federation does not initialize | ZENTALK_FEDERATION_ENABLED=true set, key file exists at ZENTALK_SERVER_KEY_PATH, passphrase correct; grep API logs for federation |
| Cannot reach peers | curl https://<peer>/api/federation/health, outbound HTTPS allowed by firewall, DNS resolves |
| Messages not delivering | Peer health endpoint reachable, rate-limit errors in logs, outbox retry interval |
| “Replay attack detected” in logs | Normal — duplicate nonces are rejected. If excessive, identify the misbehaving peer and add it to ZENTALK_BLOCKED_SERVER_IDS |
See also
Section titled “See also”- Federation overview — endpoint reference, trust model, rate limits
- Environments and base URLs
- REST API reference
