Skip to content

forwarded headers support for reverse proxy deployments - #10016

Open
Mohammed-suhail-devops wants to merge 8 commits into
nginx:mainfrom
Mohammed-suhail-devops:feature/forwaded-host
Open

forwarded headers support for reverse proxy deployments#10016
Mohammed-suhail-devops wants to merge 8 commits into
nginx:mainfrom
Mohammed-suhail-devops:feature/forwaded-host

Conversation

@Mohammed-suhail-devops

@Mohammed-suhail-devops Mohammed-suhail-devops commented May 26, 2026

Copy link
Copy Markdown

Fixes #10015

Use Case

When the NGINX Ingress Controller is deployed behind reverse proxies (Azure Application Gateway, AWS ALB, etc.), the original Host header is modified to an internal service name, but the original hostname is preserved in the X-Forwarded-Host header. Currently, the controller always uses $host which contains the modified hostname, causing backend applications to reject requests (403/404) or generate incorrect URLs.

Changes

This PR adds logic to prioritize the X-Forwarded-Host header (when present) over the standard $host variable, with a safe fallback.

Implementation:

  • Introduces $best_http_host variable with conditional logic
  • Uses X-Forwarded-Host when present (original hostname from proxy)
  • Falls back to $host when X-Forwarded-Host is absent or empty
  • Modified both nginx.ingress.tmpl (OSS) and nginx-plus.ingress.tmpl (Plus)

NGINX Configuration Added:

set $forwarded_host $host;
if ($http_x_forwarded_host != "") {
    set $forwarded_host $http_x_forwarded_host;
}
proxy_set_header Host $forwarded_host;
proxy_set_header X-Forwarded-Host $forwarded_host;

@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

✅ All required contributors have signed the F5 CLA for this PR. Thank you!
Posted by the CLA Assistant Lite bot.

@github-actions github-actions Bot added the enhancement Pull requests for new features/feature enhancements label May 26, 2026
@Mohammed-suhail-devops

Copy link
Copy Markdown
Author

I have hereby read the F5 CLA and agree to its terms

@suhail-blueocean

Copy link
Copy Markdown

Hi @spencerugbo, could you please check this pr we need this change as soon as its blocked the whole migration of us.

@suhail-blueocean

Copy link
Copy Markdown

Hi @javorszky @vepatel, please look into this pr we need this change

@AlexFenlon AlexFenlon added the community Issues or PRs opened by an external contributor label Jul 9, 2026
@AlexFenlon

Copy link
Copy Markdown
Contributor

