[Django]-Django not taking script name header from nginx

3👍

I didn’t find the answer to this in all my searching, so here it is:

  1. This is only properly implemented in Django 3.1.6, so 1.8 won’t work.
  2. proxy_pass http://127.0.0.1:8000/ is wrong, you need proxy_pass http://127.0.0.1:8000 (without the trailing backslash) in order to keep /myapp at the start of the URL path. Django expects its requests to start with the value of the SCRIPT_NAME header, and strips it off before your apps see it. Django will return an error for any URL that doesn’t start like this.
  3. If your settings.py defines STATIC_URL as an absolute path like /static/, Django will not prepend it with your script name. Use a relative path like static/.
  4. If there are any other proxies in between, make sure they are not dropping the STATIC_FILES header. For example, if you have another nginx in between then you need to make sure that your server block contains the line underscores_in_headers on;.
  5. settings like LOGIN_REDIRECT_URL are not prepended (and you cannot use the relative path trick as this does something different), so you can fudge this with a line: proxy_redirect / /myapp/;, but then most redirects are prepended, so you also need a line before that: proxy_redirect /myapp/ /myapp/;. This technique will not work if /myapp/myapp is actually a valid path, of course.
  6. If you want to serve the static files statically, remember to include a location block for that.

So your nginx block should look a bit like this:

location /myapp/ {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header SCRIPT_NAME /myapp;
    proxy_redirect /myapp/ /myapp/;
    proxy_redirect / /myapp/;
    location /myapp/static/ {
        root /var/www; # files are deployed to /var/www/myapp/static
    }
}

and in settings.py:

STATIC_URL=`static/`

and in requirements.txt:

Django==3.1.6

What a nightmare. Does anybody have any better ideas?

2👍

I was able to partially resolve this with some help from the following snippet: http://flask.pocoo.org/snippets/35/

location /myapp {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header X-Script-Name /myapp;
    }

Then I created a wsgi.py file in the myapp folder with:

from django.core.wsgi import get_wsgi_application

_application = get_wsgi_application()

def application(environ, start_response):
    script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
    if script_name:
        environ['SCRIPT_NAME'] = script_name
        path_info = environ['PATH_INFO']
        if path_info.startswith(script_name):
            environ['PATH_INFO'] = path_info[len(script_name):]

    scheme = environ.get('HTTP_X_SCHEME', '')
    if scheme:
        environ['wsgi.url_scheme'] = scheme
    return _application(environ, start_response)

I also had to switch from using

python manage.py runserver 0.0.0.0:8000

for serving the application to using uWSGI. For this I followed the instructions at Setting up Django and your web server with uWSGI and nginx
but essentially boiled down to

pip install uwsgi

and then serving the Django app with

uwsgi --http :8000 --module myapp.wsgi

EDIT: This currently doesn’t work for static and media assets. See this open ticket: https://code.djangoproject.com/ticket/25598

👤snth

0👍

Well that was another one day journey in trying to get things work. Now with success and not to much efforts. Here is a working example off getting django 3.2 run behind traefik on a subpath and on another port:

docker-compose.yaml


app:
    build: YOUR DJANGO APP
    command: gunicorn --bind=0.0.0.0 --log-level=debug --timeout 1000 --workers 1 --threads 4 herre.wsgi
    ports:
      - 8000:8000
    labels:
      - "traefik.enable=true"
      - "traefik.http.middlewares.appheaders.headers.customrequestheaders.SCRIPT_NAME=/$APP_SUB_PATH"
      - "traefik.http.routers.app.rule=PathPrefix(`/$APP_SUB_PATH`)"
      - "traefik.http.routers.app.middlewares=appheaders"
      - "traefik.http.routers.app.entrypoints=web"

  traefik:
    image: "traefik:v2.5"
    container_name: "traefik"
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

settings.py

STATIC_URL = "static" 
LOGIN_URL = "auth_login" # <-- we can specify view names here that can be resolved by resolve_url
LOGIN_REDIRECT_URI = "index" <-- again view name


In this configuration there is no need to change the wsgi application. But beware that this does not run with "python manage.py runserver" but only with
a deployment server.

Hope that saves anyone the trouble it cost me !

Leave a comment