Deploying Django Applications with Gunicorn and Nginx

It expands your short outline into a detailed, structured, and professional article — suitable for technical blogs, documentation, or learning materials.
No icons or emojis are used. It includes clear headings, subheadings, explanations, and code.

Introduction

Deploying a Django application to production is one of the most important milestones in any web project. After developing and testing your app locally, the next challenge is to serve it to real users over the internet efficiently and securely.

In production, Django’s built-in development server (python manage.py runserver) is not suitable. It’s intended for local testing, not for handling high traffic, concurrency, or security concerns. For real-world deployment, the recommended setup combines two robust tools: Gunicorn and Nginx.

  • Gunicorn is a Python WSGI HTTP server that serves Django applications.
  • Nginx is a powerful, high-performance web server and reverse proxy that handles client requests, static files, and load balancing.

Together, Gunicorn and Nginx form a proven stack that powers millions of production sites.
In this post, we’ll cover every step — from installation to optimization — for deploying Django with Gunicorn and Nginx.


1. Understanding the Deployment Architecture

Before we begin, it’s essential to understand how these components interact.

  1. Client (Browser/User) sends an HTTP request to your server.
  2. Nginx receives the request on port 80 (or 443 for HTTPS).
  3. Nginx forwards the request to Gunicorn, which runs your Django app.
  4. Gunicorn processes the request through Django’s WSGI interface.
  5. Django returns the response to Gunicorn.
  6. Gunicorn sends the response back to Nginx.
  7. Nginx finally delivers the response to the client.

This layered approach provides performance, flexibility, and security benefits:

  • Nginx can serve static and media files directly.
  • Gunicorn focuses solely on running your Django application.
  • Both can be restarted or scaled independently.

2. Preparing the Server Environment

Before installing Gunicorn or Nginx, make sure your server environment is ready.

Step 1: Update Your System

If you’re on Ubuntu or Debian-based systems:

sudo apt update
sudo apt upgrade -y

Step 2: Install Python and Virtual Environment Tools

Django projects should always run inside a virtual environment to isolate dependencies.

sudo apt install python3-pip python3-venv -y

Create and activate your virtual environment:

mkdir /home/ubuntu/django_project
cd /home/ubuntu/django_project
python3 -m venv venv
source venv/bin/activate

Step 3: Install Django and Dependencies

pip install django gunicorn

If your project already exists, clone it from your repository:

git clone https://github.com/yourusername/yourproject.git
cd yourproject
pip install -r requirements.txt

3. Configuring Django for Production

Before deployment, adjust a few Django settings to make your app production-ready.

Step 1: Update ALLOWED_HOSTS

In settings.py:

ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com', 'your.server.ip']

This ensures Django only serves requests from your actual domain or IP.

Step 2: Disable Debug Mode

Set:

DEBUG = False

Leaving DEBUG=True in production exposes sensitive information in error pages.

Step 3: Collect Static Files

In production, Django doesn’t serve static files automatically. Run:

python manage.py collectstatic

This command gathers all static assets into a single directory (by default STATIC_ROOT), ready for Nginx to serve efficiently.


4. Installing and Running Gunicorn

Now that Django is configured, let’s set up Gunicorn to serve your application.

Step 1: Install Gunicorn

If you haven’t already:

pip install gunicorn

Step 2: Test Gunicorn Manually

Navigate to your Django project directory (where manage.py is located) and run:

gunicorn projectname.wsgi

Replace projectname with your actual Django project’s name.

By default, Gunicorn will run on port 8000. Visit your server’s IP in a browser:

http://your_server_ip:8000

If you see your Django app, Gunicorn is working.

Press Ctrl + C to stop it.


5. Creating a Gunicorn Systemd Service

Instead of running Gunicorn manually, we’ll create a systemd service to manage it as a background process. This ensures Gunicorn starts automatically when the server boots and can be easily restarted.

Step 1: Create the Service File

Create a new file:

sudo nano /etc/systemd/system/gunicorn.service

Add the following configuration (adjust paths as needed):

