Skip to content

HTTPS and reverse proxy

View as Markdown

The web container listens on plain HTTP (port 8080 by default). Put a reverse proxy in front of it to terminate TLS. Everything goes through web: the proxy needs a single upstream and never talks to api or gateway directly.

Path Traffic Proxy requirement
/ Static interface Nothing special.
/api/ REST API and server-sent events (live progress of enrolments and jobs) Do not buffer responses. Allow idle connections of at least 1 hour.
/gateway/ WebSocket of the web terminal Pass the Upgrade and Connection headers. Allow connections of up to 9 hours (the gateway closes idle sessions after 15 minutes and any session after 8 hours).

Also:

  • Set INFRAWARD_PUBLIC_URL in .env to the exact public URL, for example https://infraward.example.com, and run docker compose up -d after changing it. The API checks the Origin header of write requests against it and the enrolment command is built from it.
  • Bind web to the loopback interface (INFRAWARD_HTTP_PORT=127.0.0.1:8080) when the proxy runs on the same host, so port 8080 is not reachable from the network.
  • Send X-Forwarded-For and X-Forwarded-Proto, and set INFRAWARD_TRUSTED_PROXY in .env to the proxy’s IP address (or CIDR) as the web container sees it. The web container only believes those headers when they come from that address, and passes a single client IP to the API and the gateway. The audit log, the login rate limits and the terminal sessions use it. Until you set it, they record the proxy’s IP. See Find the proxy’s address.
  • Do not write /api/enroll/ requests to the proxy’s access log: the single-use enrolment token travels in the path. The web container already excludes them from its own log.
  • The web container already sends X-Frame-Options, Content-Security-Policy: frame-ancestors 'none', Strict-Transport-Security, X-Content-Type-Options and Referrer-Policy. Do not add them again in the proxy.

Caddy obtains and renews the certificate automatically and handles WebSocket upgrades without extra settings.

/etc/caddy/Caddyfile
infraward.example.com {
# Caddy does not write access logs unless you enable the log directive.
# If you do, exclude /api/enroll/*: the enrolment token is in the path.
reverse_proxy 127.0.0.1:8080 {
# Server-sent events: send each event as soon as it arrives.
flush_interval -1
}
}

The value of INFRAWARD_TRUSTED_PROXY is the address the connections come from inside the container, which is not always the one you expect:

  • Proxy on the same host (Caddy or nginx connecting to 127.0.0.1:8080): with Docker it is usually the gateway of the Compose network, not 127.0.0.1.
  • Proxy in a container (Traefik): the IP of the proxy container on the network it shares with web, or the CIDR of that network if only proxy containers are attached to it.

To find it, open InfraWard through the proxy and read the source address of the requests in the log of web:

Terminal window
docker compose logs --tail 5 web

Set it in .env and recreate web:

.env
INFRAWARD_TRUSTED_PROXY=172.18.0.1
Terminal window
docker compose up -d web

After that, the same log shows the real client IPs. The default (127.0.0.1) trusts no proxy. Never use a range that clients can connect from directly: anyone in it could choose the IP that appears in the audit log.

  1. Open https://infraward.example.com and sign in. If the login page loads but signing in returns you to it, the session cookie is being dropped: check that the site is served over HTTPS.
  2. Enrol a server or start a scan and watch the progress page. If the steps only update when you reload, the proxy is buffering /api/.
  3. Open a terminal on a server with administration access. If it shows Se ha perdido la conexión con el gateway (Lost connection to the gateway) right away, the proxy is not forwarding the WebSocket upgrade on /gateway/.
  4. Look at your sign-in in Ajustes → Auditoría (Settings → Audit). If it shows the proxy’s IP instead of yours, INFRAWARD_TRUSTED_PROXY is unset or does not match the proxy.