Commands to setup a secure django website on ubuntu

20 Mar 2021 / Oscar F
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 chmod 755 -R /home/appuser

adduser dbuser

Continue logged in as the newly created appuser.

sudo apt update
sudo apt upgrade

sudo add-apt-repository universe
sudo add-apt-repository restricted
sudo add-apt-repository multiverse

sudo apt install python3-pip python3-dev libpq-dev python3-virtualenv

sudo apt install postgresql postgresql-contrib

sudo apt install nginx
sudo su - postgres

createdb devdb

createuser -P dbuser

psql

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/14/main/pg_hba.conf

local all postgres peer

local all all peer

host all all 127.0.0.1/32 md5

host all all ::1/128 md5

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

pip install --upgrade pip

pip install virtualenv

mkdir ~/devsite
cd ~/devsite

python3 -V

# python3.8 -m venv env

virtualenv env
cd ~/devsite

source env/bin/activate

pip install django gunicorn psycopg2-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

LANGUAGE_CODE = 'sv'

TIME_ZONE = 'Europe/Stockholm'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        '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'
source env/bin/activate

python manage.py check --deploy

python manage.py migrate

python manage.py collectstatic

python manage.py check --deploy

deactivate

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 daemon
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 \
          devsite.wsgi:application

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

sudo systemctl enable gunicorn.socket

sudo systemctl status gunicorn.socket
sudo nano /etc/nginx/sites-available/devsite
server {
    listen 80;
    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 / {
        #include proxy_params;
        proxy_pass_header Server;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header Host $http_host;

        proxy_pass http://unix:/run/gunicorn.sock;
    }
}
sudo ln -s /etc/nginx/sites-available/devsite /etc/nginx/sites-enabled/

sudo nginx -t

sudo systemctl restart nginx

sudo systemctl restart gunicorn.socket gunicorn.service

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
source env/bin/activate

python manage.py check --deploy

python manage.py migrate

python manage.py collectstatic

python manage.py check --deploy

deactivate

sudo systemctl restart gunicorn.socket gunicorn.service

sudo nginx -t

sudo systemctl restart nginx

sudo systemctl daemon-reload