[Unit]
Description=Gunicorn Daemon for Django Project
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/django_project/yourproject
ExecStart=/home/ubuntu/django_project/venv/bin/gunicorn --workers 3 --bind unix:/home/ubuntu/django_project/yourproject.sock projectname.wsgi:application

[Install]
WantedBy=multi-user.target

Step 2: Start and Enable the Service

sudo systemctl start gunicorn
sudo systemctl enable gunicorn

Check the status:

sudo systemctl status gunicorn

If it’s running correctly, Gunicorn is now serving your Django app through a Unix socket (.sock file) instead of a TCP port.


6. Installing and Configuring Nginx

Next, we’ll configure Nginx as a reverse proxy to forward web traffic to Gunicorn.

Step 1: Install Nginx

sudo apt install nginx -y

Step 2: Configure Nginx Server Block

Create a new configuration file for your site:

sudo nano /etc/nginx/sites-available/yourproject

Add this configuration:

server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
    root /home/ubuntu/django_project/yourproject;
}
location / {
    include proxy_params;
    proxy_pass http://unix:/home/ubuntu/django_project/yourproject.sock;
}
}

Step 3: Enable the Configuration

Link it to sites-enabled:

sudo ln -s /etc/nginx/sites-available/yourproject /etc/nginx/sites-enabled

Test Nginx configuration:

sudo nginx -t

If no errors appear, reload Nginx:

sudo systemctl restart nginx

Your Django app should now be accessible through your domain name.


7. Adjusting Firewall Rules

If you’re using Ubuntu’s UFW firewall, allow Nginx to handle web traffic.

Check available profiles:

sudo ufw app list

You should see something like:

Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS

Allow Nginx Full:

sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status

Now port 80 (HTTP) and port 443 (HTTPS, later for SSL) are open.


8. Setting Up Domain and DNS

For your site to be accessible by domain name, you need to:

  1. Register a domain (for example, through Namecheap or Google Domains).
  2. Point the domain’s A record to your server’s IP address.

Example DNS record:

Type: A
Host: @
Value: your_server_ip
TTL: 3600

Once propagated, visiting http://yourdomain.com should load your Django app via Nginx and Gunicorn.


9. Configuring HTTPS with Let’s Encrypt

Security is crucial in production. Using HTTPS protects data in transit and boosts SEO rankings. We’ll use Certbot, a free SSL certificate generator from Let’s Encrypt.

Step 1: Install Certbot

sudo apt install certbot python3-certbot-nginx -y

Step 2: Obtain and Install Certificate

Run the following command:

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Follow the interactive prompts. Once complete, Certbot automatically updates your Nginx configuration with SSL support.

Step 3: Auto-Renew Certificates

Let’s Encrypt certificates expire every 90 days. Add a cron job for automatic renewal:

sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer

You can manually test renewal with:

sudo certbot renew --dry-run

Now your site is accessible over HTTPS securely.


10. Testing the Full Deployment

At this point, your Django app runs behind Gunicorn and Nginx with HTTPS enabled.

Step 1: Check Gunicorn

sudo systemctl status gunicorn

Step 2: Check Nginx

sudo systemctl status nginx

Step 3: Visit the Website

Open your browser and navigate to:

https://yourdomain.com

You should see your Django application live with a secure padlock symbol.


11. Common Deployment Troubleshooting

Even small misconfigurations can cause issues during deployment. Let’s look at some frequent problems and solutions.

Problem 1: 502 Bad Gateway

Cause: Nginx cannot connect to Gunicorn.

Fix:

  • Ensure Gunicorn is running.
  • Verify the socket path in Nginx and Gunicorn configurations matches exactly.
  • Check file permissions for the socket file.

Problem 2: 403 Forbidden

Cause: Nginx doesn’t have permission to access static or media files.

Fix:
Ensure directories are readable by the www-data group:

sudo chown -R :www-data /home/ubuntu/django_project/yourproject
sudo chmod -R 755 /home/ubuntu/django_project/yourproject

