Deploy Django on a Hostinger VPS (Gunicorn, Nginx, PostgreSQL)
A production Django setup on Ubuntu: a virtualenv, PostgreSQL, Gunicorn under systemd, Nginx serving static files, the security settings Django needs behind a proxy, and free HTTPS.
What you'll end up with
Nginx takes the request, serves static and media files straight from disk, and passes everything else over a Unix socket to Gunicorn, which runs your Django app. systemd keeps Gunicorn alive. PostgreSQL holds the data on the same machine.
This assumes Ubuntu 24.04 with a secured sudo user and an A record already pointing at the server.
Step 1 — System packages and the database
Ubuntu 24.04's Python 3.12 is supported by current Django releases.
sudo apt update
sudo apt install -y python3-venv python3-dev build-essential libpq-dev \
postgresql nginx gitsudo -u postgres psql <<'SQL'
CREATE USER myproject WITH PASSWORD 'use-a-long-random-password';
CREATE DATABASE myproject OWNER myproject;
SQLStep 2 — Code and virtualenv
sudo mkdir -p /var/www && sudo chown $USER:$USER /var/www
cd /var/www
git clone https://github.com/you/myproject.git
cd myproject
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt gunicorn "psycopg[binary]"Step 3 — Production settings
Read secrets from the environment, not from `settings.py`. The proxy settings matter: without them Django thinks every request arrived over plain HTTP, and secure cookies and CSRF checks break.
import os
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = False
ALLOWED_HOSTS = ["example.com", "www.example.com"]
CSRF_TRUSTED_ORIGINS = ["https://example.com", "https://www.example.com"]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "myproject",
"USER": "myproject",
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": "127.0.0.1",
"PORT": "5432",
}
}
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
# Behind Nginx terminating HTTPS
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = TrueDJANGO_SECRET_KEY=generate-a-long-random-string
DB_PASSWORD=use-a-long-random-passwordset -a; source .env; set +a
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py createsuperuser
python manage.py check --deployStep 4 — Gunicorn under systemd
A common starting point for workers is two per CPU core plus one, so 3 on KVM 1 and 5 on KVM 2. `RuntimeDirectory` makes systemd create `/run/gunicorn` for the socket on every start.
[Unit]
Description=Gunicorn for myproject
After=network.target postgresql.service
[Service]
User=deploy
Group=www-data
WorkingDirectory=/var/www/myproject
EnvironmentFile=/var/www/myproject/.env
RuntimeDirectory=gunicorn
ExecStart=/var/www/myproject/.venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/gunicorn/myproject.sock \
--access-logfile - \
myproject.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now gunicorn
sudo systemctl status gunicorn
journalctl -u gunicorn -f # live logsStep 5 — Nginx
Static and media files never touch Python; Nginx serves them directly.
server {
listen 80;
server_name example.com www.example.com;
client_max_body_size 20M;
location /static/ {
alias /var/www/myproject/staticfiles/;
expires 30d;
}
location /media/ {
alias /var/www/myproject/media/;
}
location / {
proxy_pass http://unix:/run/gunicorn/myproject.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;
}
}sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.comRedeploying
The same pattern as every other guide here: a script that GitHub Actions can call.
#!/usr/bin/env bash
set -euo pipefail
cd /var/www/myproject
set -a; source .env; set +a
git pull origin main
.venv/bin/pip install -r requirements.txt
.venv/bin/python manage.py migrate --noinput
.venv/bin/python manage.py collectstatic --noinput
sudo systemctl restart gunicorn
echo "Deployed at $(date -Iseconds)"Frequently asked questions
Can I run Django on Hostinger shared hosting?
Not well. Shared plans are built for PHP and don't let you run a long-lived Python process like Gunicorn. Use a VPS for Django.
Which VPS plan do I need for Django?
KVM 1 handles a typical Django app with PostgreSQL. Choose KVM 2 if you add Celery workers and Redis, or expect steady traffic.
Why does Django say 'Bad Request (400)'?
The domain isn't in ALLOWED_HOSTS. Add every hostname you serve, including www, and restart Gunicorn.
Where do I see errors when DEBUG is off?
Run `journalctl -u gunicorn` for app errors and check /var/log/nginx/error.log for proxy errors. Setting up ADMINS with email, or an error tracker such as Sentry, saves a lot of digging.
Ready to save an extra 20% with coupon WEBHOST2026?
Get my discount link →Disclosure: links to Hostinger on this page are referral links. If you buy through them we may earn a commission, at no extra cost to you. It doesn't change what we recommend. About us
Keep reading
Secure a new Hostinger VPS: the 10-minute checklist
SSH keys, no root or password logins, a firewall, Fail2ban, automatic security updates and snapshots. The minimum to do before a fresh VPS goes on the internet, with copy-paste commands.
TutorialsHow to point a domain to your Hostinger VPS (A records, step by step)
Find your VPS IP, add the right A and AAAA records whether your domain is at Hostinger, GoDaddy or Namecheap, and check propagation before you ask Let's Encrypt for a certificate.
TutorialsAuto-deploy to a Hostinger VPS with GitHub Actions (push to deploy)
Every push to main deploys your app to the VPS. A dedicated SSH key, pinned host keys, a locked-down forced command and a workflow under 30 lines, with no third-party actions.