Skip to content

Support X-Forwarded-Host in ProxyHeadersMiddleware#2922

Open
Kludex wants to merge 2 commits intomainfrom
x-forwarded-host
Open

Support X-Forwarded-Host in ProxyHeadersMiddleware#2922
Kludex wants to merge 2 commits intomainfrom
x-forwarded-host

Conversation

@Kludex
Copy link
Copy Markdown
Owner

@Kludex Kludex commented Apr 28, 2026

Closes #965

Summary

Adds X-Forwarded-Host support to ProxyHeadersMiddleware. When the connecting peer is in forwarded_allow_ips and the header is present, the middleware:

  • Replaces the host request header in scope["headers"] with the forwarded value
  • Sets scope["server"] to (host, port), parsing IPv4/IPv6/host:port via the existing _parse_host_port helper used for X-Forwarded-For
  • Defaults the port to 443 when the (possibly already-rewritten) scope["scheme"] is https or wss, and 80 otherwise

This matches the behavior of Werkzeug's ProxyFix (linked by @Kludex in the issue as the reference) and addresses the redirect_slashes and StaticFiles redirect breakage that surfaces when a reverse proxy rewrites Host to its internal hostname (e.g., Caddy 2.11+, Heroku, many CDNs).

Behaviour

  • Trust gating reuses the existing forwarded_allow_ips check; no new CLI flag.
  • Empty / whitespace-only X-Forwarded-Host is a no-op.
  • The order in the middleware is proto -> for -> host, so the host block can use the forwarded scheme to pick the default port.
  • The original Host header is replaced (not appended) - matches Werkzeug and Hypercorn.

Tests

New cases cover untrusted-peer ignore, hostname / host:port / IPv4 / IPv4+port / bracketed IPv6 / IPv6+port shapes, scheme-driven default ports for http/https/ws/wss, host-header replacement (no duplicates), and the combined for+proto+host case.

Checklist

  • I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.

AI Disclaimer

This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 28, 2026

📖 Docs preview: https://1adee858.uvicorn.pages.dev

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 28, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing x-forwarded-host (a162bc9) with main (10ddc6d)

Open in CodSpeed

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f68cc4f524

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +58 to +62
x_forwarded_host = headers[b"x-forwarded-host"].decode("latin1").strip()

if x_forwarded_host:
host, port = _parse_host_port(x_forwarded_host)
if not port:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse only one X-Forwarded-Host hop before rewriting host

When a trusted proxy sends a multi-hop X-Forwarded-Host value (comma-separated, which is common with chained proxies), this code parses the entire header as one host instead of selecting a single hop first. That means scope["server"] can be set to an invalid hostname like "example.com,proxy.local" (or lose a valid port in IPv6+port chains), and the rewritten Host header will also contain the full list, which can break URL generation/redirect behavior in proxied production setups. Split the header into hops (and choose the intended trusted hop) before calling _parse_host_port.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support X-Forwarded-Host in proxy-headers

1 participant