Hello! Welcome to the seventh lesson in our module on Web Servers and Application Runtimes.
In our previous lessons, we've dissected the components of a modern Python backend. We've seen the distinct roles of a web server like Nginx and an application server like Gunicorn, understood the WSGI/ASGI contract that allows them to communicate, and explored how to optimize performance using HTTP caching.
So far, this has been mostly theoretical. Today, we bridge theory and practice. The learning outcome for this lesson is to configure Nginx as a reverse proxy to forward requests to a Gunicorn-managed Python application. We will create a complete, functioning deployment chain that is the blueprint for a real-world production environment. This hands-on session will solidify your understanding of how these critical infrastructure pieces fit together.
The Production Architecture
Let's start with a visual of our target architecture.

This separation of concerns is fundamental:
- Nginx is the public-facing entry point. It excels at handling thousands of connections, terminating SSL, and serving static files from disk at high speed.
- Gunicorn is the application server. Its job is to manage Python worker processes and translate HTTP requests from Nginx into a format the Django application can understand (WSGI). It's not designed to be exposed directly to the internet.
Let's get our hands dirty and build this.
A Practical Walkthrough with Docker
For this lesson, we will follow a comprehensive video tutorial that uses Docker to set up the environment. This is an excellent modern approach as it allows us to create isolated, reproducible environments for our application (Django/Gunicorn) and our reverse proxy (Nginx).
The video uses Django, which aligns perfectly with your experience. We will proceed step-by-step, focusing first on getting the Django application running with Gunicorn, and then placing Nginx in front of it.
Nginx Reverse Proxy | Django Deployment | Docker | Staticfiles
The video 'Nginx Reverse Proxy | Django Deployment' from Very Academy provides a clear, step-by-step guide to our exact goal. We'll start by watching the initial overview to understand the full picture.
Watch the segment from 00:00:32 to 00:04:09. This section outlines the architecture we are about to build, matching the diagram above. Pay attention to the two distinct roles: Nginx as the reverse proxy and Gunicorn as the WSGI server for Django.
Part 1: Running the Django Application with Gunicorn
Before we can put Nginx in front of our application, we need the application to be running under the control of a proper application server, not Django's built-in development server.
Your experience with manage.py runserver is for development. In production, we use a WSGI server like Gunicorn. The process involves:
- Installing Gunicorn into our project's environment.
- Running Gunicorn and telling it where our Django project's WSGI application object is located (usually in
myproject/wsgi.py). - Binding Gunicorn to a network address and port (e.g.,
127.0.0.1:8000) so that other processes on the same machine (like Nginx) can connect to it. - Running
collectstaticto gather all static files (from your apps and the Django admin) into a single directory.
The next segment of the video walks through this setup within a Docker container.
Nginx Reverse Proxy | Django Deployment | Docker | Staticfiles
Let's continue with the video to see how to containerize a Django application and run it with Gunicorn.
Watch from 00:05:50 to 00:25:57. Don't worry about memorizing every Docker command. Instead, focus on these key steps and concepts: Creating a requirements.txt file and adding gunicorn to it. Running python manage.py collectstatic and understanding why this is necessary. The CMD in the Dockerfile that starts Gunicorn, binding it to port 8000. This replaces the runserver command. By the end of this segment, you will see a Django application being served by Gunicorn, accessible on port 8000.
Part 2: Configuring Nginx as a Reverse Proxy
Now that Gunicorn is serving our Django application on port 8000, we'll introduce Nginx as the front door. Nginx will listen on the standard web port (80), and its primary job will be to proxy pass requests to Gunicorn.
The core of this configuration lies in a server block in an Nginx configuration file. The key directive is proxy_pass.
server {
listen 80;
server_name your_domain.com;
location / {
# This is the magic part.
# It tells Nginx to forward any request for the root path '/'
# to the Gunicorn server listening on localhost port 8000.
proxy_pass http://127.0.0.1:8000;
# These headers are important to pass along original request info
# to the Django application.
proxy_set_header Host $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;
}
}
Let's see this in action. The next part of the video implements this exact logic.
Nginx Reverse Proxy | Django Deployment | Docker | Staticfiles
This is the most critical part of the lesson. The video will now guide you through creating the Nginx configuration to act as a reverse proxy.
Watch from 00:27:33 to 00:43:23. Focus on: The Nginx configuration file (default.conf). The upstream block, which defines a pool of servers to proxy to (in this case, just our one Gunicorn container). The location / block and the proxy_pass directive that forwards traffic. The debugging process where the port mapping is changed from ports to expose in the docker-compose.yml file. This is a crucial concept: ports maps a port to the host machine, while expose only makes it available to other containers in the same Docker network.
For a text-based alternative that explains these directives clearly, the following article is an excellent reference.
Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu
The article 'Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu' from DigitalOcean provides a fantastic, concise explanation of the Nginx server block.
Read the section 'Step 10 — Configure Nginx to Proxy Pass to Gunicorn'. This provides a clear, text-based example of an Nginx configuration file. Notice it uses a Unix socket (proxy_pass http://unix:/run/gunicorn.sock;) instead of a TCP port. This is a common and slightly more performant alternative when Nginx and Gunicorn run on the same host.
Part 3: Offloading Static Files to Nginx
Our reverse proxy is working, but there's one major optimization left. Right now, requests for static files (like CSS and JavaScript) are also going through the full proxy chain: Nginx -> Gunicorn -> Django. This is inefficient.
A much better approach is to configure Nginx to handle these requests directly. Since we ran collectstatic, all our static files are in one known directory. We can tell Nginx: "If a request comes in for a URL starting with /static/, don't bother Gunicorn. Just find the corresponding file in the static directory on the filesystem and serve it yourself."
This is done with a second location block in our Nginx configuration.
location /static/ {
# 'alias' tells Nginx the path on the file system that
# corresponds to this URL path.
alias /path/to/your/project/staticfiles/;
}
Let's see the final piece of the puzzle fall into place.
Nginx Reverse Proxy | Django Deployment | Docker | Staticfiles
The final configuration step is to set up Nginx to serve static files directly.
Watch from 00:43:23 to 00:49:03. This section explains how to create a shared volume in Docker so the Nginx container can access the static files collected by the Django container. It then adds the location /static/ block to the Nginx configuration to serve those files directly.
Conclusion
Congratulations! You have now configured the cornerstone of a modern web application deployment. By placing Nginx in front of a Gunicorn-managed Django application, you have created a robust, scalable, and performant architecture.
Key Takeaways:
- A production setup consists of a web server (Nginx) acting as a reverse proxy for an application server (Gunicorn).
- The key Nginx directive is
proxy_pass, which forwards requests to the application server listening on a TCP port or a Unix socket. - It's crucial to pass headers like
HostandX-Forwarded-Forso the backend application is aware of the original client's request details. - A major performance gain comes from configuring Nginx with a
location /static/block to serve static assets directly from the filesystem, bypassing the Python application entirely. - This architecture lets each component do what it does best: Nginx handles raw network traffic and static files, while Gunicorn and Django handle dynamic application logic.
Preview of the Next Lesson:
So far, every request we've handled has been stateless. The server responds to a request without any memory of previous interactions with that client. But most real applications need to maintain state—knowing who is logged in, what's in their shopping cart, etc. In our next lesson, we will dive into how this is achieved by exploring how cookies and sessions work at the HTTP protocol level to maintain state.