Problem 3: Gunicorn Fails to Start

Check logs:

sudo journalctl -u gunicorn

Look for syntax errors, incorrect paths, or missing modules.

Problem 4: Static Files Not Loading

Confirm your Nginx static block matches the STATIC_ROOT directory from settings.py.


12. Scaling and Performance Optimization

Once your application is live, you can improve scalability and performance.

Use Multiple Gunicorn Workers

In your Gunicorn service file, adjust:

--workers 3

The general rule is (2 x CPU cores) + 1 workers for optimal concurrency.

Enable Gzip Compression in Nginx

Add to your Nginx config:

gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml+rss;
gzip_min_length 256;

Cache Static Files

You can instruct browsers to cache static assets for faster loading:

location /static/ {
root /home/ubuntu/django_project/yourproject;
expires 30d;
add_header Cache-Control "public";
}

Use a Process Manager

Tools like Supervisor or systemd (as used above) ensure Gunicorn automatically restarts if it crashes.


13. Deployment Automation (Optional)

Manual setup is fine for learning, but in larger projects, automation tools simplify deployment.

Options include:

  • Fabric – Python-based remote execution.
  • Ansible – Infrastructure as code automation.
  • Docker + Docker Compose – Containerized deployment.
  • GitHub Actions or Jenkins – Continuous Integration/Continuous Deployment (CI/CD).

For instance, a Docker-based deployment might include:

  • Gunicorn running in a container.
  • Nginx as a reverse proxy container.
  • PostgreSQL or MySQL database container.

Automation reduces human error and allows consistent deployments across environments.


14. Monitoring and Logging

Production apps need monitoring for stability and debugging.

View Logs

  • Gunicorn logs: sudo journalctl -u gunicorn
  • Nginx logs: /var/log/nginx/access.log /var/log/nginx/error.log

Set Up Monitoring Tools

  • Prometheus + Grafana: Server and app metrics.
  • Sentry: Real-time error tracking for Django.
  • UptimeRobot: External uptime monitoring.

These tools ensure you know when something breaks before users do.


15. Deployment Checklist

Before calling your deployment complete, review this checklist:

  1. Django DEBUG = False
  2. Proper ALLOWED_HOSTS
  3. Static files collected
  4. Gunicorn service configured
  5. Nginx reverse proxy configured
  6. SSL certificates installed
  7. UFW firewall enabled
  8. Logs tested
  9. Automatic renewal for certificates enabled
  10. Backup strategy in place

Meeting these ensures a secure, reliable production environment.


16. Why Use Gunicorn and Nginx Together

You might wonder: why not use Gunicorn alone?

  • Gunicorn alone: Handles Python requests well but isn’t optimized for static files, buffering, or SSL termination.
  • Nginx alone: Excellent web server, but it cannot directly run Django code.

By combining both:

  • Nginx serves static and media files directly.
  • Gunicorn executes Python code efficiently.
  • Nginx handles SSL, caching, and client load balancing.

This layered approach provides speed, security, and reliability.


17. Upgrading or Restarting Services

Whenever you make changes, restart both services:

sudo systemctl daemon-reload
sudo systemctl restart gunicorn
sudo systemctl restart nginx

To check for errors:

sudo systemctl status gunicorn
sudo nginx -t

18. Backups and Recovery

Regular backups are vital for production reliability.

Database Backup

For PostgreSQL:

pg_dump dbname > backup.sql

For SQLite:

cp db.sqlite3 backup.sqlite3

Static Files Backup

tar -czf static_backup.tar.gz /home/ubuntu/django_project/yourproject/static/

Automate backups using cron jobs to ensure disaster recovery.


19. Future Enhancements

After mastering the Gunicorn + Nginx setup, consider adding:

  • Celery for asynchronous task processing.
  • Redis for caching and session storage.
  • Load balancing across multiple Gunicorn workers using Nginx upstream blocks.
  • CDN integration for faster static delivery worldwide.

These tools extend performance and scalability as your traffic grows.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *