Applies to: LumaDock VPS • Ubuntu 22.04 LTS (Jammy)

This step-by-step guide shows how to deploy a Django application on a fresh LumaDock VPS. You’ll set up Python, a virtual environment, Gunicorn (WSGI), and Nginx as a reverse proxy—with optional PostgreSQL and free HTTPS using Let’s Encrypt.

Requirements

  • Fresh LumaDock VPS running Ubuntu 22.04 LTS (IPv4/IPv6 as preferred)
  • Root SSH access (or a sudo-enabled user)
  • A domain pointed to your VPS public IP (recommended for HTTPS)

1) Connect & update

ssh root@YOUR_SERVER_IP
apt update && apt -y upgrade

Tip: Create a sudo user instead of using root directly: adduser admin && usermod -aG sudo admin, then ssh admin@YOUR_SERVER_IP.

2) Install Python & build tools

apt install -y python3 python3-venv python3-pip python3-dev build-essential nginx git

3) Create a Django project & virtual environment

  1. Create a project directory (example: /opt/djangoapp):
    mkdir -p /opt/djangoapp && cd /opt/djangoapp
  2. Create & activate a virtualenv:
    python3 -m venv venv
    source venv/bin/activate
  3. Install Django & Gunicorn:
    pip install --upgrade pip
    pip install django gunicorn
  4. Create a new Django project (named myproject):
    django-admin startproject myproject .
  5. Allow your server host/domain in Django:
    # edit settings.py
    nano myproject/settings.py
    # set:
    # ALLOWED_HOSTS = ["YOUR_DOMAIN_OR_IP"]

4) Run with Gunicorn (test locally)

# from /opt/djangoapp with venv active
python manage.py migrate
python manage.py collectstatic --noinput
gunicorn --bind 127.0.0.1:8000 myproject.wsgi

Keep this running in one terminal (for a quick test). In another terminal/shell: curl -I http://127.0.0.1:8000 should return 200 OK.

5) Set up Nginx reverse proxy

  1. Create an Nginx server block (replace domain):
    nano /etc/nginx/sites-available/djangoapp
    server {
        listen 80;
        server_name YOUR_DOMAIN_OR_IP;
    
        # Max upload size (adjust as needed)
        client_max_body_size 20M;
    
        location /static/ {
            alias /opt/djangoapp/static/;
        }
    
        location / {
            proxy_pass http://127.0.0.1:8000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
  2. Enable and test config:
    ln -s /etc/nginx/sites-available/djangoapp /etc/nginx/sites-enabled/
    nginx -t && systemctl restart nginx
  3. Open your site: http://YOUR_DOMAIN_OR_IP. You should see the Django welcome page.

6) Create a systemd service for Gunicorn

This keeps your app running in the background and restarts it on boot.

nano /etc/systemd/system/gunicorn-djangoapp.service
[Unit]
Description=Gunicorn for Django (djangoapp)
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/djangoapp
Environment="PATH=/opt/djangoapp/venv/bin"
ExecStart=/opt/djangoapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 myproject.wsgi:application
Restart=always

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now gunicorn-djangoapp
systemctl status gunicorn-djangoapp

7) (Optional) Enable HTTPS with Let’s Encrypt

  1. Install Certbot:
    apt install -y certbot python3-certbot-nginx
  2. Issue a certificate (replace domain and email):
    certbot --nginx -d YOUR_DOMAIN -m you@example.com --agree-tos --redirect
  3. Verify auto-renew (a systemd timer is installed by default):
    systemctl list-timers | grep certbot

8) (Optional) Use PostgreSQL instead of SQLite

  1. Install PostgreSQL & dev headers:
    apt install -y postgresql postgresql-contrib libpq-dev
  2. Create DB & user:
    sudo -u postgres psql
    CREATE DATABASE mydb;
    CREATE USER myuser WITH PASSWORD 'StrongPassword123!';
    GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;
    \q
  3. Install driver & configure Django:
    source /opt/djangoapp/venv/bin/activate
    pip install psycopg2-binary
    # myproject/settings.py
    DATABASES = {
      "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "mydb",
        "USER": "myuser",
        "PASSWORD": "StrongPassword123!",
        "HOST": "127.0.0.1",
        "PORT": "5432",
      }
    }
  4. Migrate & reload:
    python manage.py migrate
    systemctl restart gunicorn-djangoapp

If PostgreSQL is remote, restrict firewall to your app IP and use SSL as needed.

9) Configure & collect static files

# myproject/settings.py
STATIC_URL = "static/"
STATIC_ROOT = "/opt/djangoapp/static"
python manage.py collectstatic --noinput
systemctl restart gunicorn-djangoapp
systemctl restart nginx

The Nginx block maps /static/ to /opt/djangoapp/static/.

Troubleshooting

Symptom Likely cause Fix
Bad Request (400) from Django ALLOWED_HOSTS missing your domain/IP Add your domain/IP to ALLOWED_HOSTS in settings.py, restart Gunicorn.
403/404 on static files Static not collected or Nginx path mismatch Set STATIC_ROOT, run collectstatic, verify Nginx location /static/ alias.
502 Bad Gateway Gunicorn not running / wrong socket/port Check systemctl status gunicorn-djangoapp, logs, ensure proxy_pass matches Gunicorn bind.
Connection refused on port 80/443 Nginx down or firewall blocking systemctl status nginx, open ports in cloud/OS firewall, ensure DNS points to VPS IP.

Appendix: Useful commands

Service & logs
journalctl -u gunicorn-djangoapp -e
journalctl -u nginx -e
systemctl restart gunicorn-djangoapp nginx
Open ports check
ss -tulpen | egrep ':80|:443|:8000'
curl -I http://127.0.0.1:8000
curl -I http://YOUR_DOMAIN_OR_IP
Was this answer helpful? 0 Users Found This Useful (0 Votes)