Configuration guide
Это содержимое пока не доступно на вашем языке.
The relay reads one TOML file, /etc/rchat/server.toml by default (change it with --config). This page covers the settings most operators touch. The configuration reference lists every key with its type and default.
How the file is read
Section titled “How the file is read”- A missing, unreadable or invalid file is a startup error. The relay never quietly falls back to defaults.
- Server settings can sit under
[server]or at the top level. Sub-tables work either way too:[server.acme]and[acme]mean the same. - Unknown keys are a startup error in every table, so a typo stops the server with
unknown field .... Keys left over from earlier templates must be removed before upgrading; Troubleshooting names the common ones. - Removed keys are refused with an explanation:
message_queue_persist_path(see Offline queue) andtrusted_proxy(see Behind a TCP front end). - Resource limits are checked at startup. A value of
0or out of range stops the server, and there is no “unlimited” setting.
A typical file
Section titled “A typical file”[server]bind_addr = "0.0.0.0:443"websocket_path = "/Zk3q-replace-with-32-random-url-safe-chars"relay_identity_path = "/var/lib/rchat/relay_identity.key"
[server.acme]enabled = truedomains = ["chat.example.com"]cache_dir = "/var/lib/rchat/acme"staging = false
[server.offline_queue]ttl_secs = 259200 # 72 hourspersist = false # memory only
[server.decoy]enabled = true# root = "/var/www/decoy"rchat/config/server.toml in the rchat-core repository is a commented template with every limit and both certificate options. rchat/config/server-behind-proxy.toml is the template for a relay behind a TCP front end.
Certificates
Section titled “Certificates”Configure exactly one certificate source: [server.acme] (built-in Let’s Encrypt) or a static certificate (cert_path and key_path under [server.tls]). TLS always terminates at the relay. See TLS and certificates.
TLS 1.3 resumption tickets are on by default ([server.tls] session_tickets = true), for both sources; with ACME, a [server.tls] table may hold only that key. See TLS resumption.
Relay identity
Section titled “Relay identity”relay_identity_path sets where the relay’s long-term identity key lives (default: relay_identity.key next to the config file). Create it once with --init-relay-identity. See Relay identity.
WebSocket path and decoy site
Section titled “WebSocket path and decoy site”Only a WebSocket upgrade to exactly websocket_path reaches the relay. Any query string is ignored when matching. Every other request gets the decoy website, including plain HTTP requests, upgrades to other paths and garbage bytes. Request paths are never logged.
Set a long random path:
echo "/$(openssl rand -base64 24 | tr '+/' '-_')"Then put the same path in every client’s server URL: wss://chat.example.com/<path>. The path must start with / and be plain ASCII with no ?, #, spaces or control characters.
The decoy ([server.decoy]):
| Key | Default | Meaning |
|---|---|---|
enabled | true | Serve a decoy website to non-relay requests. With false, those requests get a plain 404. The path check still applies. |
root | none | Absolute path to a static site to serve. It must exist at startup. Symlinks that point outside it are not followed. With no root, a built-in site is served that varies with host. |
host | first [server.acme] domain | Host name that seeds the built-in site, together with the relay’s secret identity key. Never shown on the page. |
server_header | "nginx/1.24.0" | Value of the Server response header. |
write_timeout_secs | 30 | Time limit for writing one decoy response. |
The decoy answers one request per connection. It is a mitigation, not a disguise: it keeps a casual scan from seeing what runs on the port, but a determined prober can tell it apart from real nginx by its behaviour (no keep-alive, TLS and timing details).
Range requests follow nginx 1.24: one satisfiable range gets a 206; several ranges get a 206 with a multipart/byteranges body; a malformed or unsatisfiable set such as bytes=abc or bytes=5-3 gets a 416; ranges adding up to more than the file get the whole file. A 206 carries no Accept-Ranges header.
A decoy site for each relay
Section titled “A decoy site for each relay”rchat-server --config <path> --render-decoy <dir> [--host <hostname>] writes a small static website into <dir> and exits. The site is a login page for an invented product (webmail, a NAS admin page, a photo library, a cloud drive or code hosting). The product, its brand name, accent colour and logo are chosen from the relay’s secret identity key and its host name, so two relays never serve identical pages and nobody can work out a relay’s page from its host name and pin. The same identity and host always give the same site, the one the relay serves itself. Re-provisioning a relay with the same identity key reproduces its site.
The command reads the relay identity key, so it needs --config and must run as the service user, like --print-invite. Without the key it refuses.
sudo -u rchat rchat-server --config /etc/rchat/server.toml --render-decoy /var/lib/rchat/decoy --host chat.example.comThen point the relay at it:
[server.decoy]root = "/var/lib/rchat/decoy"- Host name. The host is lower-cased and any trailing dot is dropped before it is used, so
Chat.Example.com.andchat.example.comgive the same site. Without--host, the command uses[server.decoy] host, or else the first[server.acme]domain from the config file, or else no host at all (the identity key alone seeds the site). - Directory.
<dir>must be empty or missing, or a directory this command rendered before. Anything else is refused and left untouched. Symlinks are not followed out of<dir>, and each file is written through a temporary file and a rename. - Self-contained. All styles, scripts and logos are in the site’s own files, with no outside fetches. The login form never submits; it shows an “incorrect username or password” message.
- Missing pages. Unknown paths get the site’s own
404.htmlpage with status 404. - Nothing identifying. The site never contains the WebSocket path, the relay pin, a version number or the project name.
- Without a
root. The relay serves a built-in site of the same kind, seeded byHKDF-SHA256(secret identity key, salt = host name, info = "rchat/decoy-seed/v3"), with the host from[server.decoy] host, else the first[server.acme]domain, else none. The page is unique to the relay and cannot be worked out from its host name or public pin. Running--rotate-relay-identitychanges it, because the key changes. A site already rendered to disk stays as it is until you render it again. - Upgrading to 2.0.0. Earlier builds seeded the page from the host name alone, so every relay with a host name gets a new decoy design once, on upgrade. Relays without a host keep theirs. Re-run
--render-decoyif you serve a rendered site and want it to match.
The decoy makes the relay look like an ordinary website to casual probes. It is not a guarantee against a determined prober.
Offline queue
Section titled “Offline queue”Messages and message requests for a recipient who is not connected are held by the relay until they are delivered or expire. They are filed under the recipient only; the sender’s identity is inside the encrypted payload.
[server.offline_queue]ttl_secs = 259200 # 72 h; 3600 to 259200 (72 h is the maximum)knock_budget_bytes = 16777216 # 16 MiB for message requests, separate from messagespersist = false # memory only (default)# persist_path = "/var/lib/rchat/queue.rcq"# persist_key_path = "/run/rchat/queue.key"# allow_persistent_key = false-
By default nothing is written to disk. Queued items live in memory and are lost on restart.
-
ttl_secsis how long a queued message lives, at most 72 hours: a larger value stops the relay at startup, so the app’s promise that waiting copies expire within 72 hours holds on every relay. Message requests live at most 72 hours, whatever the setting. -
Steady-size entries. Messages of exactly the steady traffic size (1,226 bytes sealed) may fill at most half of one recipient’s queue (500 of 1,000 messages); beyond that they are refused like a full queue, not dropped.
-
Encrypted persistence is opt-in. With
persist = truethe queue is written encrypted topersist_pathevery 5 minutes and on shutdown, and read back (then deleted) at start. The relay refuses to start unless the key file atpersist_key_pathis exactly 32 bytes, mode0600or narrower, owned by the service user, in a different directory and on a different filesystem from the data, and on a memory filesystem (Linuxtmpfsorramfs; FreeBSDtmpfsormfs). Queued data then survives a service restart but not a reboot. -
The shipped systemd units keep
/run/rchat(a tmpfs, preserved across restarts but not reboots) and create/run/rchat/queue.keyonce per boot if it is missing, so the template paths work as they are. -
allow_persistent_key = trueskips only the memory-filesystem check and logs a warning. A relay that sets it can no longer say that its queue key is lost on reboot. -
What the sender’s “Sent” means. The relay acknowledges a message only once it has handed it to the recipient’s connection or holds it in the recipient’s queue. A message that does not fit the caps is refused with no acknowledgement; queued messages are never dropped to make room. A message for someone who is connected but has not yet picked up the conversation again (just after an app restart, say) is handed straight to their connection instead of waiting for their next connection.
Size caps for the queue are under Limits and memory budgets below and on the resource limits page.
Behind a TCP front end (PROXY protocol v2)
Section titled “Behind a TCP front end (PROXY protocol v2)”By default the relay owns port 443 and sees each client’s address itself. If another service must share port 443, put a TCP (layer-4) passthrough on the same host in front of it and turn on proxy_protocol:
[server]bind_addr = "127.0.0.1:8443"max_connections_per_ip = 0max_connections_per_ipv6_48 = 0proxy_protocol = true- Every connection must then start with a PROXY protocol v2 header (HAProxy
send-proxy-v2, caddy-l4proxy_protocol v2), read before TLS. Connections without a valid v2 header, or from a non-loopback peer, are refused. nginx sends only v1, which is refused. - The relay refuses to start with
proxy_protocol = trueunlessbind_addris a loopback address and both per-address caps (max_connections_per_ip,max_connections_per_ipv6_48) are0. - The address in the header is used for the per-source fair shares only, never for authentication, logging or connection caps: any local process can send a header naming any source.
- The front end must not terminate TLS. The app’s connection proof is bound to the TLS session, so TLS has to end at the relay.
rchat/config/server-behind-proxy.toml in rchat-core is the template. Behind a TCP front end has HAProxy and Caddy examples.
Keepalive and idle connections
Section titled “Keepalive and idle connections”- Idle timeout.
connection_timeout_secs(default300, allowed60–86400) closes a connection that has sent nothing for that long. The app sends a keepalive every 22 to 38 seconds, so connected apps are never affected. - The relay’s pings. The relay sends its own keepalive ping every 25 to 35 seconds and closes a connection whose pong is more than 60 seconds late. The 60 seconds count from when the ping was actually written to the socket, not from when it was queued, so a ping waiting behind a large message that the client is still reading slowly does not cut the connection. A connection whose socket accepts nothing for 30 seconds is closed as stalled instead.
- The app’s pings. The relay answers each keepalive ping from the app with a pong. These answers start no timer of their own.
- Identification. A new connection must complete a signed key publish within
identification_timeout_secs(default15). A hello alone does not count.
None of the ping timings can be configured.
Limits and memory budgets
Section titled “Limits and memory budgets”Every limit has a safe default, and a value of 0 or out of range stops the relay at startup (the per-address caps accept 0 to turn them off). The ones most operators look at:
| Key | Default | What it limits |
|---|---|---|
max_connections | 10000 | Simultaneous connections |
max_connections_per_ip | 64 | Connections from one IPv4 address or IPv6 /64 (0 = off) |
rate_limit_bps | 1048576 | Inbound bytes per second per connection (throttled, not closed) |
max_message_bytes | 4194304 | Largest message from a client |
offline_queue_max_total_bytes | 268435456 | Offline queue, all recipients |
offline_queue_max_bytes_per_recipient | 16777216 | Offline queue, one recipient (also at most 1000 messages) |
outbound_budget_bytes | 1073741824 | Bytes queued for sending, all connections |
buffer_budget_bytes | 536870912 | WebSocket buffers, all connections |
When outbound_budget_bytes or buffer_budget_bytes is spent, the relay closes the heaviest connection of the source holding the most. Size both budgets and the offline queue to the host’s memory: the defaults suit hosts with 4 GB or more; on a 2 GB host use about 256 MiB and 128 MiB.
The full list, with allowed ranges, fair shares and metrics, is on the resource limits page.
Local development
Section titled “Local development”--no-tls serves plaintext WebSocket and is for local development only. It requires [server] dev_origin, the origin clients dial, and refuses a non-loopback bind:
[server]bind_addr = "127.0.0.1:18443"dev_origin = "ws://127.0.0.1:18443"rchat-server --config ./dev.toml --init-relay-identityrchat-server --config ./dev.toml --no-tlsWithout TLS there is no channel binding: client proofs are bound to a hash of dev_origin, which only stops replay to a relay with a different origin. Never expose a --no-tls relay.
Logging
Section titled “Logging”--log-level(orRCHAT_LOG_LEVEL) sets verbosity. The default isinfo. Logs go to standard output; under systemd they land in the journal.[logging] log_client_ips = trueadds client IP addresses todebuglines only. The default isfalse.[logging] formatandoutputare accepted but not currently used.
What each level contains is described in What the relay logs and stores.
Metrics
Section titled “Metrics”[metrics] serves Prometheus counters on 127.0.0.1:9090 by default. The endpoint is unauthenticated: a non-loopback bind_addr stops the relay at startup unless you also set allow_public = true, and then you must firewall it. See Metrics.
Settings that are accepted but not used
Section titled “Settings that are accepted but not used”circuit_ttl_secsis accepted and ignored, with a startup warning. Circuits live while both endpoints are connected and end when either disconnects or closes them. There is no time-based expiry.redis_url,[logging] formatand[logging] outputparse but have no effect.