Static files not found if SCRIPT_NAME is defined?

Hi all,

I have a django application (containerised), which works and serves static files.
I want to serve this application under a nginx proxy.
But suddenly, the static files are not found by django.

In urls.txt, I have the following code:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('scrape/', include('crawlerScrapy.urls')),
    path('api/', include('api.urls')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

print('STATIC_URL:', settings.STATIC_URL)
print('STATIC_ROOT:', settings.STATIC_ROOT)
print('URL Patterns:', urlpatterns)
import os
print(os.listdir(settings.STATIC_ROOT + 'rest_framework/js'))

When the code runs, I get the following:

STATIC_URL: /disinformation_data_server/static/
STATIC_ROOT: /app/static/
URL Patterns: [<URLResolver <URLPattern list> (admin:admin) 'admin/'>, <URLResolver <module 'crawlerScrapy.urls' from '/app/crawlerScrapy/urls.py'> (None:None) 'scrape/'>, <URLResolver <module 'api.urls' from '/app/api/urls.py'> (None:None) 'api/'>, <URLPattern '^disinformation_data_server/static/(?P<path>.*)$'>]
['load-ajax-form.js', 'jquery-3.7.1.min.js', 'bootstrap.min.js', 'prettify-min.js', 'coreapi-0.1.1.js', 'csrf.js', 'ajax-form.js', 'default.js']

So, everything seems fine from inside django. But when the static files are requested, I see in the logs:

Not Found: /disinformation_data_server/static/rest_framework/js/jquery-3.7.1.min.js
Not Found: /disinformation_data_server/static/rest_framework/js/ajax-form.js
Not Found: /disinformation_data_server/static/rest_framework/js/csrf.js

Since the files exist in the static folder, why I am getting that they are not found?

Please post your nginx configuration for project entries.

Here it is:

    location /disinformation_data_server/ {
        proxy_http_version 1.1;
        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;
        proxy_set_header Host $http_host;
        proxy_set_header SCRIPT_NAME /disinformation_data_server/;
        proxy_pass http://127.0.0.1:8020;
        proxy_redirect off;
    }

How are you running Django in its container? (gunicorn? uwsgi? something else?)

In the general case, for a production-quality deployment behind nginx, you will want to have collectstatic copy your files to a data volume that nginx also has mounted, and have a separate nginx configuration paragraph to read the static files from that volume. You don’t want to have Django serving your static files.

Here is my container entry point:

#!/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
cd $SCRIPT_DIR

while ! nc -z $SQL_HOST $SQL_HOST_PORT; do
  sleep 0.1
done
echo "Database started"


case $* in
  *gunicorn* | *runserver*)
    echo "Appling database migrations..."
    python manage.py makemigrations 
    runuser -u appuser -- python manage.py migrate

    echo "Collect static files..."
    python manage.py collectstatic --no-input

    echo "Starting Celery Worker..."
    runuser -u appuser -- watchmedo auto-restart -d /app -p '*.py' -R -D -- celery --app disinformationDataServer worker --loglevel INFO -E &

    echo "Executing CMD with watchmedo: \"$@\""
    runuser -u appuser -- watchmedo auto-restart -d /app -p '*.py' -R -D -- $@
  ;;
  *)
    echo "Executing CMD: \"$@\""
    exec $@
  ;;
esac

The container logs are:

