Proxies and Networking
If you are setting up Cambium on a personal server for one game team or company, you can disregard this section. If you are in a situation where you need to have multiple organizations exist on a single server, or serve connections over plaintext, read ahead.
Serving plaintext
Setting disable_tls: true serves plain HTTP. This should be done when your Cambium server is behind a reverse proxy or entirely on a local network. You should never serve plain HTTP if your server is exposed to the open internet.
With disable_tls: true and no bind_address of your own, the server listens on 127.0.0.1 only and is unreachable from anywhere else, which is the configuration needed for a reverse proxy. Putting plaintext on a public interface takes two deliberate settings.
# A backend behind a proxy: correct, and unreachable except through it.
disable_tls: true
# Plaintext across a LAN you control: possible, but you have to say so.
disable_tls: true
bind_address: 0.0.0.0The banner tells you which of the three states you are in every time the server starts: the certificate fingerprint when TLS is on, a quiet confirmation when plaintext is confined to loopback, and a loud warning when it is not.
Several servers, one machine
A Cambium server owns a port. Two servers mean two ports, and everyone has to know which one is theirs. A reverse proxy replaces that with names: one address, one port, one certificate, and each team reaches its own server by hostname.
This works because the hostname travels with the request twice — once in the TLS handshake, so the proxy knows which certificate to present, and again in the HTTP Host header once the connection is up. A single listener can read the name and route on it.
┌─► 127.0.0.1:9001 organization1
client ──► 1.2.3.4:443 ─► proxy ────┼─► 127.0.0.1:9002 organization2
└─► 127.0.0.1:9003 organization3Each Cambium server changes almost nothing: it still listens on one port and still behaves as though it were the only one. It just moves to loopback and stops doing its own TLS. Cambium servers still stay one-per-organization.
Nginx configuration
Repeat the upstream and server pair for each Cambium server sharing the proxy, changing the name, the certificate and the port.
upstream cambium_gameteam {
server 127.0.0.1:9001;
keepalive 16;
}
server {
listen 443 ssl;
server_name gameteam.example.com;
ssl_certificate /etc/letsencrypt/live/gameteam.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/gameteam.example.com/privkey.pem;
client_max_body_size 1024m;
location / {
proxy_pass http://cambium_gameteam;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
}
}Note:
| Line | Why it matters |
|---|---|
client_max_body_size 1024m | nginx defaults to 1 MB and answers anything larger with a 413 before Cambium sees it, so submits fail with an error that does not come from Cambium at all. Set it above your largest file; 0 means unlimited. |
keepalive + proxy_http_version 1.1 | Without the upstream block and these two lines, nginx opens a new connection to Cambium for every single request — it defaults to HTTP/1.0 upstream and keeps no pool. Cambium is built around keep-alive, so leaving them out costs you most of it. |
X-Forwarded-For | How Cambium learns who the real client is. Without it every user looks like the proxy — see below. |
Caddy configuration
Caddy needs almost nothing: it sets the forwarding headers itself, keeps an upstream connection pool by default, has no body-size limit to raise, and obtains certificates automatically.
gameteam.example.com {
reverse_proxy 127.0.0.1:9001
}
filmteam.example.com {
reverse_proxy 127.0.0.1:9002
}Telling Cambium about your proxy
Behind a proxy every request arrives from the proxy, so without being told otherwise Cambium sees one client where you have twenty. That matters most for the login rate limit: it locks an address out after five failed attempts, and if every user shares one address then one person mistyping their password locks out the whole team.
The fix is trusted_proxies. List your proxy, and Cambium will read the real client address from the X-Forwarded-For header it sets.
trusted_proxies: 127.0.0.1Leave it empty on a directly-reachable server. An empty list means Cambium ignores the header entirely and treats the connecting address as the client, which is what you want when there is no proxy, otherwise anyone could set the header themselves and sidestep the rate limit.
The web portal
Setting enable_web_portal: true serves a browser-facing page on the same port as the API. That split works the same whether or not a proxy is in front, which is why the configurations above route everything to one backend and let Cambium sort it out.
It follows organization features. The portal is browser access to the organization surface — availability, shared builds, team activity — so turning those off takes the portal with it, whatever this setting says. In the Administration page the browser switch sits directly under the organization one and dims when it is off.
With the portal off, a browser hitting the port gets nothing at all: no page, no error, just a closed connection. That is deliberate — an error page would carry a Server header naming the product and its version, which is free information for anyone scanning. It does not hide the server (the port is still open, and TLS still presents a certificate), it just stops it introducing itself to people who have no business here.
One thing to expect: a browser visiting a server that uses Cambium's own self-signed certificate gets a full-page security warning first. The desktop client checks the fingerprint instead, so it never sees one; a browser has no equivalent. In practice the portal belongs on a deployment with a real certificate, which is the same deployment that wants a proxy.
Checking it works
From a machine that is not the server:
curl https://gameteam.example.com/api/servershould return JSON naming that server. If two hostnames return the same name, the proxy is routing both to one backend.- Connect the desktop client to the hostname, with no port. It should not ask you to verify a fingerprint — a certificate from a public authority is accepted automatically.
- Submit a file comfortably larger than a megabyte. A failure here is almost always the proxy's body limit rather than Cambium.
- Get one account's password wrong five times, then log in correctly as a different account from a different machine. The second login should work. If it is refused,
trusted_proxiesis not set and everyone is sharing one rate-limit bucket.
For monitoring, a plain TCP connection check is harmless, but an HTTPS request to /api/server tells you far more: it exercises the certificate, the proxy, the route and the server in one go.
When something is wrong
| What you see | Usually means |
|---|---|
| Clients cannot reach the server after an upgrade, and the banner says plaintext on 127.0.0.1 | disable_tls: true now implies loopback. Set bind_address if you meant to serve the network directly. |
| Submits fail on larger files, smaller ones work | The proxy's request body limit. On nginx raise client_max_body_size. |
| One person's bad password locks everyone out | trusted_proxies is unset, so every user shares the proxy's address. |
| Every hostname reaches the same server | The proxy is not routing on Host, or both blocks point at one port. |
| The browser warns about the certificate | The server is using its own self-signed certificate. Expected — use a CA-issued one for anything a browser touches. |
| The server will not start: address already in use | Another Cambium process still holds the port. Stop it before starting a new one. |