Why can't Nginx find Django's static files?

3,216 views
Skip to first unread message

Robert F.

unread,
Feb 20, 2020, 11:35:51 AM2/20/20
to Django users
I'm trying to understand how static files are served up by Django using a project I've created on my Mac using Django 3, Gunicorn, and Nginx.  The website serves up templates correctly except that the templates can't see my CSS stylesheet.  When I go to a page, for example ```127.0.0.1:8000/app1/``` and view the source, I see this in my HTML template:

    <link rel="stylesheet" href="/static/css/main.css">

If I click on the href link, I get a page "Not Found" at the address ```http://127.0.0.1:8000/static/css/main.css```.  This seems like it should be the correct link but the template can't see the main.css stylesheet as I would expect.

Here are the relevant files in my 'mysite' project located in /Users/me/projects/django/django-staticfiles:

├── app1
│   ├── templates
│   │   └── app1
│   │       └── index.html
├── mysite
│   ├── settings.py
│   └── wsgi.py
├── static
│   └── css
│       └── main.css
├── static-final
│   ├── admin
│   │   ├── css
│   │   ├── fonts
│   │   ├── img
│   │   └── js
│   └── css
│       └── main.css

Here are the relevant settings:

# mysite/settings.py
...
DEBUG = False
ALLOWED_HOSTS = ['127.0.0.1', ]
...
INSTALLED_APPS = [
    'django.contrib.staticfiles',
    'app1',
]
...
STATIC_URL = '/static/'
STATICFILES_DIRS = (
        os.path.join(BASE_DIR, 'static'),
)
STATIC_ROOT = os.path.join(BASE_DIR, 'static-final/')
...

I installed Gunicorn into my virtual environment and run it with this command:

gunicorn --bind 0.0.0.0:8000 mysite.wsgi

I installed Nginx using Homebrew.  Here is the part of nginx.conf that includes my Nginx settings:

# /usr/local/etc/nginx.conf
...
http {
    ...
    server {
        listen      8080;
        server_name localhost;
    }
    ...
    include server/*;
}

Here is my Nginx configuration:

# /usr/local/etc/nginx/servers/django-staticfiles
server {
    listen 80;
    server_name 127.0.0.1;

    location / {
        proxy_pass http://127.0.0.1:8000;
    }

    location /static/ {
        alias /Users/me/projects/django/django-staticfiles/static-final/;
    }
}

I should add that when I stop Gunicorn and start Django's development server with DEBUG = True, the app1 template accesses the main.css file correctly.  It just can't see it when I'm running Gunicorn and Nginx.  I realize that I don't have a real need to run Gunicorn/Nginx on my Mac, but I've just set it up so I can compare and contrast how Django manages static files in development versus production.

Stephen J. Butler

unread,
Feb 20, 2020, 11:52:31 AM2/20/20
to django...@googlegroups.com
Django only serves up static files itself when run using runserver. This is meant for development and not production use. When you run it through gunicorn the Django framework won't serve up static files. That's why you connections to :8000 (gunicorn directly, bypassing nginx) don't work right.

For running in production/gunicorn you need to run "collectstatic" after changes, and connect to nginx (probably port 80/443) so that the "location/alias" block works as intended and you get your static files.

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/75f129a6-2dbf-47cc-b4ea-08008b30b047%40googlegroups.com.

Jody Fitzpatrick

unread,
Feb 21, 2020, 9:22:40 AM2/21/20
to Django users
Take a look at whitenoise for django, this should help you. 

Aldian Fazrihady

unread,
Feb 21, 2020, 10:05:34 AM2/21/20
to django...@googlegroups.com
When you are using Nginx, it is assumed you are trying to make a production environment.
On production environments you don't want your backend code execution to be slowed down by something like serving people downloading static images.
On production environments, Django will focus on its purpose, which is to handle application code logic.

Static files handling should be delegated to other server such as Nginx. The static file folder will be referenced directly by Nginx.
Most of static files, such as images are so irrelevant to your backend application logic so that they can be cached on the browser.


--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/75f129a6-2dbf-47cc-b4ea-08008b30b047%40googlegroups.com.


--
Regards,

Message has been deleted

Robert F.

unread,
Feb 21, 2020, 12:48:35 PM2/21/20
to Django users


On Thursday, February 20, 2020 at 8:52:31 AM UTC-8, Stephen J. Butler wrote:
Django only serves up static files itself when run using runserver. This is meant for development and not production use. When you run it through gunicorn the Django framework won't serve up static files. That's why you connections to :8000 (gunicorn directly, bypassing nginx) don't work right.

For running in production/gunicorn you need to run "collectstatic" after changes, and connect to nginx (probably port 80/443) so that the "location/alias" block works as intended and you get your static files.


I understand what you're saying but I don't understand specifically what it is that I'm doing wrong.  Should I not start Gunicorn with ":8000"?  Do I need to modify my Nginx configuration file?  I don't quite see how all of these pieces work together and which piece (or pieces) are wrong.  Thanks.

אורי

unread,
Feb 21, 2020, 2:12:43 PM2/21/20
to django...@googlegroups.com
If it's a development server and not production, you can add something like this to your urls.py:

from django.conf import settings as django_settings
from django.conf.urls.static import static

...

if (django_settings.DEBUG):
    urlpatterns = static(prefix=django_settings.MEDIA_URL, document_root=django_settings.MEDIA_ROOT) + urlpatterns


Stephen J. Butler

unread,
Feb 21, 2020, 6:27:31 PM2/21/20
to django...@googlegroups.com
The way production servers are setup with Django uses this kind of request workflow:

client /myapp1 --> nginx:8080 ---(proxy)---> gunicorn:8000 (runs python)
client /myapp2 --> nginx:8080 ---(proxy)---> gunicorn:8000 (runs python)
client /myapp3 --> nginx:8080 ---(proxy)---> gunicorn:8000 (runs python)
client /static/somefile1.css ---> nginx:8080 (reads from the filesystem)
client /static/somefile2.css ---> nginx:8080 (reads from the filesystem)

Notice how everything first goes to nginx, and then your nginx either proxies/forwards the request to gunicorn for the python code, or it serves up the static file itself. That's what we want to do. gunicorn is very good at running dynamic python code but it's very bad at serving up static files. nginx is very good at serving static files but can't run python code. See how they work together?

Here's another way to think about it. Take a look sometime at the number of requests a browser makes to a simple Django website that maybe has 10 images on it, 5 CSS files, and 5 JS files. The initial request fetches dynamic content and the python code, but then there are 20+ (!!) other requests that fetch all the static things.

So in that case, only 5% of your requests need to run in gunicorn and the rest of them should be handled by nginx directly. If you did the bad thing and just let gunicorn handle it all then you'd need to provision x20 the resources for gunicorn to make your site have good response times under load! That's because you'd be handling x20 the connections just to serve static files.

Therefore, every good sysadmin puts gunicorn behind nginx or apache or some other proxy technology. It says "Let the frontend server (nginx, apache, CloudFront, CDN, etc) do the easy task of serving up files from /static, and send everything else to the backend server (gunicorn) for the hard part of generating dynamic content."

Now, you setup your nginx and gunicorn properly as far as I can tell. The only bad thing you did was talk to gunicorn directly instead of going through the nginx proxy like you should have.

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages