CSRF validation failure due to https

I am testing on our staging server and am getting CSRF failures when trying to log in our proxy server or any CNAME pointing to it. The response is 403 Forbidden. This is the message I get when setting DEBUG=True:

Referer checking failed - xhttp://www.app.com/accounts/login/?next=/ does not match xhttps://www.app.com/.

(“xhttp” is because the forum won’t let me post more than “2 links”… even though they’re not real links)

This is the nginx configuration:

upstream app_server {
    server unix:/home/app/run/gunicorn.sock fail_timeout=0;
}

# proxy server to the django app server
server {
    server_name proxy.staging.app.com;
    listen 80 default_server;

    location / {
        include proxy_params;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_pass https://staging.app.com:8080;
    }
}

# django app server
server {
    server_name staging.app.com;
    listen 80;
    listen 8080 ssl;
    listen 443 ssl; 
    #  ssl certificates...

    location / {
        include proxy_params;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_pass http://unix:/home/app/run/gunicorn.sock;
    }
}

# https redirect 
server {
    server_name staging.app.com;
    listen 80;
    return 301 https://$host$request_uri;
}

The http request is made to the proxy at proxy.staging.app.com which proxy passes with a https request to the block where the Django app runs (well, where it proxies to the socket running it).

As you can see, proxy headers are included so that the Django app knows the original host, scheme, port, etc. Relevant settings are also set:

USE_X_FORWARDED_HOST = True
USE_X_FORWARDED_PORT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

The reason CSRF validation fails seems to be that the addresses don’t match because of the scheme. I really don’t understand how this is happening: everything is configured correctly so that the original host and scheme is passed to Django, therefore why is it generating a CSRF token for the wrong address? Shouldn’t it be generating it for the original http address rather than the https one?

Another solution that works me which I just tried on one of the project served by Nginx is adding the following to nginx server conf:

map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}

server {
    listen   80;
    listen   [::]:80;

    server_name my.backend.com *.my.backend.com

    location / {
      return 301 https://$host$request_uri;
    }
}

server {
  listen 443 ssl http2;
  server_name my.backend.com *.my.backend.com;
  ssl_certificate /etc/letsencrypt/live/my.backend.com/fullchain.pem; # managed by Certbot
  ssl_certificate_key /etc/letsencrypt/live/my.backend.com/privkey.pem; # managed by Certbot


  location /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }

  include ssl.conf;

  location / {
    proxy_http_version 1.1;
    proxy_pass http://my_backend:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Upgrade $http_upgrade;
    proxy_cache_bypass $http_upgrade;
  }
}

In the above, the two headers that are very important are

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

In the settings.py I have:

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True
USE_X_FORWARDED_PORT = True

In this configuration, I don’t need to touch CSRF_TRUSTED_ORIGINS at all.