Skip to content

Forward client Host (with port) and scheme in reverse-proxy templates#81

Open
mikl wants to merge 1 commit into
mainfrom
fix/forward-host-with-port-and-scheme
Open

Forward client Host (with port) and scheme in reverse-proxy templates#81
mikl wants to merge 1 commit into
mainfrom
fix/forward-host-with-port-and-scheme

Conversation

@mikl

@mikl mikl commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Problem

The reverse-proxy templates use proxy_set_header Host $host and don't forward the request scheme.

  • $host normalises the Host header and drops any non-standard port.
  • No X-Forwarded-Proto is sent, so the backend can't tell the public scheme.

When the proxy is published on a non-standard host port (e.g. https://app.local:37103), any backend that builds absolute URLs from the request headers — Drupal, Next.js, etc. — generates them as https://app.local/... (i.e. port 443). Those links then 404 because the app is only reachable on :37103.

Root cause: Docker NATs the published port down to the container's :443, so the only place the real public port survives is the client's Host header — which $host discards.

Fix

  • proxy_set_header Host $http_host — preserves the exact Host the client sent, including the port. ($host:$server_port wouldn't work: $server_port is nginx's listen port, 443, not the client's port.)
  • proxy_set_header X-Forwarded-Proto $scheme — forwards the real scheme. The server block listens on both 80 and 443, so $scheme is correct either way.

Applied to all four reverse-proxy variants: proxy, nextjs, storybook, vite. The drupal variant is unaffected — it serves via fastcgi and already passes HTTP_HOST/HTTPS from the connection.

Testing

Verified against the proxy variant in a dev stack published on :37103: a request-derived redirect that previously returned Location: http://app.local/frontpage now correctly returns https://app.local:37103/frontpage, and Drupal-generated login/image URLs carry the right scheme and port. The other three variants take the identical change to the same directive.

🤖 Generated with Claude Code

The reverse-proxy templates set `proxy_set_header Host $host`, which
normalises the Host header and drops any non-standard port, and they
never forward the request scheme. Downstream apps that build absolute
URLs from the request (e.g. Drupal, Next.js) therefore emit URLs with
the wrong port and scheme when the proxy is published on a non-standard
host port such as https://app.local:37103 — the generated links point at
https://app.local (port 443) and 404.

Use $http_host to preserve the exact Host the client sent (including the
port), and forward X-Forwarded-Proto $scheme so the backend knows the
public scheme. Both listen ports (80/443) share one server block, so
$scheme is correct for either. Applied to all four reverse-proxy
variants (proxy, nextjs, storybook, vite); the drupal variant already
passes HTTP_HOST/HTTPS via fastcgi_params.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mikl mikl requested a review from a team as a code owner July 1, 2026 22:30
@mikl

mikl commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

The main difference, $http_host has the value of the Host header sent by the browser, where $host is derived from nginx configuration. (docs).

For our use-case, the former is better, since we shouldn't have anything accessing the proxy that does not send a proper Host-header.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant