Hello! Welcome back to our course.
In the last lesson, we demystified the Nginx configuration file, exploring the key http, server, and location blocks. You learned how these blocks form a hierarchy to control Nginx's behavior and how Fedora's approach to configuration in /etc/nginx/conf.d/ differs from the sites-available/sites-enabled structure you might see in tutorials for Ubuntu.
Today, we will put that knowledge to immediate practical use. Our goal is to configure Nginx to serve a Django project's static files. This is a core task in deploying any Django application. When you switch a Django project to production mode by setting DEBUG = False, Django's built-in development server no longer handles static files (like CSS, JavaScript, and images) for performance and security reasons. We need to tell our powerful web server, Nginx, to take over that job.
This lesson bridges the gap between your Django development work and a production-ready server setup, a crucial skill for a full-stack developer.
1. The "Why": How Nginx Serves Static Files
Before we start configuring, it's essential to understand the workflow. When a user visits your deployed Django site, two main types of requests happen. The following diagram illustrates this process perfectly.

Here's the breakdown:
- Request for the Page: The user's browser requests a URL, like
http://your-site.com/blog/. Nginx receives this request and, acting as a reverse proxy (which we'll configure fully later), passes it to your Django application (via Gunicorn). Django processes the request, renders the HTML template, and sends the HTML page back to the browser. - Request for Static Files: The browser parses the HTML and finds tags like
<link rel="stylesheet" href="/static/css/main.css">or<script src="/static/js/app.js">. It then sends separate requests back to the server for each of these files. - Nginx Takes Over: Our Nginx configuration will include a special
locationblock that matches the/static/URL. When Nginx sees these requests, it doesn't bother Django. Instead, it fetches the files directly from a designated folder on the server and sends them to the browser.
This separation of duties is highly efficient. Nginx is optimized for serving static files, while Django can focus on what it does best: handling application logic.
2. Step 1: Preparing Your Django Project
First, we need to tell Django to gather all the necessary static files from your various apps (including Django's own admin app) into a single directory for Nginx to use.
Django Settings
Open your Django project's settings.py file. We need to ensure three key settings are configured for production:
DEBUG = False: This is the master switch for putting Django in production mode.ALLOWED_HOSTS = ['your_server_ip_or_domain']: For security, Django requires you to specify which hostnames it will serve. Replace the placeholder with your server's actual IP address or domain name. You can use['*']for initial testing, but it's not recommended for true production.STATIC_ROOT = '/var/www/myproject/static': This is the most important setting for this lesson. It tells Django the absolute path to a single directory where it should collect all static files. We are choosing/var/www/as it's a standard location for web content on Linux.
The following video segment walks through this exact process. While the narrator is using Ubuntu and apt, the steps inside the settings.py file and the manage.py commands are universal to Django.
Django Tutorial - Deploy Django to the production server with Gunicorn and Nginx #21
Watch this clip to see how to configure your project's settings.py for deployment and run the collectstatic command. Focus on the changes to DEBUG, ALLOWED_HOSTS, and STATIC_ROOT.
Watch from 14:36 to 16:44. The key action is running python manage.py collectstatic and seeing the new static directory being created and populated.
Hands-On Task: Collect Your Static Files
Now, let's execute these steps on your Fedora system.
-
Create the Directory: The
STATIC_ROOTpath we defined doesn't exist yet, and it's in a system-protected area (/var/www). We need to create it and give your user account ownership. Open your terminal and run:# Create the directory structure for your project's web root sudo mkdir -p /var/www/myproject/static # Change the ownership from 'root' to your current user # The $USER variable automatically uses your username sudo chown -R $USER:$USER /var/www/myproject -
Run
collectstatic: Navigate to your Django project's root directory (wheremanage.pyis located) and run the command:python manage.py collectstaticYou should see output indicating that hundreds of static files have been copied. If you check the contents of
/var/www/myproject/static, you'll find all your project's static assets, including those for the Django admin interface.ls /var/www/myproject/static
3. Step 2: Configuring Nginx
With our static files collected, we can now tell Nginx where to find them. We'll do this by creating a new configuration file for our project in /etc/nginx/conf.d/.
The alias Directive
Inside our new configuration, we'll use a location block to match requests for /static/. Within this block, we'll use the alias directive.
alias: This directive maps a URL location to a different directory path on the server. When a request matcheslocation /static/,aliastells Nginx to look for the requested file inside the path specified.
For location /static/ { alias /var/www/myproject/static/; }:
- A request for
/static/css/styles.css... - ...is mapped to the file
/var/www/myproject/static/css/styles.css.
This is a clean and explicit way to handle static file serving. The following guide provides an excellent written reference for this process.
Securely Deploy a Django App: Serving Static Files Directly With Nginx
This article from Real Python provides a detailed, step-by-step guide to configuring Nginx for a Django application. We'll focus on the section about serving static files.
Read the section titled 'Serving Static Files Directly With Nginx'. Pay close attention to the sample Nginx configuration file that adds a location /static block. Notice how it uses the alias directive, just as we will.
Hands-On Task: Create the Nginx Configuration
-
Create the Config File: Use a text editor like
nanoto create a new configuration file for your project.sudo nano /etc/nginx/conf.d/myproject.conf -
Add the Configuration: Paste the following configuration into the file. Be sure to replace
your_server_ip_or_domainwith your server's IP address or domain.# /etc/nginx/conf.d/myproject.conf server { listen 80; server_name your_server_ip_or_domain; # This block tells Nginx where to find the static files location /static/ { alias /var/www/myproject/static/; } # We will add a location for the Django app itself in a later lesson # For now, this will just return a 404 for the root URL location / { # proxy_pass http://... ; # To be added later return 404; # Placeholder } } -
Test and Restart Nginx: Save and close the file. Always test your Nginx configuration before restarting the service.
# Check for syntax errors sudo nginx -t # If the test is successful, restart Nginx to apply the changes sudo systemctl restart nginx
4. The Inevitable Roadblock: SELinux
Now for the moment of truth. Let's try to access one of the static files that Django's admin interface uses. Open your web browser and navigate to:
http://your_server_ip/static/admin/css/base.css
You will most likely see a 403 Forbidden error page from Nginx.
Don't panic! This is expected. You have done everything correctly from a traditional web server perspective. Your Django settings are right, your file permissions are right, and your Nginx configuration is right. The culprit is a powerful security feature specific to Fedora and other Red Hat-based systems: SELinux.
SELinux (Security-Enhanced Linux) adds a layer of mandatory access control. It labels every file and process on the system with a "security context." The Nginx process runs with the httpd_t context. By default, processes with the httpd_t context are only allowed to read files with a web-content context, like httpd_sys_content_t.
Our files in /var/www/myproject/static/ currently have a default context that SELinux doesn't trust for web serving, so it blocks Nginx from reading them, resulting in the 403 error.
We will not solve this problem today. Instead, this sets the stage for our next lesson, where we will dive deep into SELinux. The article below describes the exact scenario we are facing.
NGINX SELinux: Fix Permission Denied
This article discusses common SELinux issues with Nginx. The second scenario it describes is precisely what we're encountering.
Briefly read the scenario titled '2. Static Files Return 403 Forbidden'. Note how it mentions checking file contexts with ls -Z and fixing them with semanage fcontext and restorecon. These are the tools we will master in our next session.
Conclusion
Excellent work today! You have successfully configured both Django and Nginx for static file serving in a production environment. More importantly, you've encountered a real-world, distribution-specific challenge with SELinux, which is a fantastic learning opportunity.
Key Takeaways:
- In production (
DEBUG=False), a web server like Nginx must be configured to serve Django's static files. - The Django
collectstaticcommand gathers all project static files into the directory specified bySTATIC_ROOT. - An Nginx
location /static/block, combined with thealiasdirective, efficiently maps the static URL to the correct filesystem path. - On Fedora, a correct Nginx configuration can still fail with a 403 Forbidden error due to SELinux security contexts.
Preview of the Next Lesson:
Next time, we will become SELinux detectives. You will learn how to diagnose SELinux denials using audit logs and set correct file contexts to allow Nginx to serve web content. By the end of it, you will have resolved the 403 error, and your static files will finally load correctly.