Commands to setup a secure django website on ubuntu

, 20 Aug 2026 /
Nginx, postgresql, django and Let's Encrypt logos.

TLDR: How to setup a secure django website on ubuntu.

Add limited user accounts

[9]: Linode - set up and secure / #add-a-limited-user-account

When logged in as root add an appuser and a dbuser.

adduser appuser
adduser appuser sudo
sudo chown appuser:appuser  -R /home/appuser/

Continue logged in as the newly created appuser.

sudo apt update
sudo apt upgrade

sudo apt install python3-pip python3-dev python3-venv

sudo apt install postgresql postgresql-contrib libpq-dev 

sudo apt install nginx
sudo su - postgres

createdb devdb

createuser -P dbuser

psql devdb

ALTER ROLE dbuser SET client_encoding TO 'utf8';

ALTER ROLE dbuser SET default_transaction_isolation TO 'read committed';

ALTER ROLE dbuser SET timezone TO 'UTC';

GRANT ALL PRIVILEGES ON DATABASE devdb TO dbuser;

GRANT ALL PRIVILEGES ON SCHEMA public TO dbuser;

\q

exit

Security check.

For maximum security, ... this is an important security consideration.

[2]: How to secure postgresql on an ubuntu vps

sudo nano /etc/postgresql/18/main/pg_hba.conf

local all postgres peer

local all all peer

host all all 127.0.0.1/32 scram-sha-256

host all all ::1/128 scram-sha-256

To access PostgreSQL from a remote location, consider using SSH to connect to the database machine and then using a local connection to the database from there.

[2]: How to secure postgresql on an ubuntu vps

mkdir ~/devsite
cd ~/devsite

python3 -V

python3 -m venv env

source env/bin/activate

pip install --upgrade pip
cd ~/devsite

source env/bin/activate

pip install django gunicorn uvicorn[standard] uvicorn_worker psycopg[binary]

django-admin startproject devsite .

deactivate
nano ~/devsite/devsite/settings.py
SECRET_KEY = ''

with open('/etc/secret_key.txt') as f:
    SECRET_KEY = f.read().strip()

with open('/etc/database_name.txt') as f:
    db_name = f.read().strip()

with open('/etc/database_user.txt') as f:
    db_user = f.read().strip()

with open('/etc/database_password.txt') as f:
    db_password = f.read().strip()

DEBUG = False

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': db_name,
        'USER': db_user,
        'PASSWORD': db_password,
        'HOST': 'localhost',
        'PORT': '',    # Set to empty string for default.
        }
    }
sudo nano /etc/secret_key.txt
sudo nano /etc/database_name.txt
sudo nano /etc/database_user.txt
sudo nano /etc/database_password.txt
nano ~/devsite/devsite/settings.py
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'static/'

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media/'

Creating systemd Socket and Service Files for Gunicorn

[6]: How to set up django with postgres, nginx and gunicorn on ubuntu 20.04

sudo nano /etc/systemd/system/gunicorn.socket
[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target
sudo nano /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn ASGI service for Django
Requires=gunicorn.socket
After=network.target

[Service]
User=appuser
Group=appuser
WorkingDirectory=/home/appuser/devsite
ExecStart=/home/appuser/devsite/env/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          -k uvicorn_worker.UvicornWorker \
          devsite.asgi:application

[Install]
WantedBy=multi-user.target
sudo systemctl start gunicorn.socket

sudo systemctl start gunicorn.service

sudo systemctl enable gunicorn.socket

sudo systemctl status gunicorn.socket

sudo systemctl status gunicorn.service
sudo nano /etc/nginx/sites-available/devsite
server {
    server_name devsite.com;

    location = /favicon.ico { access_log off; log_not_found off; }

    location /static/ {
        alias /home/appuser/devsite/static/;
        add_header Cache-Control "no-cache";
    }
    location /media/ {
        alias /home/appuser/devsite/media/;
        add_header Cache-Control "no-cache";
    }
    location /robots.txt {
        alias /home/appuser/devsite/robots.txt;
    }

    location / {
        proxy_pass http://unix:/run/gunicorn.sock;
        proxy_set_header Host $http_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;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
sudo ln -s /etc/nginx/sites-available/devsite /etc/nginx/sites-enabled/

sudo nginx -t

sudo systemctl restart nginx

sudo systemctl daemon-reload
sudo apt-get update

sudo apt-get remove certbot

sudo apt-get update

sudo snap install --classic certbot

sudo certbot --nginx -d devsite.com -d www.devsite.com

sudo certbot renew --dry-run
nano ~/devsite/devsite/settings.py
debug = False

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# SECURE_SSL_REDIRECT = True ?

CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True

# SECURE_HSTS_SECONDS = 60 ?
SECURE_BROWSER_XSS_FILTER = True

Final checking

source env/bin/activate

python manage.py check --deploy

python manage.py migrate

python manage.py collectstatic

deactivate
sudo systemctl restart gunicorn.socket gunicorn.service nginx

sudo systemctl status gunicorn.socket

sudo systemctl status gunicorn.service