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:
- ensure all proxies in front of Django are passing along the
X-Forwarded-Host
header - turn on
USE_X_FORWARDED_HOST
in the settings - 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.