Database started
Appling database migrations...
STATIC_URL: /static/
STATIC_ROOT: /app/static
URL Patterns: [<URLResolver <URLPattern list> (admin:admin) 'admin/'>, <URLResolver <module 'crawlerScrapy.urls' from '/app/crawlerScrapy/urls.py'> (None:None) 'scrape/'>, <URLResolver <module 'api.urls' from '/app/api/urls.py'> (None:None) 'api/'>, <URLPattern '^static/(?P<path>.*)$'>]
['load-ajax-form.js', 'jquery-3.7.1.min.js', 'bootstrap.min.js', 'prettify-min.js', 'coreapi-0.1.1.js', 'csrf.js', 'ajax-form.js', 'default.js']
No changes detected
STATIC_URL: /static/
STATIC_ROOT: /app/static
URL Patterns: [<URLResolver <URLPattern list> (admin:admin) 'admin/'>, <URLResolver <module 'crawlerScrapy.urls' from '/app/crawlerScrapy/urls.py'> (None:None) 'scrape/'>, <URLResolver <module 'api.urls' from '/app/api/urls.py'> (None:None) 'api/'>, <URLPattern '^static/(?P<path>.*)$'>]
['load-ajax-form.js', 'jquery-3.7.1.min.js', 'bootstrap.min.js', 'prettify-min.js', 'coreapi-0.1.1.js', 'csrf.js', 'ajax-form.js', 'default.js']
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, crawlerScrapy, sessions
Running migrations:
  No migrations to apply.
Collect static files...

163 static files copied to '/app/static'.
Starting Celery Worker...
Executing CMD with watchmedo: "gunicorn --bind 0.0.0.0:8000 --workers 3 disinformationDataServer.wsgi:application"

If you look the logs in the original post, where I list a directory files, the files are there.

Yes, but is that a separate data volume that nginx can access directly? You also didn’t show a separate location directive for nginx to do that.

Static files should not be served by Django. They should be served by nginx like it would serve any other file.

I want static data to be served by django. The static data are inside the container, nginx has no access to them. Nginx runs on a server, and serves 10+ other containers.

Actually, they are the static data of django rest framework.

The problem is why they are not found by django when proxied (the only change is the SCRIPT_NAME env variable).

How can I debug the view that handles the static files, that says “not found”?

You don’t.

That’s a mistake.

Not a big deal.

This does not make a difference.

I suspect that the other difference is that you’re running this under gunicorn (production quality) instead of runserver (development only).

In the same container, with gunicorn, static files are found and served, if env variable SCRIPT_NAME is not defined.

On the other hand, I looked the code of the view that serves the static files, it does not print “Not Found:”. It just returns 404.

It may not be a django problem, but a gunicorn one. Needs more debugging.

You might get it to “work”, but that’s still not how you should be deploying in a production-quality environment.

Its a django bug. To make it work, I had to do this:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('scrape/', include('crawlerScrapy.urls')),
    path('api/', include('api.urls')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static('static/', document_root=settings.STATIC_ROOT)

Django will modify STATIC_URL and prepend it with SCRIPT_NAME if it does not start with /.
But this will add SCRIPT_NAME also in the regular expression that matches the request url.
The rest of the patterns do not have the SCRIPT_NAME prefix…

So the not found is more fundamental, there is no match between the urls and the request url, the file serve view is never called.

So, if SCRIPT_NAME is defined, static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) is wrong.

Actually, it is not “a Django bug”, because you’re using Django in a situation for which it is neither designed nor intended to be used.

From the docs for Serving static files in production:

The basic outline of putting static files into production consists of two steps: run the collectstatic command when static files change, then arrange for the collected static files directory (STATIC_ROOT) to be moved to the static file server and served.

[Emphasis added]

And in the docs for How to manage static files (e.g. images, JavaScript, CSS) | Django documentation | Django :

Serving the files

In addition to these configuration steps, you’ll also need to actually serve the static files.

During development, if you use django.contrib.staticfiles, this will be done automatically by runserver when DEBUG is set to True (see django.contrib.staticfiles.views.serve()).

This method is grossly inefficient and probably insecure, so it is unsuitable for production.

See How to deploy static files for proper strategies to serve static files in production environments.

And in the Deployment section of that same page:

  1. Use a web server of your choice to serve the files. How to deploy static files covers some common deployment strategies for static files.

So while what you’ve found is technically accurate - it doesn’t matter, because you shouldn’t even be trying to do this.