Skip to content

Conversation

@jerry-yuan
Copy link

@jerry-yuan jerry-yuan commented Jan 31, 2026

…everse proxy scenarios

As mentioned in #5216, when Nginx is behind another proxy server (like CloudFlare or AWS ALB), the force-SSL feature can cause redirect loops because Nginx sees the connection as plain HTTP while SSL is already handled upstream. This adds a new boolean option to trust the X-Forwarded-Proto header from upstream proxies.

Changes:

  • Add trust_forwarded_proto column to proxy_host table (migration)
  • Update model and API schema to support the new boolean field
  • Modify force-ssl Nginx template to check X-Forwarded-Proto/X-Forwarded-Scheme
  • Add map directives in nginx.conf to validate and sanitize forwarded headers
  • Add advanced option toggle in frontend UI with i18n support (EN/ZH)
  • Set proxy headers from validated map variables instead of $scheme

This allows administrators to control SSL redirect behavior when Nginx is deployed behind a TLS-terminating proxy.

…everse proxy scenarios

When Nginx is behind another proxy server (like CloudFlare or AWS ALB), the force-SSL
feature can cause redirect loops because Nginx sees the connection as plain HTTP
while SSL is already handled upstream. This adds a new boolean option to trust
the X-Forwarded-Proto header from upstream proxies.

Changes:
- Add `trust_forwarded_proto` column to proxy_host table (migration)
- Update model and API schema to support the new boolean field
- Modify force-ssl Nginx template to check X-Forwarded-Proto/X-Forwarded-Scheme
- Add map directives in nginx.conf to validate and sanitize forwarded headers
- Add advanced option toggle in frontend UI with i18n support (EN/ZH)
- Set proxy headers from validated map variables instead of $scheme

This allows administrators to control SSL redirect behavior when Nginx is deployed
behind a TLS-terminating proxy.
@jerry-yuan jerry-yuan changed the title Add trust_forwarded_proto option for SSL redirect handling in r… WIP: Add trust_forwarded_proto option for SSL redirect handling in r… Feb 1, 2026
@jerry-yuan jerry-yuan changed the title WIP: Add trust_forwarded_proto option for SSL redirect handling in r… Add trust_forwarded_proto option for SSL redirect handling in r… Feb 1, 2026
@jerry-yuan jerry-yuan marked this pull request as draft February 3, 2026 07:07
@jerry-yuan jerry-yuan closed this Feb 3, 2026
@jerry-yuan jerry-yuan reopened this Feb 3, 2026
@jerry-yuan jerry-yuan marked this pull request as ready for review February 3, 2026 07:09
@jerry-yuan
Copy link
Author

@jc21 May I ask you for a code review?

@jc21 jc21 added the requires-verification Waiting for one or more people to confirm the fix label Feb 4, 2026
@jc21
Copy link
Member

jc21 commented Feb 4, 2026

Yep I'll see how I go today

@MansourM
Copy link

MansourM commented Feb 9, 2026

will this also fix
#4262
?
i've been manually fixing my configs so far

@jerry-yuan
Copy link
Author

will this also fix
#4262
?
i've been manually fixing my configs so far

Yes, we are working on a similar fix. My changes primarily address the loop redirect issue when a proxied NPM instance has Force SSL enabled. To ensure compatibility with multi-layer nested scenarios, I've also incidentally fixed the propagation of the X-Forwarded-Scheme and X-Forwarded-Proto headers.

@jc21 Hey, looks like I'm not the only one with this kind of issue 😁

@jc21
Copy link
Member

jc21 commented Feb 10, 2026

Ok so I've been testing and

  • 👍 works for a fresh instance
  • 👎 not working when used on an existing instance with existing proxy hosts
❯ Starting nginx ...
nginx: [emerg] unknown "trust_forwarded_proto" variable

My comment about quoting the comparision should eleviate this error, just has to be tested.

@jerry-yuan
Copy link
Author

@jc21 Thank you for your Code Review. Following your feedback, I have fixed the conditional statement for trust_forwarded_proto. Additionally, I added a check to verify if the trust_forwarded_proto variable is defined before using it. If it is not defined, I now assign it a default value of "F". I also locally validated the compatibility between older configuration files (those missing the trust_forwarded_proto definition) and the new software version:

  1. Started a fresh NPM instance.
  2. Created two virtual hosts with Force SSL enabled.
  3. Entered the Docker container and manually removed the trust_forwarded_proto definition from /data/nginx/1.conf to simulate an older configuration.
  4. Reopened the configuration window for the second virtual host and saved it to trigger an Nginx reload.
  5. Observed any NPM errors and checked the logs of the npm2dev.core container.

