Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

localhost in build_absolute_uri for Django with Nginx

On production I use the chain Django - UWSGI - Docker - Nxing. UWSGI works with the port 50012 and Ngxin is configured as:

proxy_pass http://localhost:50012;

Django process thinks that its host is localhost:50012 instead of the domain that Nginx listens to. So when the function build_absolute_uri is called there's localhost:50012 instead of my domain. Is there a way to make Django use the custom host name when build_absolute_uri is called?

Notice: in some libraries build_absolute_uri called implicitly (like social-django, or example), so avoiding this function is not a solution in my case.

like image 698
Fomalhaut Avatar asked Sep 21 '19 22:09

Fomalhaut


3 Answers

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.

like image 50
Paulo Scardine Avatar answered Oct 23 '22 12:10

Paulo Scardine


I got mine working using proxy_redirect

Lets say you have a container or an upstream with the name app and you want it to return 127.0.0.1 as the host, Then your config should include:

server {
  listen 80;

  location / {
    proxy_pass http://app:8000;

    proxy_redirect http://app:8000 http://127.0.0.1:8000;
  }
}

Here's my final config:

server {
  listen 80;

  location / {
    proxy_pass http://app:8000;

    proxy_set_header X-Forwarded-Host $host:$server_port;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_redirect http://app:8000 http://127.0.0.1:8000;
  }
}

Also checkout this article for a detailed explanation https://mattsegal.dev/nginx-django-reverse-proxy-config.html

like image 27
Divine Hycenth Avatar answered Oct 23 '22 11:10

Divine Hycenth


I had a problem same as the question, I used nginx, gunicorn and django on production without docker.

get_current_site(request).domain

returned localhost so I have some issue in drf yasg base url. Simply I solved it by adding

include proxy_params;

to nginx conf.

like image 43
mastisa Avatar answered Oct 23 '22 11:10

mastisa