Deploying a Django App to Production With Gunicorn & Nginx
Deploy a Django app to production the real way: Gunicorn via systemd socket, Nginx reverse proxy, SSL with Certbot, log rotation, and a simple deploy script.
Drake Nguyen
Founder · System Architect
From Working to Shipped
Across the last nine parts we built a complete AI interview app — parsing, an agent pipeline, background processing, voice, and scoring. In part 9 it ran end to end on a laptop. This final part is about the gap almost every tutorial skips: getting it onto the internet, reliably, so real users can reach it. That means deploying a Django app to production with Gunicorn and Nginx, plus SSL, logging, and a repeatable deploy step.
One thing this guide assumes is already in place: a production database. SQLite won't do for a real deployment, so before deploying you'll want a proper database server set up — either PostgreSQL or MySQL, each covered in its own guide. With that ready, the rest of this walkthrough gets the app itself online.
The architecture is standard and battle-tested: Nginx sits at the front handling HTTPS and static files, and passes dynamic requests over a local socket to Gunicorn, which actually runs Django. Nothing exotic — just each tool doing the one job it's good at.
Running Gunicorn as a systemd Service
Gunicorn is the application server that runs Django in production. Rather than launch it by hand, we let systemd manage it — so it starts on boot, restarts on crash, and is controllable like any other service. The setup uses two units: a socket and a service.
The socket unit creates the Unix socket Nginx will talk to:
# /etc/systemd/system/aiinterviewer.socket
[Unit]
Description=aiinterviewer gunicorn socket
[Socket]
ListenStream=/run/aiinterviewer.sock
[Install]
WantedBy=sockets.target
The service unit runs Gunicorn itself, bound to that socket:
# /etc/systemd/system/aiinterviewer.service
[Unit]
Description=aiinterviewer gunicorn daemon
Requires=aiinterviewer.socket
After=network.target
[Service]
Group=www-data
WorkingDirectory=/var/www/ai_interviewer
ExecStart=/var/www/ai_interviewer/venv/bin/gunicorn \
--workers 3 \
--timeout 180 \
--log-level info \
--capture-output \
--bind unix:/run/aiinterviewer.sock \
--access-logfile /var/www/ai_interviewer/media/log/gunicorn/access.log \
--error-logfile /var/www/ai_interviewer/media/log/gunicorn/error.log \
ai_interviewer.wsgi:application
[Install]
WantedBy=multi-user.target
A couple of the Gunicorn flags are worth understanding. --workers 3 runs three worker processes so requests are handled in parallel; a common starting point is roughly two-times-CPU-cores plus one. --timeout 180 is generous here on purpose — this app makes slow AI calls, and a stricter default would kill long-running requests. See the Gunicorn settings docs for the full list.
With the units written, reload systemd and start the socket — Gunicorn launches automatically the first time a request hits it:
sudo systemctl daemon-reload
sudo systemctl start aiinterviewer.socket
sudo systemctl enable aiinterviewer.socket
# verify the socket exists and responds
sudo systemctl status aiinterviewer.socket
curl --unix-socket /run/aiinterviewer.sock localhost
Nginx as a Reverse Proxy
Nginx is the public-facing front door. It serves static files directly (fast, no Python involved) and forwards everything else to Gunicorn over the socket:
# /etc/nginx/sites-available/aiinterviewer
server {
listen 80;
server_name ai-interviewer.tech www.ai-interviewer.tech;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /var/www/ai_interviewer;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/aiinterviewer.sock;
}
}
Enable the site, test the config before reloading (a syntax error would otherwise take Nginx down), and restart:
sudo ln -s /etc/nginx/sites-available/aiinterviewer /etc/nginx/sites-enabled
sudo nginx -t # always test before reloading
sudo systemctl restart nginx
Serving static files from Nginx instead of Django is the key move here: Nginx is built for it, and it keeps your Python workers free to handle the requests that actually need them.
HTTPS With Certbot
A production app has no business running on plain HTTP — especially one handling résumés. Certbot gets a free Let's Encrypt certificate and rewires Nginx for HTTPS in one command:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d ai-interviewer.tech -d www.ai-interviewer.tech
Certbot edits the Nginx config to serve HTTPS, sets up the HTTP-to-HTTPS redirect, and installs a timer to auto-renew the certificate before it expires. It's one of the few "just works" experiences in server administration.
Firewall, Log Rotation, and Repeatable Deploys
Three finishing touches turn a working server into one you can actually operate.
A firewall, so only the ports you intend are reachable — SSH and web traffic, nothing else:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Log rotation, so Gunicorn's access and error logs don't quietly fill the disk. This keeps 90 days, compressed:
# /etc/logrotate.d/aiinterviewer
/var/www/ai_interviewer/media/log/gunicorn/*.log {
daily
rotate 90
compress
delaycompress
missingok
notifempty
copytruncate
}
The copytruncate option matters for Gunicorn specifically: it lets logs rotate without needing to signal or restart the process. Left unmanaged, logs are one of the most common ways a small server runs itself out of disk months later.
A deploy script, so shipping an update is one command instead of a remembered sequence:
# build.sh
#!/bin/bash
git pull origin master
source venv/bin/activate
python manage.py migrate --noinput
python manage.py collectstatic --noinput
sudo systemctl restart aiinterviewer
echo "Build completed!"
Every deploy now runs the same steps in the same order — pull, migrate, collect static, restart. Scripting it isn't just convenience; it removes the chance of forgetting a step at 2am, which is exactly when deploys tend to happen.
The App Is Live — and the Series Is Done
That's the whole journey: from an empty Django project to a live, HTTPS-secured AI interview app that parses résumés, runs an agent-driven interview by voice, scores the answers, and reports back — reachable by anyone, at a real domain. Every part was the real thing, built and shipped, not a toy.
You can try the finished product, free and with no sign-up, at ai-interviewer.tech. And if you'd rather start from the complete, production-ready source instead of rebuilding all ten parts yourself — the tuned prompts, the hardened pieces, and the full deployment included — it's packaged as the AI Mock Interview SaaS Starter Kit. Whether you build it or start from the source, you now know exactly what goes into a real AI product — end to end.