Docs
VPS setup (Ubuntu 24.04)
A complete server from a fresh VPS in six steps. You need released binaries and root access — no Rust toolchain, no compiler, nothing to build.
Before you start
- A fresh Ubuntu 24.04 VPS with root access.
- 1 vCPU and 1 GB RAM is enough for a small deployment; relays are I/O-bound, not CPU-bound.
- A DNS
Arecord pointing at the box if you want the TLS-camouflage gateway on:443. - The server binary archive for your architecture from the download page.
etoki:// address when it finishes.1. Install the binaries
Download the archive, verify its checksum, and install both binaries to /usr/local/bin. Do not skip the checksum — it is the only integrity check on a binary distribution.
# On a fresh Ubuntu 24.04 box, as root
apt update && apt install -y curl ca-certificates coturn certbot
curl -fLO <download-url>/etoki-server-<version>-linux-x86_64.tar.gz
curl -fLO <download-url>/etoki-server-<version>-linux-x86_64.tar.gz.sha256
sha256sum -c etoki-server-<version>-linux-x86_64.tar.gz.sha256
tar xzf etoki-server-<version>-linux-x86_64.tar.gz
install -m 0755 etoki etoki-botrelay /usr/local/bin/sha256sum -c does not print OK, stop. Do not install the binary.2. Stage the deploy tree
The deploy/ directory ships an idempotent installer plus verbatim systemd units for every service. Copy it to the box at /opt/etoki-deploy, then describe this server in its server.env:
# /opt/etoki-deploy/n1/server.env — this box's facts
SERVER_NAME="n1"
SERVER_IP="YOUR_SERVER_IP"
EXTERNAL_IP="YOUR_SERVER_IP" # coturn external-ip
SERVER_HOST="relay.example.com" # DNS name pointing here
TLS_SNI="relay.example.com" # gateway certificate name
HAS_BOTRELAY=1 # 0 on boxes without the bot relay
BOT_HOST="bot.example.com" # bot API name (bot-relay boxes only)
# Extra names the directory refuses, on top of the built-in guard
# (admin, support, etoki, security, ... are blocked by default).
RESERVED_EXTRA="relay,router,directory,gateway,billing,legal"Set HAS_BOTRELAY=0 on any box that should not run the bot relay. Everything else is identical between servers. Point the DNS names at this box before the next step: certificate issuance checks them.
3. Bootstrap
Run the installer as root. It creates the dedicated service users, sets up state directories, templates the coturn configuration, and installs and enables the systemd units:
# Bootstrap: service users, state dirs, coturn, certificates, units.
# Idempotent — safe to re-run for upgrades.
TURN_SECRET=$(openssl rand -hex 32) bash /opt/etoki-deploy/n1/install.sh/var/lib, so it is also the upgrade path.Keep the TURN_SECRET value. You will need it again when adding the TURN server in the app, and every server in the same deployment should share it.
server.env and run the same command again.Certificates
The installer gets a Let’s Encrypt certificate for each name on the box and mirrors it to /etc/etoki/tls/<domain>/, readable by the one service that needs it. A renewal hook reinstalls it and restarts that service.
# The installer does this for you; this is what it runs.
# HTTP-01 on :80 — TLS-ALPN would need :443, and the gateway owns that.
certbot certonly --standalone -d relay.example.com
# Renewal is the packaged timer. Check it end to end:
certbot renew --dry-run
systemctl list-timers certbot.timerThe gateway’s certificate is camouflage, not trust: clients pin relay fingerprints and never verify it. It is CA-issued anyway because a self-signed certificate on :443 is itself an anomaly worth avoiding, and because the decoy page should load in a browser. The bot API’s certificate is load-bearing — without it every caller has to skip verification.
4. Record the service identities
On first start each service generates a long-term key and prints its address. The fingerprint in that address is what clients pin — there is no CA and no PKI, so these strings are the trust anchor:
# Each service prints its address on first start. Record these:
# clients pin the fingerprint, so it must not change.
journalctl -u etoki-router -n 20 --no-pager | grep etoki://
journalctl -u etoki-directory -n 20 --no-pager | grep etoki://Save the router and directory addresses somewhere safe. They survive restarts and upgrades because the identity lives in /var/lib.
5. Open the ports
ufw allow 22/tcp
ufw allow 443/tcp # gateway (TLS camouflage)
ufw allow 5223/tcp # router
ufw allow 5443/tcp # file relay
ufw allow 5663/tcp # directory
ufw allow 3478 # coturn (TCP + UDP)
ufw allow 80/tcp # certbot renewals (HTTP-01)
ufw allow 8443/tcp # bot API (only on the bot-relay host)
ufw enableOnly open 8443 on the host that actually runs the bot relay. Its plain-HTTP listener stays on 127.0.0.1:5880 and should never be exposed. Port 80 has to stay reachable even though nothing listens there day to day: certbot binds it for a few seconds during each renewal.
6. Verify
systemctl status etoki-router etoki-filerelay etoki-directory etoki-gateway
# Real certificates, so no -k anywhere:
curl -o /dev/null -w '%{http_code}\n' https://relay.example.com/ # 200, decoy page
curl https://bot.example.com:8443/v1/healthz # bot-relay host onlyAll units should report active (running). From the app, add your router address under relay management and run the built-in health check.
Running a second server
Repeat every step on the second box. Only two things differ: its TLS_SNI (each gateway needs its own DNS name, and so its own certificate) and whether it runs the bot relay. Point the bot relay at both directories so usernames resolve either way.
Upgrading
Replace the binary and restart. Identity, queues and registrations are all under /var/lib and are left untouched, so client-pinned fingerprints keep working:
# Upgrade in place: state under /var/lib is untouched, so the
# router and directory fingerprints survive the restart.
scp etoki root@your.host:/usr/local/bin/etoki
ssh root@your.host 'systemctl restart etoki-router'