Hi @suhail-blueocean, we plan on addressing this hopefully later in this year (see #7555)

We do like what is here but there is still a lot of discussion internally about related things. We will credit you if we end up taking in some/all of your PR

Thanks

@vepatel

vepatel commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

hey @Mohammed-suhail-devops #10539 might supersede this PR as its covers VirtualServer as well

@Mohammed-suhail-devops

Copy link
Copy Markdown
Author

Hi @vepatel
Thanks for the update and the work on this enhancement.

After reviewing the implementation, I don't believe this change resolves the issue described in this enhancement request.

The current implementation adds support for overriding the Host header through the nginx.org/proxy-set-headers annotation and avoids generating a duplicate proxy_set_header Host $host; when a custom Host header is already configured. However, the default behaviour remains unchanged. The generated configuration still falls back to:

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;

when no explicit Host override is provided.

The problem described in this issue is different.

In deployments behind Azure Front Door, F5, CDNs, or other reverse proxies, the original client hostname is already available in the incoming request as X-Forwarded-Host. However, the ingress controller continues to use $host, which represents the ingress/origin hostname, rather than the original public hostname.

For example:

Incoming request to NGINX

Host: secondary-origin.internal.com
X-Forwarded-Host: public-domain.com

Current behaviour:

proxy_set_header Host $host;

Backend receives:

Host: secondary-origin.internal.com

Expected behaviour:

Host: public-domain.com

The newly added annotation allows users to manually configure:

proxy_set_header Host $http_x_forwarded_host;

on every Ingress, but this does not solve the underlying problem. It simply provides a manual workaround.

The enhancement requested here is for the controller to automatically preserve the original client-facing host by preferring X-Forwarded-Host when it is already present, while safely falling back to $host when it is not. That would avoid requiring custom annotations on every Ingress and provide consistent behaviour across DR and multi-origin deployments.

So, while this change improves configurability, I don't believe it addresses the issue reported in this enhancement request.

One thing I'd like to highlight is that the VirtualServer implementation already provides a mechanism to override the default X-Forwarded-Host behaviour:

{{- if not ($custom_headers | hasCIKey "X-Forwarded-Host") }}
{{ $proxyOrGRPC }}_set_header X-Forwarded-Host $host;
{{- end }}

With this implementation, the default X-Forwarded-Host is only generated when the user has not already configured one through custom_headers. If a custom X-Forwarded-Host is supplied, the template respects that value instead of forcing $host.

This makes it possible for VirtualServer users to preserve the original client-facing hostname by configuring:

X-Forwarded-Host: $http_x_forwarded_host

instead of always sending:

X-Forwarded-Host: $host

In comparison, the Ingress template currently generates:

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;

by default, which causes the original hostname to be replaced with the ingress/origin hostname whenever traffic passes through multiple proxy layers.

My request is to provide similar behaviour for Ingress. If an incoming X-Forwarded-Host is already available from trusted upstream infrastructure such as Azure Front Door, F5, or a CDN, the Ingress controller should be able to preserve that value instead of always using $host.

This would make the behaviour consistent between VirtualServer and Ingress and would solve the host preservation issue for DR and multi-origin deployments without requiring manual per-Ingress workarounds.

@haywoodsh

haywoodsh commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Hi @Mohammed-suhail-devops, thanks for explaining the use case.

We have renamed the ConfigMap key in #10539 to disable-forwarded-headers and close #7555. With this you should be able to achieve some of the logic with snippets, without the issue of repeated proxy_set_header directives.

That said, you would still not be able to set the Host header to another variable without a custom template.

I have reopened #10015 to track this specific gap, and we aim to deliver a complete solution for this with the milestone set to 5.7.0.

On this PR specifically, rather than changing the native behaviour, I think it would be better to have an opt-in ConfigMap key use-forwarded-headers, which would also help users migrating from ingress-nginx.

Because of the ongoing work in #10557 and #10539 scheduled for 5.6.0, the template could look like this. It's a rough draft and needs testing:

{{- if $location.UseForwardedHeaders}}
set $forwarded_host $host;
if ($http_x_forwarded_host != "") { 
    set $forwarded_host $http_x_forwarded_host; (will need to take first element instead of whole list)
}
set $forwarded_port $server_port;
if ($http_x_forwarded_port != "") {
    set $forwarded_port $http_x_forwarded_port;
}
set $forwarded_proto {{if $server.RedirectToHTTPS}}https{{else}}$scheme{{end}};
if ($http_x_forwarded_proto != "") {
    set $forwarded_proto $http_x_forwarded_proto;
}
{{- else}}
set $forwarded_host $host;
set $forwarded_port $server_port;
set $forwarded_proto {{if $server.RedirectToHTTPS}}https{{else}}$scheme{{end}};
{{- end}}
{{- if $location.UpstreamVhost}}
proxy_set_header Host {{$location.UpstreamVhost}};
{{- else}}
proxy_set_header Host $forwarded_host;
{{- end}}
{{- if not $location.DisableForwardedHeaders}}
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $forwarded_host;
proxy_set_header X-Forwarded-Port $forwarded_port;
proxy_set_header X-Forwarded-Proto $forwarded_proto;
{{- end}}

Would something like this fix your use case? Let me know what you think. I'll track the details in #10015.

Thanks again for raising this. The use case is valid and we want to support it natively.

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

Labels

community Issues or PRs opened by an external contributor enhancement Pull requests for new features/feature enhancements

Projects

Status: Todo ☑

Development

Successfully merging this pull request may close these issues.

Preserve original host in multi-origin / DR environments when X-Forwarded-Host is unavailable

5 participants