Skip to content

Setting up Django on Ubuntu

Austin Kong edited this page Feb 9, 2018 · 23 revisions

Based on Vultr and Digital Ocean guide.

  1. Install prerequisites
  2. PostgreSQL
  3. Django
  4. Gunicorn
  5. Nginx
  6. Miscellaneous
  7. Maintenance
  8. Setup SSL certificate

Install prerequisites

$ apt-get install python3-pip python3-dev virtualenv nginx postgresql postgresql-contrib libpq-dev

Set time zone

$ timedatectl
$ timedatectl set-timezone Australia/Sydney

Compiling latest Python from source

$ wget https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tar.xz
$ tar xf Python-3.6.4.tar.xz

Install prerequisites

$ sudo apt-get build-dep python

Or explicitly

$ sudo apt-get install build-essential libssl-dev zlib1g-dev libncurses5-dev libreadline-dev

libgdbm-dev libdb5.3-dev libbz2-dev liblzma-dev libsqlite3-dev libffi-dev tcl-dev tk tk-dev xz-utils

Might also need ?

$ sudo apt-get install lzma lzma-dev llvm

Configure and compile

$ ./configure
$ make -s -j2 #-j controls the number of cores used
$ make test
$ sudo make install

Monitoring

Cockpit

$ sudo apt-get install cockpit

Access at https://ip-address-of-machine:9090

Setting up a Let's encrypt certificate

cat /etc/letsencrypt/live/cockpit.your-domain.com/fullchain.pem >> /etc/cockpit/ws-certs.d/1-my-cert.cert
cat /etc/letsencrypt/live/cockpit.your-domain.com/privkey.pem >> /etc/cockpit/ws-certs.d/1-my-cert.cert

You will need to do this every time the certificate gets renewed (every 3 months).

PostgreSQL

Setup database

Log into the user postgres.

$ sudo -u postgres -s

Create a new database.

$ createdb dbname

Create a user for the new database and set a password.

$ createuser -P dbuser

To change a user's password

postgres=# \password dbuser

Grant user access to the database.

$ psql
postgres=# GRANT ALL PRIVILEGES ON DATABASE dbname TO dbuser;
postgres=# \q
$ exit

Enable and start PostgreSQL:

$ systemctl enable postgresql
$ systemctl start postgresql

Backing up and restoring database

Login as postgres and navigate to /tmp for permission reasons

$ sudo -u postgres -s
$ cd /tmp
$ pg_dump dbname > dump.sql

To wipe existing database (user may still exist if manually wiping)

$ psql
postgres=# DROP DATABASE dbname;
postgres=# CREATE USER dbuser WITH PASSWORD dbpass;
postgres=# CREATE DATABASE dbname;
postgres=# GRANT ALL PRIVILEGES ON DATABASE dbname TO dbuser;
postgres=# \q

Restore a backup. Close all connections to db first before restoring (kill Gunicorn/Django services)

$ psql dbname < dump.sql

Django

Setup virtual environment

$ virtualenv venv -p python3
$ source venv/bin/activate

Install these minimum requirements:

$ pip install django gunicorn psycopg2

or for an existing project

$ pip install -r requirements.txt

Configure Django

Create a new one or git clone an existing one

Modify settings.py as follows:

DEBUG = True
ALLOWED_HOSTS = ['*']
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'dbname',
        'USER': 'dbuser',
        'PASSWORD': 'dbpassword',
        'HOST': 'localhost',
        'PORT': ''
    }
}

Setup and migrate databases.

$ pip install -r requirements.txt
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py collectstatic
$ python manage.py loaddata <fixture name>

Note: Once nginx is configured, the test server can be run to be visible externally

$ python manage.py loaddata runserver

Gunicorn

Docs

Create /etc/systemd/system/gunicorn.service. Reqiures sudo.

[Unit]
Description=Gunicorn for Django
After=network.target

[Service]
User=user
Group=www-data
WorkingDirectory=/pathto/projectname/
ExecStart=/pathto/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 projectname.wsgi:application

[Install]
WantedBy=multi-user.target

