localhost in build_absolute_uri for Django with Nginx

The problem

When the public hostname you use to reach the proxy differ from the internal hostname of the application server, Django has no way to know which hostname was used in the original request unless the proxy is passing this information along.

Possible Solutions

1) Set the proxy to pass along the orginal host

From MDN:

The X-Forwarded-Host (XFH) header is a de-facto standard header for identifying the original host requested by the client in the Host HTTP request header.

Host names and ports of reverse proxies (load balancers, CDNs) may differ from the origin server handling the request, in that case the X-Forwarded-Host header is useful to determine which Host was originally used.

There are two things you should do:

  1. ensure all proxies in front of Django are passing along the X-Forwarded-Host header
  2. turn on USE_X_FORWARDED_HOST in the settings
  3. if the internal and external scheme differ as well, set SECURE_PROXY_SSL_HEADER to a meaningful value and set the server to send the corresponding header

When USE_X_FORWARDED_HOST is set to True in settings.py, HttpRequest.build_absolute_uri uses the X-Forwarded-Host header instead of request.META['HTTP_HOST'] or request.META['SERVER_NAME'].

I will not delve too much into the proxy setup part (as it is more related to professional network administration than to programming in the scope of this site) but for nginx it should be something like:

location / {
    ...
    proxy_set_header X-Forwarded-Host $host:$server_port;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    ...
    proxy_pass http://upstream:port;
}    

Probably the best solution as it is fully dynamic, you don't have to change anything if the public scheme/hostname changes in the future.

If the internal and external scheme differ as well you may want to set SECURE_PROXY_SSL_HEADER in settings.py to something like this:

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

And then add the following to the server config:

proxy_set_header X-Forwarded-Proto https;

2) Use the same hostname for public and private servers

Lets say your public hostname is "host.example.com": you can add a line like this to your /etc/hosts (on Windows %windir%\System32\drivers\etc\hosts):

127.0.0.1    host.example.com

Now you can use the hostname in the nginx config:

proxy_pass http://host.example.com:port;

When the internal and external scheme differ as well (external https, internal http), you may want to set SECURE_PROXY_SSL_HEADER as described in the first solution.

Every time the public hostname changes you will have to update the config but I guess this is OK for small projects.