Host Django on subfolder
Asked Answered
I

4

8

I have deployed Django with Gunicorn and NGINX, and it works fine, if the Django app is served on the root url, with this configuration:

server {
    listen 80;

    location = /favicon.ico { access_log off; log_not_found off; }

    location / {
        include proxy_params;
        proxy_pass http://unix:my_app.sock;
    }
}

However when I try to serve the Django app on another URL, it doesn't work. If I try to access http://domain/my_app/admin/ then Django tells me it cannot find the view.

This is the NGINX config:

server {
    listen 80;

    location = /favicon.ico { access_log off; log_not_found off; }

    location /my_app {
        include proxy_params;
        proxy_pass http://unix:/var/my_app/app.sock;
    }
}

How could I make this work? I was not able to find any solution to specify something like a "BASE_URL" so far.

Inadvisable answered 22/12, 2017 at 11:51 Comment(1)
The setting you're looking for is FORCE_SCRIPT_NAME - you can also have nginx set the header X-Script-Name to /my_appLeilaleilah
L
8

My comment doesn't show the whole picture. When I've run Django sites on subfolders, I like to use a dynamic config so that you can still access the machine directly (without the proxy) and have a working web-app. This can help a LOT for debugging tricky stuff like this that is hard to reproduce in dev.

If you do not have the ability to pass the header or modify wsgi.py, you can still set FORCE_SCRIPT_NAME in your Django settings.

3 steps:

  1. set up a proxy in front of the webserver that strips the subfolder out of the URL
  2. set the X-Script-Name header so that your Django site generates its urls with /myapp/ infront of them -- make sure you're using the {% url %} tag and reverse, vs. hard-coding!
  3. modify myapp/wsgi.py to read a new header X-Script-Name into the wsgi environment variable SCRIPT_NAME (based on this flask snippet)

Here is an example Nginx config for a proxy that points to a Django site on a subdirectory and also sets X-Script-Name (steps 1 and 2), note that this doesn't use a unix socket, so it's a little different from the OP's question. Would welcome an edit:

nginx.conf

location /my_app {
    proxy_pass https://mywebapp.com/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Script-Name /my_app;
    proxy_cookie_path / /my_app;
}

And to read X-Script-Name:

myapp/wsgi.py

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings")

_application = get_wsgi_application()


def application(environ, start_response):
    # http://flask.pocoo.org/snippets/35/
    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)
Leilaleilah answered 22/12, 2017 at 17:21 Comment(6)
If this worked years ago, it doesn't anymore.Uncle
@Uncle - this config should still work. I've made a minor edit to the proxy server's NGINX config that should simplify some things. The assumption here is that you have a working application at https://mywebapp.com/, and there is a separate domain that the subfolder is set up on (suppose it's https://user-facing-domain.com/my_app/). The proxy at https://user-facing-domain.com runs the NGINX config above.Leilaleilah
..."that strips the subfolder out of the URL"... seems to be an essential part of it!Pythagoreanism
This seems like a partial solution. The CSS isn't working because the pages are looking for /static.Monstrance
@Brendon - it sounds like for your use case you'll need to create a storage backend that considers SCRIPT_NAME when determining STATIC_URL. Keep in mind it might need to be handled differently between development and production. I don't think this problem comes up when using a CDN for staticfiles.Leilaleilah
Is there a way to get this to work with django runserver? I'm trying to get this to work on code-server for development envs.Distribution
P
3

if you run django with gunicorn and use uvicorn as asgi worker, like

gunicorn example:asgi.app -w 4 -k uvicorn.workers.UvicornWorker

you need to add root_path='/api/' to django asgi scope, unfortunately, gunicorn can't pass this thought arguments but you can write a custom worker and run gunicorn with it

class MyUvicornWorker(UvicornWorker):
    CONFIG_KWARGS = {"loop": "auto", "http": "auto", "root_path": "/api/"}

and then run gunicorn with custom worker

gunicorn example:asgi.app -w 4 -k example.workers.MyUvicornWorker

Paton answered 16/1, 2023 at 16:10 Comment(0)
D
1

@andyC thank you for your answer.

With up-to-date Django (4.1.4) it worked with the following settings. I made the following setting in nginx for django, which is behind the api gateway.

settings.py

USE_X_FORWARDED_HOST = True
FORCE_SCRIPT_NAME = '/content/'
SESSION_COOKIE_PATH = '/content/'

LOGIN_URL = "login/"
LOGIN_REDIRECT_URL = '/content/'
LOGOUT_REDIRECT_URL = '/content/'


STATIC_URL = '/content/static/'

STATIC_ROOT = os.path.join(BASE_DIR, "static/")

# Media files
MEDIA_URL = '/content/media/'

MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles/")

apiGateway -> nginx.conf

server_name api.mydomain.com;
  location /auth/ {
    proxy_pass http://django_app:8002/;
    proxy_set_header Host $http_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;
  }
Dissogeny answered 30/12, 2022 at 17:43 Comment(0)
E
0

Here is what I solve this problem. I can run multiple django running on same server/domain with subdirectory.

project/settings.py

MY_PROJECT = env('MY_PROJECT', '')  # example; '/dj'
if MY_PROJECT:
    USE_X_FORWARDED_HOST = True
    FORCE_SCRIPT_NAME = MY_PROJECT + "/"
    SESSION_COOKIE_PATH = MY_PROJECT + "/"

LOGIN_URL = "login/"
LOGIN_REDIRECT_URL = MY_PROJECT + "/"
LOGOUT_REDIRECT_URL = MY_PROJECT + "/"

NGINX configuration

location /dj/ {
    proxy_set_header X-Forwarded-Protocol $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_pass http://127.0.0.1:8000/;
}

Be aware that location url should end with "/"

Eluvium answered 4/7, 2022 at 3:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.