## Why Two Servers? Nginx handles static files, SSL termination, and connection buffering far more efficiently than Gunicorn. Gunicorn focuses on running Python — together they handle thousands of concurrent users. ## gunicorn.conf.py ```python import multiprocessing bind = '0.0.0.0:8000' workers = multiprocessing.cpu_count() * 2 + 1 worker_class = 'sync' timeout = 120 keepalive = 5 max_requests = 1000 max_requests_jitter = 100 accesslog = '-' errorlog = '-' ``` ## Nginx Config ```nginx upstream django_app { server 127.0.0.1:8000; } server { listen 80; server_name yourdomain.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name yourdomain.com; ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; location /static/ { alias /var/www/app/staticfiles/; expires 1y; } location / { proxy_pass http://django_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } } ``` ## Worker Count Rule of Thumb `workers = CPU cores × 2 + 1` For CPU-bound apps use fewer workers; for I/O-bound (most Django apps) this formula works well.