Self-Hosting the Relay
The relay is open source under AGPL-3.0 and self-hostable, which is the way to remove Kangentic from your connection metadata entirely.
Source: github.com/Kangentic/relay.
Why you might self-host
Section titled “Why you might self-host”The relay cannot read your traffic either way. What self-hosting changes is the metadata: who gets to observe your IP addresses, connection timing, and traffic shape. Running your own means the answer is “you”. See what a relay operator can still see.
Quickstart
Section titled “Quickstart”-
Clone and start it.
Terminal window git clone https://github.com/Kangentic/relay.gitcd relaycp .env.example .envdocker compose up -dcurl http://127.0.0.1:8080/healthzThis pulls
ghcr.io/kangentic/relay, the same image the hosted instance runs. -
Put TLS in front of it.
The relay speaks plain HTTP and WebSocket and never terminates TLS. Your phone will refuse to pair with a relay that is not using TLS, so you need a reverse proxy. The repository ships a
Caddyfile.examplethat handles certificates automatically. -
Point the desktop at it.
In Settings > Mobile Devices, set Relay to Custom Relay and enter your address, for example
wss://relay.example.com. Use Test connection to confirm it responds.
The one setting that will stop it booting
Section titled “The one setting that will stop it booting”If you put a reverse proxy in front of the relay, you must set both TRUST_PROXY=true and TRUSTED_PROXY_CIDRS. The relay refuses to start with TRUST_PROXY=true and an empty CIDR list.
That refusal is deliberate. Trusting CF-Connecting-IP or X-Forwarded-For from any peer would let a client with no proxy in front of it forge either header and bypass every per-IP cap and rate limit.
Once a peer is trusted, X-Forwarded-For is read from the right, nearest hop first, skipping trusted-proxy hops. A proxy that appends to the header rather than replacing it is therefore still safe.
Your reverse proxy should also strip headers it is not authoritative for. The shipped Caddyfile.example does this:
relay.example.com { reverse_proxy relay:8080 { header_up -CF-Connecting-IP header_up X-Forwarded-For {client_ip} header_up -X-Real-IP header_up -Forwarded }}The -CF-Connecting-IP strip matters: the relay reads that header first, so leaving it in place with no Cloudflare in the topology would let any client forge it.
Everything else is optional. Every variable has a working default, and Relay Reference documents the full set.
Choosing an image tag
Section titled “Choosing an image tag”docker-compose.yml reads IMAGE_TAG and defaults to latest, which tracks the newest commit on the main branch rather than a release.
Relay address rules
Section titled “Relay address rules”The address you give the desktop has to satisfy the phone as well, which enforces the same rules independently when it scans the pairing QR code:
wss://is required.ws://is accepted only forlocalhost,127.0.0.1, and[::1].- No backslashes, no URL fragment, and no username or password in the URL.
- 512 bytes maximum, because the address has to fit in the pairing QR payload.
Operational limits
Section titled “Operational limits”One instance
Section titled “One instance”The table matching two peers to a slot lives in the relay process’s memory, so both peers of a connection must reach the same process.
The same applies to DNS: never point a relay hostname at more than one server.
Capacity
Section titled “Capacity”A small VPS goes a long way. Measured on a two-core instance, 500 concurrent pairs at 10 frames per second each hold sub-3 ms median latency at around 93 MB resident, and 2,500 pairs cost roughly 8 KB per socket. The practical ceilings are bandwidth and file descriptors rather than CPU, so raise LimitNOFILE or ulimit -n comfortably above MAX_CONNECTIONS. The shipped compose file sets it to 65535.
Because a slow consumer’s buffer can grow to MAX_BUFFERED_BYTES, connection caps alone do not bound memory. Set a container memory limit as the real backstop.
Licensing
Section titled “Licensing”The relay is AGPL-3.0-only and self-hostable indefinitely. With no admission webhook configured, which is the default, every connection is admitted, and the relay never talks to any control plane. See Kangentic Relay.
Next steps
Section titled “Next steps”Next: Relay Reference - every environment variable, endpoint, and close code.
See also:
- Relay Security Model - what a slot identifier does and does not buy an attacker
- The Hosted Relay - what you are opting out of
- Pairing a Phone - pointing a desktop at your relay during pairing
- Privacy Policy - the same disclosure in policy form
Kangentic is free and open source. A star helps other people find it.
Star on GitHub