I found that even after changing if ($trust_forwarded_proto = T) to if ($trust_forwarded_proto = "T"), I still couldn’t prevent Nginx from throwing errors. Therefore, I decided to check whether the $trust_forwarded_proto variable is defined and initialize it before using it (as implemented in the diff here: https://github.com/NginxProxyManager/nginx-proxy-manager/pull/5260/changes#diff-25174e53424655eff165a58345aedd0fb61a56c4c01bd22a47e889b20caa7cb4R11). After adding this initialization check, I observed that Nginx no longer reported errors.

I am curious about how you set up existing instances with proxied hosts in your development environment. If possible, I would appreciate learning your method so I can more comprehensively test such scenarios.

@nginxproxymanagerci
Copy link

Docker Image for build 8 is available on DockerHub:

nginxproxymanager/nginx-proxy-manager-dev:pr-5260

Note

Ensure you backup your NPM instance before testing this image! Especially if there are database changes.
This is a different docker image namespace than the official image.

Warning

Changes and additions to DNS Providers require verification by at least 2 members of the community!

@jc21
Copy link
Member

jc21 commented Feb 11, 2026

I am curious about how you set up existing instances with proxied hosts in your development environment. If possible, I would appreciate learning your method so I can more comprehensively test such scenarios.

I simply deploy the image in question to one of my production instances, after making a backup of the data, of course.

@jc21
Copy link
Member

jc21 commented Feb 11, 2026

However before that last step I would get the PR, run ./scripts/start-dev

This is good enought for most dev testing but if I need to test SSL there are other tweaks required depending on the case.

@jc21
Copy link
Member

jc21 commented Feb 11, 2026

ok so I've got this latest image working. However..

  • https://yum.jc21.com/
  • Hosted with Cloudflare, proxied through to my server
  • This is forced ssl, but without your new advanced option enabled

I expect to see the redirect loop? Instead, it's working as if the advanced option was enabled.

Screenshot 2026-02-11 at 10 49 32 Screenshot 2026-02-11 at 10 46 18

Generated proxy config:

# ------------------------------------------------------------
# mantine.jc21.com, public.jc21.com, yum.jc21.com
# ------------------------------------------------------------

map $scheme $hsts_header {
    https   "max-age=63072000;includeSubDomains; preload";
}

server {
  set $forward_scheme http;
  set $server         "172.17.0.1";
  set $port           8800;

  listen 80;
  listen [::]:80;
  listen 443 ssl;
  listen [::]:443 ssl;

  server_name mantine.jc21.com public.jc21.com yum.jc21.com;

  http2 on;

  # Let's Encrypt SSL
  include conf.d/include/letsencrypt-acme-challenge.conf;
  include conf.d/include/ssl-cache.conf;
  include conf.d/include/ssl-ciphers.conf;
  ssl_certificate /etc/letsencrypt/live/npm-13/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/npm-13/privkey.pem;

  # Block Exploits
  include conf.d/include/block-exploits.conf;

  # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
  add_header Strict-Transport-Security $hsts_header always;

  # Force SSL
  set $trust_forwarded_proto "F";
  include conf.d/include/force-ssl.conf;

  access_log /data/logs/proxy-host-44_access.log proxy;
  error_log /data/logs/proxy-host-44_error.log warn;

  location / {
    # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
    add_header Strict-Transport-Security $hsts_header always;

    # Proxy!
    include conf.d/include/proxy.conf;
  }

  # Custom
  include /data/nginx/custom/server_proxy[.]conf;
}

Cloudflare SSL Settings:

Screenshot 2026-02-11 at 10 51 52

@jerry-yuan
Copy link
Author

@jc21 Thank you for your reply. I think I understand what's happening now. As illustrated in your screenshot, when you select Full (strict) or Full mode, Cloudflare enables SSL encryption on both segments: between the Client and Cloudflare, and between Cloudflare and your origin server (NPM). In this case, the request received by the origin server is over HTTPS, which satisfies the condition for Force SSL, thus no redirect response is generated.

You could try changing Cloudflare's SSL setting to Flexible, where SSL is only enforced between the Client and Cloudflare, while HTTP is used between Cloudflare and your origin server. In this scenario, your origin server will receive an HTTP request carrying the X-Forwarded-Proto header. I believe this should trigger the redirect loop.

I'd like to emphasize that using HTTP to connect to your origin over the public internet (an untrusted network environment) is indeed not a good practice, so your choice of Full mode is reasonable. The newly added configuration primarily aims to support HTTP connections to the origin within internal networks (a controlled environment), while incidentally supporting Cloudflare's Flexible mode.

@jerry-yuan
Copy link
Author

I am curious about how you set up existing instances with proxied hosts in your development environment. If possible, I would appreciate learning your method so I can more comprehensively test such scenarios.

I simply deploy the image in question to one of my production instances, after making a backup of the data, of course.

Direct testing in a production environment is indeed a bit daunting. I understand now. I plan to clone my production environment configuration and data to test the PR image as well.

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

Labels

requires-verification Waiting for one or more people to confirm the fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants