Django SECURE_PROXY_SSL_HEADER requires header stripping
Django can decide that a request is secure even when the connection to Django itself uses HTTP. The SECURE_PROXY_SSL_HEADER setting names a request header and the value that represents HTTPS. This is intended for a site behind a trusted proxy that terminates TLS. The setting is not proof of TLS by itself.
The Django documentation is explicit about the caveat: the header must be stripped from all client requests by the proxy, and the application must not be directly reachable. If an attacker can send the header to Django, Django may treat an ordinary HTTP request as secure. That can affect whether secure cookies are sent or set, whether HTTPS redirects stop, and how other security-sensitive request properties are evaluated.
What the setting means
A proper review follows the request from the edge to the application. First, identify the proxy that terminates TLS. Second, verify that it removes any incoming value for the configured header before adding its own. Third, confirm that the application bind address or network policy blocks direct HTTP access. A firewall rule that allows the whole private network is weaker than a rule limited to the proxy's address and port.
Proxy and network review
The setting format matters as well. The configured header name should match what the proxy actually sends, and the expected value must be exact. During a migration, two proxies may use different names or values. A temporary compatibility setting can leave one path accepting a header from the wrong layer. Document which proxy owns the header and remove obsolete compatibility rules.
Test matrix
Test cases should include:
- An HTTPS request through the trusted proxy.
- An HTTP request through the trusted proxy.
- A direct request with the secure-looking header attached.
- A direct request without the header.
- A request through a second proxy or ingress that does not strip the header.
Conservative deployment
Inspect the resulting request.is_secure() decision, not only the visible redirect. Cookies marked Secure, HSTS behavior, form validation, and application logic may all depend on that value. If the application is reachable through more than one ingress, every ingress must enforce the same policy or be kept out of the application's network path.
The setting also interacts with host validation. A proxy may supply both protocol and host information, and an attacker may try to combine a trusted protocol header with an untrusted host header. Review ALLOWED_HOSTS, forwarded-host handling, and generated links together. The edge should set known values rather than copying arbitrary client metadata.
A conservative deployment uses one TLS-terminating proxy, strips the chosen header from clients, adds a fixed value, and exposes Django only to that proxy. The application can then use the setting as a deliberate assertion about the network path. Without those controls, the setting converts an attacker-controlled request header into a security conclusion.
SECURE_PROXY_SSL_HEADER is valuable in a correctly designed proxy topology. Its safety depends on the proxy being the only source of the header and the backend being unreachable from anywhere else.
References
- Django settings reference,
SECURE_PROXY_SSL_HEADER: https://docs.djangoproject.com/en/6.1/ref/settings/#secure-proxy-ssl-header - Django security topic guide: https://docs.djangoproject.com/en/6.1/topics/security/
Top comments (0)