Deploying a Django application doesn't have to mean expensive platform-as-a-service bills or complex container orchestration. With a Hetzner cloud server and [DeployHQ](https://www.deployhq.com), you can run a production Django app for under €5/month — with automated Git-based deployments, zero-downtime releases, and proper production security.

This guide walks through the complete process: provisioning a Hetzner server, configuring it for Django with PostgreSQL, Gunicorn, and Nginx, and setting up [DeployHQ](https://www.deployhq.com) for push-to-deploy automation.

* * *

## What This Setup Costs

One of the biggest advantages of Hetzner is the pricing. Here's what a production Django deployment actually costs:

| Component | Monthly Cost |
| --- | --- |
| Hetzner CX22 (2 vCPU, 4 GB RAM, 40 GB SSD) | €4.35 |
| IPv4 address | €0.50 |
| DeployHQ Free plan (1 project) | €0 |
| **Total** | **€4.85/month** |

For comparison, a comparable setup on DigitalOcean costs $12/month, AWS Lightsail costs $10/month, and managed platforms like Railway or Render start at $7/month per service (and you'll need at least two — web + database).

If you have multiple Django projects, DeployHQ's Pro plan at €19/month covers 10 projects with unlimited deployments and users.

* * *

## Prerequisites

Before starting, you'll need:

- A Django application in a Git repository (GitHub, GitLab, or Bitbucket)
- A `requirements.txt` file in your repository
- A [Hetzner Cloud account](https://www.hetzner.com/cloud/)
- A [DeployHQ account](https://www.deployhq.com/signup) (free tier works)
- An SSH key pair on your local machine

* * *

## Step 1: Create a Hetzner Cloud Server

Log in to the [Hetzner Cloud Console](https://console.hetzner.cloud/) and create a new project.

Click **Add Server** and configure:

- **Location** : Choose the region closest to your users (e.g., Helsinki, Falkenstein, or Ashburn)
- **Image** : Ubuntu 24.04
- **Type** : Shared vCPU, x86, **CX22** (2 vCPU, 4 GB RAM, 40 GB SSD)
- **Networking** : Leave both IPv4 and IPv6 enabled
- **SSH Keys** : Add your public SSH key
- **Name** : `django-production`

Click **Create & Buy Now**. Your server will be ready in about 30 seconds.

Test the connection:

```
ssh root@YOUR_SERVER_IP
```

* * *

## Step 2: Secure the Server

Never run your application as root. Create a dedicated deploy user and lock down SSH access.

```
# Create a deploy user
adduser --disabled-password --gecos "" deploy

# Give deploy user sudo access (for service restarts)
usermod -aG sudo deploy
echo "deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart django-app, /usr/bin/systemctl reload nginx" > /etc/sudoers.d/deploy

# Set up SSH for the deploy user
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

# Disable root SSH login and password authentication
sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd
```

Enable the firewall:

```
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable
```

From this point forward, connect as the deploy user:

```
ssh deploy@YOUR_SERVER_IP
```

* * *

## Step 3: Install Python, PostgreSQL, and Nginx

```
sudo apt update && sudo apt upgrade -y

# Python and build dependencies
sudo apt install -y python3 python3-pip python3-venv build-essential libpq-dev

# PostgreSQL
sudo apt install -y postgresql postgresql-contrib

# Nginx
sudo apt install -y nginx

# Certbot for SSL
sudo apt install -y certbot python3-certbot-nginx
```

### Create the PostgreSQL Database

```
sudo -u postgres psql <<EOF
CREATE DATABASE django_db;
CREATE USER django_user WITH PASSWORD 'GENERATE_A_STRONG_PASSWORD_HERE';
ALTER ROLE django_user SET client_encoding TO 'utf8';
ALTER ROLE django_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE django_user SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE django_db TO django_user;
\q
EOF
```

Replace `GENERATE_A_STRONG_PASSWORD_HERE` with a real password. You can generate one with:

```
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
```

* * *

## Step 4: Prepare Your Django Project

Make sure your Django project has these production-ready configurations.

### requirements.txt

Your `requirements.txt` should include:

```
django>=5.1,<5.2
gunicorn
psycopg[binary]
python-decouple
whitenoise
dj-database-url
```

### settings.py — Production Configuration

Use `python-decouple` to load sensitive settings from environment variables:

```
from decouple import config, Csv
import dj_database_url

# Security
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())

# Database
DATABASES = {
    'default': dj_database_url.config(
        default=config('DATABASE_URL')
    )
}

# Static files with WhiteNoise
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware', # Add after SecurityMiddleware
    # ... rest of your middleware
]

STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STORAGES = {
    'staticfiles': {
        'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage',
    },
}

# HTTPS settings (enable once SSL is configured)
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=True, cast=bool)
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', cast=Csv(), default='')
```

### .env File (Do NOT Commit This)

Create a `.env` file for local development:

```
SECRET_KEY=your-local-dev-secret-key
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
DATABASE_URL=sqlite:///db.sqlite3
SECURE_SSL_REDIRECT=False
CSRF_TRUSTED_ORIGINS=http://localhost:8000
```

Add `.env` to your `.gitignore`:

```
.env
staticfiles/
```

* * *

## Step 5: Set Up the Application Directory

On your server, create the directory structure:

```
sudo mkdir -p /var/www/django-app
sudo chown deploy:deploy /var/www/django-app
```

Create the production `.env` file on the server:

```
cat > /var/www/django-app/.env << 'EOF'
SECRET_KEY=your-generated-production-secret-key
DEBUG=False
ALLOWED_HOSTS=your-domain.com,YOUR_SERVER_IP
DATABASE_URL=postgres://django_user:YOUR_DB_PASSWORD@localhost:5432/django_db
SECURE_SSL_REDIRECT=False
CSRF_TRUSTED_ORIGINS=https://your-domain.com
EOF

chmod 600 /var/www/django-app/.env
```

Set `SECURE_SSL_REDIRECT=False` initially — we'll enable it after configuring SSL.

* * *

## Step 6: Configure Gunicorn as a Systemd Service

Create a systemd service file so Gunicorn starts automatically and restarts on failure:

```
sudo tee /etc/systemd/system/django-app.service > /dev/null << 'EOF'
[Unit]
Description=Django Application (Gunicorn)
After=network.target postgresql.service

[Service]
User=deploy
Group=deploy
WorkingDirectory=/var/www/django-app
ExecStart=/var/www/django-app/venv/bin/gunicorn \
    --workers 3 \
    --bind unix:/var/www/django-app/gunicorn.sock \
    --access-logfile /var/log/django-app/access.log \
    --error-logfile /var/log/django-app/error.log \
    your_project.wsgi:application
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo mkdir -p /var/log/django-app
sudo chown deploy:deploy /var/log/django-app
sudo systemctl daemon-reload
sudo systemctl enable django-app
```

Replace `your_project` with your actual Django project name (the directory containing `wsgi.py`).

* * *

## Step 7: Configure Nginx

```
sudo tee /etc/nginx/sites-available/django-app > /dev/null << 'EOF'
server {
    listen 80;
    server_name your-domain.com;

    client_max_body_size 10M;

    location /static/ {
        alias /var/www/django-app/staticfiles/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location / {
        proxy_pass http://unix:/var/www/django-app/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;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/django-app /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
```

### Add SSL with Let's Encrypt

Once your domain's DNS points to the server:

```
sudo certbot --nginx -d your-domain.com
```

Certbot will automatically modify the Nginx configuration to handle HTTPS and set up auto-renewal.

After SSL is working, update the `.env` on the server:

```
sed -i 's/SECURE_SSL_REDIRECT=False/SECURE_SSL_REDIRECT=True/' /var/www/django-app/.env
```

* * *

## Step 8: Set Up DeployHQ

Now for the automated deployment pipeline. [DeployHQ](https://www.deployhq.com) connects your Git repository to your server so every push deploys automatically.

### Create a Project

1. Log in to [DeployHQ](https://www.deployhq.com/signup) and click **New Project**
2. Connect your GitHub, GitLab, or Bitbucket repository

### Add Your Server

Go to **Servers** → **New Server** :

- **Name** : Production
- **Protocol** : SSH/SFTP
- **Hostname** : Your Hetzner server IP
- **Port** : 22
- **Username** : `deploy`
- **Authentication** : SSH Key (add DeployHQ's public key to `/home/deploy/.ssh/authorized_keys`)
- **Deployment Path** : `/var/www/django-app`
- **Zero-downtime deployments** : **Enabled**

With [zero-downtime deployments](https://deployhq.com/features/zero-downtime-deployments) enabled, [DeployHQ](https://www.deployhq.com) deploys to a new release directory and atomically switches a symlink when everything is ready. Your Django app stays live throughout the deploy.

**Important** : With zero-downtime enabled, your app is served from `/var/www/django-app/current`. Update the Gunicorn service and Nginx configuration paths accordingly:

```
# Update Gunicorn WorkingDirectory and ExecStart
sudo sed -i 's|/var/www/django-app|/var/www/django-app/current|g' /etc/systemd/system/django-app.service
sudo sed -i 's|/var/www/django-app/current/current|/var/www/django-app/current|g' /etc/systemd/system/django-app.service

# Update Nginx static path
sudo sed -i 's|/var/www/django-app/staticfiles|/var/www/django-app/current/staticfiles|g' /etc/nginx/sites-available/django-app
sudo sed -i 's|/var/www/django-app/gunicorn|/var/www/django-app/current/gunicorn|g' /etc/nginx/sites-available/django-app

sudo systemctl daemon-reload
sudo nginx -t && sudo systemctl reload nginx
```

### Configure the Build Pipeline

Go to **Build Pipeline** → **New Command**. DeployHQ's [build pipeline](https://deployhq.com/blog/build-pipelines-in-deployhq-streamline-your-deployment-workflow) runs commands on DeployHQ's servers before transferring files — so your production server doesn't need build tools.

Add a build command:

```
pip install -r requirements.txt -t vendor/
```

This installs dependencies into a `vendor/` directory that gets deployed alongside your code. Enable **Cache** for `vendor/**` to speed up subsequent deployments.

### Add SSH Post-Deploy Commands

Go to **SSH Commands** and add a command that runs **after** deployment:

```
cd /var/www/django-app/current
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt --quiet
python3 manage.py migrate --noinput
python3 manage.py collectstatic --noinput
sudo /usr/bin/systemctl restart django-app
```

### Use Config Files for the .env

Instead of manually managing the `.env` file on the server, use DeployHQ's [config files](https://deployhq.com/support/projects/configuration-files) to inject it during deployment. Go to **Config Files** → **New Config File** :

- **Path** : `.env`
- **Content** : Your production environment variables

This way, the `.env` file is deployed automatically with every release, and you can manage different values for staging vs production servers without touching the server manually.

### Enable Automatic Deployments

In your project settings, enable [automatic deployments](https://deployhq.com/blog/how-to-set-up-git-pull-deployments-with-deployhq) for the `main` branch. Now the workflow is:

```
git push origin main → DeployHQ detects push → Deploys files → Runs migrations → Restarts Gunicorn → Done
```

* * *

## Step 9: Deploy and Verify

Trigger your first deployment from the [DeployHQ](https://www.deployhq.com) dashboard. Once it completes, verify:

```
# Check the app is running
sudo systemctl status django-app

# Check Nginx is proxying correctly
curl -I http://your-domain.com

# Check the Django logs
tail -f /var/log/django-app/error.log
```

Visit your domain in a browser. Your Django app should be live.

* * *

## Production Checklist

Before considering your deployment production-ready, verify these:

- [] `DEBUG = False` in production
- [] `SECRET_KEY` is unique and not committed to Git
- [] Database password is strong and stored in `.env`
- [] SSL certificate is active (`https://` works)
- [] `SECURE_SSL_REDIRECT = True`
- [] SSH root login is disabled
- [] Firewall allows only SSH, HTTP, and HTTPS
- [] Gunicorn runs as a non-root user
- [] Static files are served by Nginx (not Django)
- [] Database backups are scheduled
- [] Log rotation is configured

* * *

## Scaling Up

As your Django app grows, Hetzner makes it easy to scale:

| Need | Solution | Cost |
| --- | --- | --- |
| More CPU/RAM | Upgrade to CX32 (4 vCPU, 8 GB) | ~€8.50/mo |
| More storage | Add a Hetzner Volume | From €0.052/GB/mo |
| Database performance | Move PostgreSQL to a separate server | ~€4.35/mo |
| Multiple apps | Add more servers in DeployHQ (Pro plan) | €19/mo for 10 projects |
| Global CDN | Add Cloudflare (free tier) | €0 |

Even at the higher end — a CX32 server, separate database, and [DeployHQ](https://www.deployhq.com) Pro — you're looking at under €30/month for a setup that handles significant traffic.

* * *

## Common Questions

### Why Hetzner over AWS or DigitalOcean?

Price. Hetzner's CX22 (2 vCPU, 4 GB RAM) costs €4.35/month. The equivalent on DigitalOcean is $12/month, and on AWS it's $10+/month for Lightsail. For a Django app that doesn't need AWS-specific services, Hetzner gives you more resources for less money.

### Can I deploy multiple Django apps to one server?

Yes. Create separate directories, Gunicorn services, and Nginx server blocks for each app. In [DeployHQ](https://www.deployhq.com), add each app as a separate server entry pointing to different deployment paths. This same approach works for other Python applications too — for example, you can follow a similar pattern when [deploying Odoo on Ubuntu](https://deployhq.com/blog/deploying-odoo-on-ubuntu-with-deployhq). The same Hetzner + [DeployHQ](https://www.deployhq.com) pattern also works for PHP CMS stacks — see [how to deploy Craft CMS on Hetzner Cloud](https://www.deployhq.com/blog/deploy-craftcms-via-deployhq-on-hetzner-cloud) for a non-Python example.

### What about managed databases?

Hetzner doesn't offer managed PostgreSQL (yet). If you want managed databases, consider using a separate service like Supabase (free tier) or Neon (free tier) while keeping your compute on Hetzner.

### How do I handle Django Celery workers?

Add a second systemd service for the Celery worker, similar to the Gunicorn service. In your [DeployHQ](https://www.deployhq.com) SSH command, restart both services after deployment.

### What if a deployment fails?

With [zero-downtime deployments](https://deployhq.com/features/zero-downtime-deployments) enabled, a failed deployment doesn't affect your live site — the symlink still points to the previous release. Fix the issue, push again, and [DeployHQ](https://www.deployhq.com) will retry. You can also roll back to any previous deployment from the [DeployHQ](https://www.deployhq.com) dashboard.

* * *

## The Bottom Line

A production Django deployment on Hetzner with [DeployHQ](https://www.deployhq.com) costs under €5/month and takes about an hour to set up. You get a real server with full control, automated Git-based deployments, zero-downtime releases, and room to scale — all without the complexity of Kubernetes or the cost of managed platforms.

[Start your free](https://www.deployhq.com/signup)[DeployHQ](https://www.deployhq.com) trial and connect it to your Hetzner server — no credit card required.

