Behind a TCP front end
Это содержимое пока не доступно на вашем языке.
A front end is not needed. The relay terminates TLS 1.3 itself and gets its own certificates (see the quick start). Use this layout only if another service must share port 443 on the host.
TLS must terminate at the relay
Section titled “TLS must terminate at the relay”The protocol (v2 and later) binds each client’s key proof, and the relay’s identity signature, to the TLS session between the client and the relay. So:
- Supported: a TCP (layer-4) passthrough on the same host that forwards the raw bytes to the relay and prefixes each connection with a PROXY protocol v2 header. Two front ends are supported: HAProxy with
send-proxy-v2, and Caddy with the caddy-l4 plugin withproxy_protocol v2. - Not supported: anything that terminates TLS in front of the relay: Caddy
reverse_proxy, nginxproxy_passon anssllistener, HAProxymode http, a CDN in proxy mode, or a TLS-terminating load balancer. The two ends see different TLS sessions and every connection fails; the app shows “This server couldn’t prove its identity”. - Not supported: nginx’s stream module. It sends only PROXY protocol version 1, which the relay refuses.
The old layout, where a proxy terminated TLS and the relay ran with --no-tls on loopback, no longer works.
The relay
Section titled “The relay”rchat/config/server-behind-proxy.toml in the rchat-core repository is the config template for this layout, and the published unit rchat-server-proxy.service runs it. The essentials:
# /etc/rchat/server.toml, behind a TCP passthrough on public :443[server]bind_addr = "127.0.0.1:8443"max_connections = 10000# Every connection arrives from the front end's address:# no per-address caps here (enforce them in the front end).max_connections_per_ip = 0max_connections_per_ipv6_48 = 0# The front end sends PROXY protocol v2 with the client's address.proxy_protocol = truewebsocket_path = "/CHANGE-ME-to-a-long-random-path"relay_identity_path = "/var/lib/rchat/relay_identity.key"
# The relay terminates TLS 1.3 itself. Send SIGHUP# (systemctl reload rchat-server) after renewing the certificate.[server.tls]cert_path = "/etc/rchat/tls/fullchain.pem"key_path = "/etc/rchat/tls/privkey.pem"
[metrics]bind_addr = "127.0.0.1:9090"
[decoy]enabled = falseBuilt-in ACME ([server.acme] instead of [server.tls]) also works when the front end forwards every connection for the domain on public :443, because Let’s Encrypt’s TLS-ALPN-01 check then reaches the relay.
Create the relay identity once, then install the unit:
sudo -u rchat /usr/local/bin/rchat-server --config /etc/rchat/server.toml --init-relay-identitycurl -fsSL -o rchat-server.service https://assets.r-chat.net/1.1.0/rchat-server-proxy.servicesudo install -m 0644 rchat-server.service /etc/systemd/system/rchat-server.servicesudo systemctl daemon-reloadsudo systemctl enable --now rchat-serverThe unit starts rchat-server --config /etc/rchat/server.toml --bind 127.0.0.1:8443, with TLS from the config file. A loopback port above 1024 needs no capability.
proxy_protocol
Section titled “proxy_protocol”With proxy_protocol = true the relay reads the PROXY v2 header from the raw TCP stream before the TLS handshake, within 5 seconds, and uses its source address for the per-source fair shares only: pending requests, circuits, stored message requests, the daily request quota and shedding (see Resource limits). It is never used for connection caps, authentication or logging.
- The parser is strict: version 2 only, commands LOCAL and PROXY, TCP over IPv4 or IPv6, at most 16 + 216 bytes. A connection without a valid header is closed (
reason="proxy_header"). - A connection whose TCP peer is not a loopback address is refused before anything is read, with a warning (
reason="proxy_not_loopback"). - The relay refuses to start with
proxy_protocol = trueunlessmax_connections_per_ip = 0,max_connections_per_ipv6_48 = 0andbind_addris a loopback address (127.0.0.1or::1; a wildcard bind is refused). - Both sides must agree. If the front end does not send PROXY v2, every connection is closed; if the relay has
proxy_protocol = false, the header breaks every TLS handshake.
Trust boundary. The relay believes a PROXY header from any process that can connect to its loopback port, so any local user or process can name any source address. At worst that skews the fair shares. Run the relay on a host you don’t share with untrusted users if fairness matters to you.
Behind a passthrough without PROXY v2, every connection comes from the front end’s address. Set both per-address caps to 0; the per-source fair shares are then off, and one client can crowd out everyone else’s requests.
The relay does not read X-Forwarded-For. The former trusted_proxy setting was removed: a config that still sets it refuses to start and points to proxy_protocol.
HAProxy
Section titled “HAProxy”Including a per-client connection cap, which takes the place of the relay’s max_connections_per_ip:
frontend rchat mode tcp bind :::443 v4v6 # At most 64 concurrent connections per client address. stick-table type ipv6 size 1m expire 10m store conn_cur tcp-request connection track-sc0 src tcp-request connection reject if { sc0_conn_cur gt 64 } default_backend relaybackend relay mode tcp server r1 127.0.0.1:8443 send-proxy-v2Caddy with caddy-l4
Section titled “Caddy with caddy-l4”Caddy needs the caddy-l4 plugin (xcaddy build --with github.com/mholt/caddy-l4). rchat/config/Caddyfile in the rchat-core repository:
{ layer4 { :443 { route { proxy { proxy_protocol v2 upstream 127.0.0.1:8443 } } } }}The file has no site blocks, so Caddy’s HTTP app does not claim :443. As far as we know caddy-l4 cannot cap concurrent connections per client address (its throttle handler limits bandwidth only), so in this layout one client can open up to max_connections. If you need a per-client cap, use HAProxy or let the relay own :443.
Check the deployment
Section titled “Check the deployment”# The certificate presented must be the relay's own, not the front end's.openssl s_client -connect chat.example.com:443 -servername chat.example.com </dev/null | head -20curl -s http://127.0.0.1:9090/metrics | grep -E 'reason="proxy_(header|not_loopback)"'Then connect once with the app. A growing proxy_header count usually means the front end is not sending PROXY v2.