TLS and security headers
Certificates
- Caddy obtains and renews Let's Encrypt certificates for every name
it serves automatically (
PLATFORM_DOMAIN,RMM_DOMAIN,RMM_FILES_DOMAINand each name inPLATFORM_OLD_DOMAINS;ACME_EMAILreceives expiry notices). Every name needs a DNS record pointing at the host and port 80 reachable (HTTP-01 challenge). The certificates live in thecaddy-datavolume; back it up to avoid re-issuing after a rebuild. - To use your own certificate, replace automatic TLS in the site blocks of
deploy/Caddyfilewithtls /path/cert.pem /path/key.pemand mount the files into thewebcontainer. - For labs without public DNS, see Installation → Lab installation.
Sites
deploy/Caddyfile always serves these sites:
| Site | Serves |
|---|---|
PLATFORM_DOMAIN | The Hub's app at / and its API at /api/platform/*; Axis's portal at /axis/* and its API at /axis/api/* (prefix stripped, to the backends); the documentation at /docs/*; /manage/api/* for agents enrolled there, other /manage/* → 308 to /axis/*. /api/platform/internal/* and any other /api/* → 404. |
RMM_DOMAIN | Axis's host from before the Hub: /api/* and /manage/api/* to the backends (enrolled agents, connectors, scripts); /docs/* and /axis/* → 308 to the same path on PLATFORM_DOMAIN; everything else (old portal pages) → 308 to https://PLATFORM_DOMAIN/axis{uri}. |
PLATFORM_OLD_DOMAINS (optional) | Earlier Hub names: /axis/api/* and /manage/api/* to the backends; everything else → 308 to the same path on PLATFORM_DOMAIN (/manage/* pages to /axis/*). |
RMM_FILES_DOMAIN | Object storage (MinIO) for presigned downloads and uploads. |
The remote desktop relay between replicas (/api/remote/v1/internal/*)
is refused (404) on every public host; replicas reach each other
directly.
Behind an existing nginx
On a host where nginx already serves other sites on ports 80 and 443 (production since 2026-09-27: the website, the helpdesk and Vaultwarden), Caddy is published on loopback and nginx routes each TLS connection by its server name (SNI) without decrypting it. Caddy keeps its own certificates, WebSockets and port 8322; the other sites keep theirs.
-
In
deploy/.env:WEB_HTTP_PORT=127.0.0.1:8080andWEB_HTTPS_PORT=127.0.0.1:8443(HTTP/3 on 443/udp and 8322 stay published directly). -
apt install libnginx-mod-stream, and at the top level of/etc/nginx/nginx.conf:stream { include /etc/nginx/stream.d/*.conf; }. -
/etc/nginx/stream.d/sni.conf:map $ssl_preread_server_name $sni_upstream {hub.entrosity.com 127.0.0.1:10444;manage.entrosity.com 127.0.0.1:10444;portal.entrosity.com 127.0.0.1:10444;files.manage.entrosity.com 127.0.0.1:10444;media.entrosity.com 127.0.0.1:10444;default 127.0.0.1:10443; # nginx's own HTTPS sites}server {listen 443;listen [::]:443;ssl_preread on;proxy_protocol on; # the client's address for the next hopproxy_pass $sni_upstream;proxy_connect_timeout 5s;proxy_timeout 24h; # agents keep WebSockets open for hours}# Strips the PROXY header before Caddy (the Caddyfile stays unchanged).server {listen 127.0.0.1:10444 proxy_protocol;proxy_pass 127.0.0.1:8443;proxy_timeout 24h;} -
Every existing HTTPS server of nginx listens on
listen 127.0.0.1:10443 ssl proxy_protocol;instead of443/[::]:443, and/etc/nginx/conf.d/00-realip.confrestores the client address:set_real_ip_from 127.0.0.1; real_ip_header proxy_protocol;. -
/etc/nginx/conf.d/entrosity-platform.confhands port 80 of the Entrosity names to Caddy (redirects to HTTPS, HTTP-01 challenges): aserverwithlisten 80;, the five names andlocation / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; }. -
nginx -t && systemctl restart nginx(a restart, not a reload: port 443 moves fromhttptostream).
Caddy sees the connections from nginx, not the client's address; the names behind Cloudflare had Cloudflare's address before anyway.
A new server name for Caddy must be added to the map, otherwise it
reaches nginx. Tools that rewrite nginx's configuration put listen 443
back and nginx then fails to start: after bench setup nginx (Frappe
helpdesk) or certbot --nginx for a new name, change their listen lines
as in step 4 again. certbot renew is not affected.
Headers
The Hub and Axis (under /axis/) are served with:
- HSTS (one year, including subdomains);
- a strict Content Security Policy;
X-Frame-Options: DENY,X-Content-Type-Options: nosniff;- Referrer and Permissions policies.
The documentation under /docs/ gets its own policy: it also allows
inline scripts and blob workers, which the static Docusaurus build and the
API reference need. The portals' policy is unchanged.
The files host sends Content-Security-Policy: default-src 'none'; sandbox, and uploads are always stored as application/octet-stream, so
nothing uploaded can run in a browser.
Agents and TLS inspection
Agents and connectors validate the server certificate against the
Windows trust store. A TLS-intercepting proxy on the customer network
must either be trusted by the machines or excluded for the server names (the address the device enrolled with, and the files host).
Symptom otherwise: x509: certificate signed by unknown authority in the
agent log.
WebSockets must pass through web proxies. If they are blocked, agents fall back to HTTPS polling (Troubleshooting).