Fix Nginx & Gunicorn Static Files Not Loading
When you run a Django or Flask app behind Gunicorn and Nginx, static files (CSS, JavaScript, images) often fail to load, leaving your site unstyled or broken. This guide walks you through the most common causes and exact fixes, from Nginx configuration to file permissions and Django settings.
What You’ll Need
- Access to your server via SSH (root or sudo privileges)
- Your project’s directory path (e.g.,
/var/www/myproject) - Your Nginx configuration file location (usually
/etc/nginx/sites-available/) - Basic familiarity with the command line
Step-by-Step
-
Check your Django/Flask static settings.
For Django, ensure insettings.pyyou have:STATIC_URL = '/static/' STATIC_ROOT = '/path/to/your/project/staticfiles/'
For Flask, set
static_folder='static'in your app. Then runpython manage.py collectstatic(Django) to gather all static files intoSTATIC_ROOT. -
Verify the static files actually exist.
Runls -la /path/to/your/project/staticfiles/(or your static folder). If empty, you missedcollectstaticor your app’s static files are elsewhere. -
Check file permissions.
Nginx runs as userwww-data(on Debian/Ubuntu). Ensure that user can read the static files:sudo chown -R www-data:www-data /path/to/your/project/staticfiles/ sudo chmod -R 755 /path/to/your/project/staticfiles/
If your project is in your home directory, Nginx may not have access. Move it to
/var/www/or adjust permissions. -
Edit your Nginx server block.
Open your site’s config (e.g.,/etc/nginx/sites-available/myproject) and add a location block for static files. It must be outside thelocation /block that proxies to Gunicorn:server { listen 80; server_name example.com; location /static/ { alias /path/to/your/project/staticfiles/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }Important: Use
aliasand ensure the trailing slashes match. If you userootinstead, the path must include thestaticdirectory name. -
Test Nginx configuration.
Runsudo nginx -t. If it reports an error, fix the syntax (common: missing semicolon or mismatched braces). -
Reload Nginx.
Runsudo systemctl reload nginx(orsudo service nginx reload). -
Clear your browser cache and test.
Hard refresh (Ctrl+Shift+R) or open an incognito window. Visithttp://yourdomain.com/static/css/style.css– you should see the file content, not a 404 or a Django error page.
Common Mistakes
- Missing trailing slashes – In the
aliasdirective, the path must end with a slash, and the location must also have a slash. Mismatches cause 404s. - Using
rootinstead ofalias– Withroot /path/to/staticfiles/, Nginx looks for/path/to/staticfiles/static/..., which is usually wrong. - Forgetting to run
collectstatic– If you changed your static files, you must run it again. - Permissions on parent directories – Even if the static folder is readable, Nginx needs execute permission on every parent directory (e.g.,
/home/user/project). Usesudo chmod o+x /home /home/user /home/user/projector move the project. - Nginx serving Django’s 404 – If you see a Django error page for static files, it means Nginx is proxying
/static/to Gunicorn. Your location block is missing or misconfigured.
Tips
- Use a dedicated domain or CDN for static files in production (e.g.,
static.example.com) to reduce server load. - Enable gzip compression for static assets in Nginx:
gzip on; gzip_types text/css application/javascript image/svg+xml;
- Set long cache headers for static files:
location /static/ { alias /path/to/staticfiles/; expires 30d; add_header Cache-Control "public"; } - If you use a virtual environment, ensure Gunicorn runs with the correct Python path – but that’s unrelated to static files.
Troubleshooting
If static files still don’t load after following the steps:
- Check Nginx error logs:
sudo tail -f /var/log/nginx/error.log. Look for “Permission denied” or “open()” errors – these point to permission or path issues. - Check access logs:
sudo tail -f /var/log/nginx/access.logto see if requests hit Nginx and what status code they return. - Test the file directly:
curl -I http://localhost/static/css/style.css(on the server). If you get 200, the issue is browser cache or DNS. If 404, check the path. - Verify Nginx user has access: Run
sudo -u www-data ls /path/to/staticfiles/. If it fails, permissions are wrong. - Check for SELinux (on RHEL-based systems):
sudo setenforce 0temporarily to test. If that fixes it, set the proper context:sudo chcon -R -t httpd_sys_content_t /path/to/staticfiles/. - If you use a Unix socket for Gunicorn, ensure the socket file is accessible to Nginx – but again, static files are handled by Nginx directly, so this is only relevant for dynamic pages.
Frequently Asked Questions
Why do static files work in development but not production?
In development, Django/Flask serve static files themselves. In production, Nginx should serve them directly. If you haven’t configured Nginx to do so, it proxies to Gunicorn, which may not serve static files efficiently or at all.
What is the difference between alias and root in Nginx?
root appends the location URI to the root path. For example, root /var/www/static; with a request to /static/css/style.css looks for /var/www/static/static/css/style.css. alias replaces the location part, so alias /var/www/static/; looks for /var/www/static/css/style.css. Use alias for static files.
Do I need to run collectstatic every time I change a CSS file?
Yes, if you’re using Django’s STATIC_ROOT. collectstatic copies all static files from your apps and STATICFILES_DIRS to STATIC_ROOT. Without it, Nginx won’t see the updated files.
Can I serve static files with Gunicorn?
Technically yes, but it’s inefficient and not recommended. Gunicorn is for Python web applications, not static file serving. Nginx is far better at handling concurrent static requests.
Why do I get a 403 Forbidden error?
This usually means Nginx can’t read the file due to permissions. Check that the www-data user has read and execute permissions on the directory and all parent directories.
What if my static files are in a different location than the default?
Adjust the alias path to point to your actual static directory. Also update STATIC_URL in Django if you’re using a custom URL prefix.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.