Deployment
How to Deploy a Django App on Ubuntu with Nginx and Gunicorn
A step-by-step guide to deploying a Django application on Ubuntu with Gunicorn as the WSGI server, Nginx as the reverse proxy, SSL with Certbot, PostgreSQL for the database, and systemd for process management.
Prerequisites and Server Setup
This guide deploys a Django application on Ubuntu 24.04 with Gunicorn as the WSGI application server, Nginx as the reverse proxy, PostgreSQL as the database, SSL certificates from the ACME certificate authority via Certbot, and systemd for process management. You need an Ubuntu server (AWS EC2, DigitalOcean, Hetzner), a domain name, and your Django project in a git repository.
SSH into your server and update system packages. Install Python 3.12, pip, virtualenv, and PostgreSQL.
sudo apt update && sudo apt upgrade -y
sudo apt install python3.12 python3.12-venv python3-pip postgresql postgresql-contrib nginx -yConfigure PostgreSQL
Create a PostgreSQL database and user for your Django application.
sudo -u postgres psql
# Inside psql:
CREATE DATABASE myapp;
CREATE USER myapp_user WITH PASSWORD 'strong_password_here';
GRANT ALL PRIVILEGES ON DATABASE myapp TO myapp_user;
qDeploy the Application Code
Clone your project, create a virtual environment, install dependencies, and configure environment variables.
# Clone the project
git clone https://github.com/youruser/myapp.git /home/ubuntu/myapp
cd /home/ubuntu/myapp
# Create and activate virtual environment
python3.12 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
pip install gunicorn psycopg2-binary
# Create the environment file
nano .env
# Add production environment variables:
# DEBUG=False
# SECRET_KEY=your-very-long-random-secret-key
# ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
# DATABASE_URL=postgresql://myapp_user:strong_password_here@localhost/myapp
# Run migrations
python manage.py migrate
# Collect static files
python manage.py collectstatic --noinput
# Create a superuser
python manage.py createsuperuserConfigure Gunicorn with systemd
Create a systemd service file so Gunicorn starts automatically and restarts on failure.
sudo nano /etc/systemd/system/gunicorn.service[Unit]
Description=Gunicorn daemon for myapp
After=network.target
[Service]
User=ubuntu
Group=ubuntu
WorkingDirectory=/home/ubuntu/myapp
EnvironmentFile=/home/ubuntu/myapp/.env
ExecStart=/home/ubuntu/myapp/venv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock myapp.wsgi:application
Restart=on-failure
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable gunicorn
sudo systemctl start gunicorn
sudo systemctl status gunicornConfigure Nginx
Create an Nginx site configuration that proxies requests to the Gunicorn Unix socket and serves static files directly.
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/myapp;
}
location /media/ {
root /home/ubuntu/myapp;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}# Enable the site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxEnable SSL with Certbot
Install Certbot and obtain a free SSL certificate. Point your domain DNS A record to the server IP first.
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Follow prompts and choose option 2 to redirect HTTP to HTTPS
# Test auto-renewal
sudo certbot renew --dry-runDeployment Update Workflow
For subsequent deployments, pull the latest code, run migrations, collect static files, and restart Gunicorn.
cd /home/ubuntu/myapp
git pull origin main
source venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput
sudo systemctl restart gunicornProduction Django Settings Checklist
Verify these settings before going live.
- DEBUG = False in production — never deploy with DEBUG = True
- ALLOWED_HOSTS must include your domain name exactly
- SECRET_KEY must be a long random string unique to production — never reuse the development key
- STATIC_ROOT = BASE_DIR / staticfiles and collectstatic run during each deploy
- CSRF_COOKIE_SECURE = True and SESSION_COOKIE_SECURE = True for HTTPS-only cookies
- SECURE_HSTS_SECONDS = 31536000 after confirming HTTPS works correctly
- Configure Django logging to write to a file or to stdout (captured by systemd journald)
- Use environment variables for DATABASE_URL and SECRET_KEY — never hardcode secrets
FAQ
How many Gunicorn workers should I configure?
The recommended formula is 2 * num_cpu_cores + 1. For a t3.small (2 vCPUs), that is 5 workers. For a t3.medium (2 vCPUs), 5 workers. Each worker is a separate process and uses memory. For memory-constrained servers, reduce the worker count and monitor. Use gunicorn with --worker-class=gevent or --worker-class=uvicorn.workers.UvicornWorker for async request handling if your Django views use async def.
Why use a Unix socket instead of a TCP port for Gunicorn?
Unix sockets are faster than TCP sockets for local inter-process communication because they bypass the TCP/IP stack. Since Nginx and Gunicorn are on the same server, a Unix socket is the correct choice. TCP ports (like 8000) are needed only when Nginx and Gunicorn are on separate servers (rare) or when using a tool that requires a port.
Should I use Gunicorn or uWSGI for Django?
Gunicorn is simpler to configure and is the default recommendation in the Django documentation. uWSGI has more configuration options and can be faster in specific benchmarks, but the configuration complexity is significantly higher. For most Django deployments, Gunicorn is the correct choice. Use uWSGI only if you have a specific requirement it meets that Gunicorn does not.
How do I run Celery alongside Django on the same server?
Create a second systemd service file for Celery, similar to the Gunicorn service but with the ExecStart pointing to celery -A myapp worker. Configure Redis or RabbitMQ as the Celery broker. Run both systemd services on the same server. For production at scale, run Celery workers on separate servers so task processing load does not compete with web request processing.
Related free tools
If you want to turn this topic into action, use one of ShortIQ's free tools for campaign planning, UTM structure, or QR distribution.
Continue Reading
Explore more guides on link shortener SaaS strategy, Bitly alternatives, and white label link management.
Free newsletter
Get new guides in your inbox
We publish practical guides on dev tooling, prompt engineering, marketing workflows, and deployment. No fluff — straight to the point.
No spam. Unsubscribe any time.
Was this article helpful?
Tell us if this guide solved the problem or what was still missing. We use this to improve the blog and only follow up if you explicitly allow it.