Skip to content

Reverse proxy

The API terminates no TLS itself. A reverse proxy in front of it does — and the compose file is arranged so that the proxy is the only way in.

The topology

Nothing is published to the host. A published port would be reachable at the machine's raw IP, skipping the TLS your proxy terminates; instead the proxy reaches the container by name over a shared Docker network:

docker network create proxy-network   # once — or reuse your proxy's own network

Attach your proxy's container to that network, then forward to revoked-api:3000 (that is CONTAINER_NAME:API_PORT) over plain http — plain is correct here, the hop never leaves the Docker network.

Nginx Proxy Manager: forward hostname revoked-api, forward port 3000, scheme http. The NPM container itself must be attached to proxy-network, or the name will not resolve.

One proxy, not two

Point the proxy at the API directly, never at another proxy in front of it. Each proxy in a row appends to X-Forwarded-For, and the server reads the rightmost entry — so behind two proxies every request appears to come from the outer proxy, and all per-IP rate limits collapse into a single shared bucket.

Let rate limits see real IPs

One dashboard step after first boot: open /_/Settings → Application, set trusted proxy headers to X-Forwarded-For, leave "use leftmost IP" off. Without it the server sees your proxy's address on every request, and one noisy visitor exhausts everyone's budget.

No proxy on this host?

docker compose --profile proxy up -d

adds the bundled nginx on :80 — the only configuration that publishes a host port. TLS is a commented block in deploy/nginx/default.conf.template with the ACME challenge path already wired for certbot's webroot mode.

Lock the dashboard down

/_/ is the superuser dashboard, and it is reachable by anyone who finds your hostname. An access list on the proxy (allow your IPs, deny the rest for /_/) is the cheapest meaningful hardening on a public install.