
How to Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu
Setting up Django with Postgres, Nginx, and Gunicorn on Ubuntu creates a production-ready web application stack that can handle real traffic while maintaining performance and scalability. This configuration combines Django’s powerful web framework with PostgreSQL’s robust database capabilities, Nginx’s efficient reverse proxy handling, and Gunicorn’s reliable WSGI server implementation. You’ll learn how to configure each component, connect them together, and troubleshoot common deployment issues that trip up even experienced developers.
How This Stack Works
The Django-Postgres-Nginx-Gunicorn stack follows a multi-tier architecture where each component handles specific responsibilities. Nginx serves as the front-facing web server, handling static files, SSL termination, and proxying dynamic requests to Gunicorn. Gunicorn acts as the WSGI server, managing Python processes and serving your Django application. PostgreSQL provides the database layer with ACID compliance and advanced querying capabilities.
Here’s the request flow:
- Client requests hit Nginx first
- Static files get served directly by Nginx
- Dynamic requests get proxied to Gunicorn via Unix socket
- Gunicorn spawns worker processes to handle Django application logic
- Django communicates with PostgreSQL for data operations
- Response travels back through the same chain
Component | Role | Performance Impact | Common Issues |
---|---|---|---|
Nginx | Reverse proxy, static files | ~10,000 concurrent connections | Proxy timeout, static file paths |
Gunicorn | WSGI server | 2x CPU cores + 1 workers optimal | Worker timeout, memory leaks |
PostgreSQL | Database | Connection pooling essential | Authentication, encoding issues |
Django | Web framework | Debug=False in production | Static files, database connections |
Prerequisites and System Setup
Before diving into the installation, ensure your Ubuntu server meets the basic requirements. This guide assumes Ubuntu 20.04 or later with sudo privileges and at least 1GB RAM for a basic setup. For production environments, consider using a VPS or dedicated server with adequate resources.
Update your system and install essential packages:
sudo apt update && sudo apt upgrade -y
sudo apt install python3-pip python3-dev python3-venv libpq-dev postgresql postgresql-contrib nginx curl
PostgreSQL Database Configuration
PostgreSQL installation creates a default user, but you’ll need to configure it properly for Django. Start by securing the installation and creating your application database:
sudo -u postgres psql
Inside the PostgreSQL shell, create your database and user:
CREATE DATABASE djangoapp;
CREATE USER djangouser WITH ENCRYPTED PASSWORD 'your_strong_password_here';
ALTER ROLE djangouser SET client_encoding TO 'utf8';
ALTER ROLE djangouser SET default_transaction_isolation TO 'read committed';
ALTER ROLE djangouser SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE djangoapp TO djangouser;
\q
Test the connection to ensure everything works:
psql -h localhost -U djangouser -d djangoapp
Common PostgreSQL issues include authentication failures and encoding problems. If you encounter “peer authentication failed”, edit the pg_hba.conf file:
sudo nano /etc/postgresql/12/main/pg_hba.conf
Change the authentication method for local connections from “peer” to “md5” and restart PostgreSQL:
sudo systemctl restart postgresql
Django Application Setup
Create a dedicated user for your Django application to improve security:
sudo adduser --system --group --home /opt/django djangoapp
sudo mkdir -p /opt/django/myproject
sudo chown djangoapp:djangoapp /opt/django/myproject
Switch to the Django user and set up the virtual environment:
sudo -u djangoapp bash
cd /opt/django/myproject
python3 -m venv venv
source venv/bin/activate
pip install django gunicorn psycopg2-binary
Create a new Django project or clone your existing one:
django-admin startproject myproject .
Configure your Django settings for production. Create a production settings file:
nano myproject/settings_prod.py
from .settings import *
import os
DEBUG = False
ALLOWED_HOSTS = ['your-domain.com', 'www.your-domain.com', 'server-ip-address']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'djangoapp',
'USER': 'djangouser',
'PASSWORD': 'your_strong_password_here',
'HOST': 'localhost',
'PORT': '',
}
}
STATIC_URL = '/static/'
STATIC_ROOT = '/opt/django/myproject/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = '/opt/django/myproject/media/'
# Security settings
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
Run initial migrations and collect static files:
python manage.py migrate --settings=myproject.settings_prod
python manage.py collectstatic --settings=myproject.settings_prod --noinput
Gunicorn Configuration
Gunicorn serves as the bridge between Nginx and Django. Create a Gunicorn configuration file:
nano /opt/django/myproject/gunicorn.conf.py
bind = "unix:/opt/django/myproject/gunicorn.sock"
workers = 3
worker_class = "sync"
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 50
timeout = 30
keepalive = 2
user = "djangoapp"
group = "djangoapp"
tmp_upload_dir = None
errorlog = "/opt/django/myproject/logs/gunicorn_error.log"
accesslog = "/opt/django/myproject/logs/gunicorn_access.log"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
Create the logs directory:
mkdir -p /opt/django/myproject/logs
Test Gunicorn manually before creating the service:
cd /opt/django/myproject
source venv/bin/activate
gunicorn --config gunicorn.conf.py myproject.wsgi:application
Create a systemd service file for Gunicorn:
sudo nano /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn instance to serve Django
After=network.target
[Service]
User=djangoapp
Group=www-data
WorkingDirectory=/opt/django/myproject
Environment="PATH=/opt/django/myproject/venv/bin"
ExecStart=/opt/django/myproject/venv/bin/gunicorn --config gunicorn.conf.py myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target
Enable and start the Gunicorn service:
sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl status gunicorn
Nginx Configuration
Nginx handles incoming requests and serves static files efficiently. Create a site configuration:
sudo nano /etc/nginx/sites-available/djangoapp
server {
listen 80;
server_name your-domain.com www.your-domain.com;
client_max_body_size 20M;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
alias /opt/django/myproject/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /opt/django/myproject/media/;
expires 7d;
}
location / {
include proxy_params;
proxy_pass http://unix:/opt/django/myproject/gunicorn.sock;
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;
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
Enable the site and test the configuration:
sudo ln -s /etc/nginx/sites-available/djangoapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Adjust file permissions for the socket file:
sudo usermod -a -G djangoapp www-data
sudo chmod 710 /opt/django/myproject/
SSL Configuration with Let’s Encrypt
Secure your application with free SSL certificates from Let’s Encrypt:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
Set up automatic renewal:
sudo crontab -e
Add this line:
0 12 * * * /usr/bin/certbot renew --quiet
Performance Optimization and Monitoring
Monitor your stack performance with these essential commands:
# Check Gunicorn workers
sudo systemctl status gunicorn
ps aux | grep gunicorn
# Monitor Nginx connections
sudo nginx -t
tail -f /var/log/nginx/access.log
# PostgreSQL performance
sudo -u postgres psql -c "SELECT * FROM pg_stat_activity;"
Optimize PostgreSQL for better performance:
sudo nano /etc/postgresql/12/main/postgresql.conf
Key settings to adjust based on your server specs:
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB
maintenance_work_mem = 64MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
Configuration | Development | Small Production | Large Production |
---|---|---|---|
Gunicorn Workers | 1 | 3-5 | (2 x CPU cores) + 1 |
PostgreSQL shared_buffers | 128MB | 256MB | 25% of RAM |
Nginx worker_processes | 1 | auto | auto |
Max connections | 100 | 200 | 500+ |
Common Issues and Troubleshooting
Here are the most frequent problems you’ll encounter and their solutions:
502 Bad Gateway Error:
- Check if Gunicorn service is running:
sudo systemctl status gunicorn
- Verify socket file permissions and Nginx user group membership
- Check Gunicorn error logs:
tail -f /opt/django/myproject/logs/gunicorn_error.log
Static Files Not Loading:
- Ensure
collectstatic
was run successfully - Check Nginx static file location configuration
- Verify file permissions on static directory
Database Connection Issues:
- Test PostgreSQL connection manually
- Check pg_hba.conf authentication settings
- Verify database credentials in Django settings
High Memory Usage:
- Reduce Gunicorn workers if memory-constrained
- Enable Gunicorn max_requests to recycle workers
- Monitor for memory leaks in Django application
Essential debugging commands:
# Check all services status
sudo systemctl status nginx gunicorn postgresql
# Monitor real-time logs
sudo journalctl -u gunicorn -f
tail -f /var/log/nginx/error.log
# Test database connectivity
sudo -u djangoapp psql -h localhost -U djangouser -d djangoapp -c "SELECT version();"
# Check socket file
ls -la /opt/django/myproject/gunicorn.sock
Real-World Use Cases and Best Practices
This stack configuration excels in several scenarios:
- E-commerce platforms: Handle high traffic with PostgreSQL’s transaction safety
- Content management systems: Nginx efficiently serves media files while Django handles dynamic content
- API backends: Gunicorn’s worker model scales well for API-heavy applications
- Data-driven applications: PostgreSQL’s advanced querying supports complex analytics
Production best practices include:
- Use environment variables for sensitive configuration
- Implement proper logging and monitoring
- Set up database backups with pg_dump automation
- Configure logrotate for application logs
- Use connection pooling for high-traffic applications
- Implement health checks for all services
For comparison with alternatives, this stack offers better performance than Apache + mod_wsgi for most Django applications, while being more straightforward than containerized deployments. The combination provides excellent stability and is widely supported in production environments.
Additional resources for deeper configuration can be found in the Django deployment documentation, Gunicorn configuration guide, and Nginx documentation.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.