Enable and start Gunicorn services:

$ sudo systemctl enable gunicorn
$ sudo systemctl start gunicorn

Nginx

Docs

Configurations

Create /etc/nginx/sites-available/projectname. Check to see if the default nginx site conflicts with the port. Read more

server {
        listen 80;
        server_name domain.example.com;
        access_log off;

        location /static/ {
                alias /pathto/projectname/myproject/staticfiles/;
        }

        location /media/ {
                alias /pathto/projectname/myproject/media/;
        }


        location / {
                proxy_pass http://127.0.0.1:8000;
        }
}

Create symbolic link to enable site

$ ln -s /etc/nginx/sites-available/projectname /etc/nginx/sites-enabled/projectname

Test Nginx configuration for syntax errors.

$ sudo nginx -t

Enable and start Nginx:

$ systemctl enable nginx
$ systemctl start nginx

413 Request Entity Too Large

Error when trying to upload files Docs Fix

$ sudo nano /etc/nginx/nginx.conf

Add the following line to http or server or location context to increase the size limit in nginx.conf or the actual nginx site.

# set client body size to 100MB
client_max_body_size 100M;

And restart the service

$ sudo systemctl reload nginx

Custom error pages

Add this under server to /etc/nginx/sites-available/projectname

error_page 404 /custom_404.html;
        location = /custom_404.html {
                root /usr/share/nginx/html;
                internal;
        }
error_page 500 502 503 504 /custom_50x.html;
        location = /custom_50x.html {
                root /usr/share/nginx/html;
                internal;
        }

Running multiple sites

Source

Alternative reading 1 2

upstream app_server_one {
    server 127.0.0.1:9000 fail_timeout=0;
}

upstream app_server_two {
    server 127.0.0.1:7000 fail_timeout=0;
}

server {
    listen 80 default_server;
    server_name: example.com;
    root /var/www//html;
    index index.html index.htm;

   # [snip...]
}

server {
    listen 80;
    server_name app1.example.com;

   # [snip...]

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://app_server_one;
    }
}

server {
    listen 80;
    server_name app2.example.com;

   # [snip...]

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://app_server_two;
    }
}

Debugging

nginx: [emerg] bind() to [::]:443 failed (98: Address already in use)

There can only be one server with parameter default_server for the listen directive in the configuration files. Even if the site is not enabled(?)

See who is using ports

$ sudo netstat -tulpn #TCP UDP listening program numeric
$ sudo fuser -k 443/tcp
$ lsof -i :80

Kill offending process

$ sudo kill -2 <PID>

Miscellaneous

DNS settings

Setup a A Record to point domain name to the IP address of the server

Firewall settings

Allow ports 80 (http), 443 (https), and 22 (ssh)

Maintenance

List all services

$ sudo systemctl --all 

List services that have failed

$ sudo systemctl --failed

See extended logs

$ sudo journalctl  -xe 

Check status of running services

$ sudo systemctl status gunicorn

Appropriate services need to be restarted if configuration files are changed.

$ sudo systemctl restart gunicorn

Setup SSL certificate

Using a free certificate from Let's Encyrpt with Certbot

Add repository and install.

$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx

Install the certificate.

$ sudo certbot --authenticator standalone --installer nginx --pre-hook "nginx -s stop" --post-hook "nginx"

certbot will edit the nginx configuration file to allow and redirect HTTPS traffic.

View installed certificates

$ sudo ls -l /etc/letsencrypt/live/your_domain_name

Renewing

$ sudo certbot renew --dry-run

Auto renewal

$ sudo crontab -e

Add the following

30 2 * * * /usr/bin/certbot renew --noninteractive --renew-hook "/bin/systemctl reload nginx" >> /var/log/le-renew.log

This will create a new cron job that will execute the certbot renew command every day at 2:30 am, and reload Nginx if a certificate is renewed. The output produced by the command will be piped to a log file located at /var/log/le-renewal.log.

Generate Strong Diffie-Hellman Group

To further increase security, you should also generate a strong Diffie-Hellman group. To generate a 2048-bit group, use this command:

$ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048