Add trust_forwarded_proto option for SSL redirect handling in r…#5260
Add trust_forwarded_proto option for SSL redirect handling in r…#5260jc21 merged 7 commits intoNginxProxyManager:developfrom
Conversation
…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.
|
@jc21 May I ask you for a code review? |
|
Yep I'll see how I go today |
|
will this also fix |
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 😁 |
|
Ok so I've been testing and
My comment about quoting the comparision should eleviate this error, just has to be tested. |
…eady created old virtual hosts
|
@jc21 Thank you for your Code Review. Following your feedback, I have fixed the conditional statement for
I found that even after changing 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. |
|
Docker Image for build 8 is available on DockerHub: Note Ensure you backup your NPM instance before testing this image! Especially if there are database changes. Warning Changes and additions to DNS Providers require verification by at least 2 members of the community! |
I simply deploy the image in question to one of my production instances, after making a backup of the data, of course. |
|
However before that last step I would get the PR, run This is good enought for most dev testing but if I need to test SSL there are other tweaks required depending on the case. |
|
ok so I've got this latest image working. However..
I expect to see the redirect loop? Instead, it's working as if the advanced option was enabled.
Generated proxy config: Cloudflare SSL Settings:
|
|
@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. |
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. |
|
Ok yeah of course...
Yes totally. These days, I need to have every confidence that things "work for me" before so many others also get the release. I've been burnt too much |
|
I've changed to flexible, I see the loop happening and enabling your flag is working as expected. Great! |
|
I'm glad that my work has received your approval. I might consider adding an explanation of the new feature in the documentation later on.🥳🥳🥳 |
|
Hi @jerryzone / @jerry-yuan / @jc21 ! Thank you for implementing this feature - it addresses a real problem with SSL redirect loops in reverse proxy scenarios. The implementation looks well thought out, especially the header validation using map directives with whitelist values. I wanted to share a couple of thoughts that might be worth considering for documentation and future enhancements: 1. Documentation for safe usage 2. Additional hardening possibility Overall, great work on solving the redirect loop issue! These are just some ideas that might help make the feature even more robust for production environments. Thanks again for your contribution! I also noticed the bot warnings regarding security regressions and custom locations. Since this is already merged, it might be critical to address the X-Forwarded-Proto spoofing possibility (CVE potential?) in a follow-up fix, especially for users not using Cloudflare/trusted proxies.
|
|
@toviszsolt I'm glad that my work has received your recognition. Regarding the future plans you mentioned, here are my thoughts:
After the code was merged into develop, I immediately looked at the current NPM documentation and started working on adding documentation for the new feature. However, I noticed that the existing Q&A and configuration documentation seem to be mostly introductory. Since my feature is only necessary in fairly complex use cases, I couldn't find a suitable section to place the description. If you think there's an appropriate place for it, I'd be happy to contribute the relevant documentation.
I believe this feature is necessary and had initially designed something similar. However, @jc21 emphasized that the project's UI/UX philosophy is to keep things as simple and user-friendly as possible. I considered my feature itself to be an advanced, low-frequency-use feature, so I trimmed down the whitelist functionality and placed it within the collapsed advanced options menu by default. If a whitelist feature is to be added in the future, it may require a reassessment of its priority and design. |
Since @jc21 knows the database structure and UI, and what's connected to what, we'll ask him if he can comment and if he has any ideas about this. |
|
These changes appear to have broken existing configurations where a public-facing NPM instance that handle TLS termination then proxies requests to internal NPM instances over HTTP; headers that were previously trusted before are no longer, and it's rather unclear to me how these changes were intended to be addressed or accommodated by admins with existing configurations. In my case the external NPM instance should not trust upstream headers as it is exposed online so I have left the new setting disabled. However that same setting is for some reason disabled in the UI of the internal NPM instance which is front of the application, it only deals with HTTP and instead trusts the There are several comments towards the end here that mention writing documentation about how this feature works and was implemented; that would indeed be massively appreciated as it appears I was not the only admin who had issues after the implementation of this feature. FWIW I am still debugging and attempting to resolve my header issues live. I am happy to share logs or more details about my set up, but will eventually issue a rollback if 2.14.0 is too broken to work with. Finally,
I would very enthusiastically support this feature, IMHO proxy-behind-a-proxy is not a particularly uncommon or user-unfriendly configuration. |
|
@acenomad Indeed, as you said, this change reverted a default feature introduced in the last version of 2.13. In that version, to quickly resolve my issue, @jc21 added the modification to trust upstream forwarded headers in the force‑SSL configuration and released a version before this change was merged. For security reasons, I reverted that change—more precisely, I added a toggle to make it opt‑in rather than enabled by default. This change essentially deals with the behavior of the forced HTTPS redirect. When Force SSL is disabled, the entire file containing this change should be removed from the nginx configuration, so the toggle no longer has any effect—it’s reasonable that it appears disabled on HTTP virtual hosts. From what I’ve seen so far, most of the reported issues stem from users mounting and modifying nginx.conf, which prevents it from being updated when the container is upgraded. In fact, NPM provides a way to customize the core configuration file—the relevant documentation is here: https://nginxproxymanager.com/advanced-config/#custom-nginx-configurations Coming back to your problem: if you could describe your overall topology and the specific symptoms you’re experiencing in more detail, I’d be happy to help resolve the issues introduced by my change. |
|
Hi @jerry-yuan thanks for the extra context! Yesterday I spent a couple hours trying to debug why some of my apps suddenly stopped authenticating properly while all other traffic otherwise behaved normally. Initially I had thought it was a result of a recent Authentik update but I eventually narrowed it down to headers not being passed properly between proxies, in particular After unsuccessfully troubleshooting for some time I rolled back to a previous host snapshot and called it a night. Picking things up again today I ran an update of the NPM containers and... nothing broke 😅 I think it's left me even more confused than previously because there are functionally no differences between the environment, nor I believe any container updates pulled between today and yesterday. I don't directly modify At this stage my best guess is, as you suggest, still probably related to something that was prevented from updating. I wouldn't yet be able to tell you what, unfortunately. Either way, seems my issue is resolved. Thanks for your contribution and assistance :) edit: typo |



…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:
trust_forwarded_protocolumn to proxy_host table (migration)This allows administrators to control SSL redirect behavior when Nginx is deployed behind a TLS-terminating proxy.