Search code examples
pythondjangonginxuwsgi

Difference between uwsgi_param and proxy_set_header


When using Nginx as a reverse proxy with uWSGI/Django, what is the difference between uwsgi_param and proxy_set_header in an Nginx configuration? Is a uWSGI parameter like an HTTP header, or is it completely different, and if so, what is its purpose?


Background: I'm doing some tinkering around with security-related HTTP headers in Django. I have a setup using Nginx as reverse proxy, with the uWSGI serving the Django app and being the proxied server:

                           _____________________________________
                           |                                    |
            http or https* |           uwsgi                    |
   browser --------------> | nginx --------------> uWSGI/Django |
                           |____________________________________|

* http 301-redirects to https equivalent;
  https response returns Strict-Transport-Security header

There are two mechanisms by which http requests 'become' https requests here:

  1. Nginx redirects port 80 requests to 443, e.g. the request history chain has a 301 redirect
  2. HTTPs responses contain a Strict-Transport-Security: max-age=31536000; includeSubDomains; preload response header; in this case, there is never a 302 redirect; the browser takes the client http request and forces it to its https equivalent right off the bat.

All that is to say, the related Django settings look like:

# tail -n4 project/settings.py
SECURE_HSTS_SECONDS = 31536000  # Seconds; *USE A SMALLER VALUE FOR TESTING FIRST!*
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

And Nginx config:

server {
    server_name .mydomain.com;
    listen 80;
    return 301 https://$host$request_uri;
}

server {
    location / {
        uwsgi_pass localhost:8000;
        include uwsgi_params;
        uwsgi_param X-Forwarded-Proto "https";
        proxy_set_header X-Forwarded-Proto "https";
    }
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

So, when the Django app gets word that the original connection was HTTPs via SECURE_PROXY_SSL_HEADER, does it have uwsgi_param or proxy_set_header to thank? Is proxy_set_header still actually used because the protocol is uwsgi rather than proxy_pass: http://localhost:8000? What does a uwsgi_param do? I see very little in the protocol description. Does it behave like an HTTP header, or is it totally different?


Solution

  • Yes, thanks to uwsgi_param or proxy_set_header HTTP_X_Forwarded_Proto header is set (otherwise it won't be present) and django app (working via http behind https proxy) can know, that original request was secure (via https).


    Nginx forwards initial http request to underlying upstream server. For this it may use different protocols - uwsgi if uwsgi_pass directive is set or http if proxy_pass directive is set. Only one of them needs to be set in block.

    By default nginx forwards all original request headers to upstream, which is controlled by proxy_pass_request_headers and uwsgi_pass_request_headers options. With proxy_set_header or uwsgi_param headers and their values can be set / added explicitly.

    With proxy_pass - request is forwarded as HTTP request to upstream server. And with proxy_set_header headers and their value to be passed can be set.

    With uwsgi_pass request is forwarded via uwsgi binary protocol. It is not http, it has no 'headers', instead it has parameters to be passed by uwsgi_param (if parameter name is prefixed with HTTP_ - it is available as a header in wsgi app).

    Uwsgi is native for wsgi servers (but most can work via http as well) and allows more fine-tuning of how request can be processed by wsgi server with parameters passed. And with configuration can be more performant. However the difference may be very subtle.

    Several cases when http is desired (and main reason is its universality):

    • for internal communication between services directly
    • you may want to use or try other https proxy other than nginx (and today there are many) and it may not support uwsgi
    • with microservice approach - there may be other proxies / sidecars between nginx and uwsgi (for authentication, logging, etc) and they will work only with http