In this article, I will cover off how we configure our servers, ready to serve Django applications. Note: do not carry out these instructions unlessyou are OK with things going wrong. I have only run through this process once, but thought it would be useful to share nevertheless.
The basics
First of all, we need to setup our environment in-line with best practice. This is where we disable password logins (using SSH keys is much more secure) and disallow root users from being able to login via SSH – again, improving security.
Step 1: create a new user and give that user SUDO priveliges – this means, we still have a user with whom we can login, even once we’ve disabled the root login.
adduser newuser
usermod -aG sudo newuser
Step 2: copy the public keys for your root user, to the user you’ve just created (to give them the ability to SSH into the server, using those keys) – this is the way I have chosen to approach this, as it means I avoid having multiple keys for the 2 users (that are both me!).
mkdir /home/newuser/.ssh
chmod 700 /home/newuser/.ssh
sudo cp /root/.ssh/authorized_keys /home/newuser/.ssh/authorized_keys
sudo chown -R newuser:newuser /home/newuser/.ssh
sudo chmod 600 /home/newuser/.ssh/authorized_keys
Step 3: SSH as your new user and edit the sshd_config file. In here, disable root login & password authentication. Then restart sshd.
sudo vim /etc/ssh/sshd_config
systemctl restart sshd
PostgreSQL and Python!
Step 4: Now, let’s install all of the Python & Postgres packages that are required.
sudo apt update
sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curl
Step 5: Now, we can configure Postgres – creating a database; user and assigning the user a bunch of roles. Here, I have called the database ‘superapp’ and the user ‘newuser’.
sudo -u postgres psql
CREATE DATABASE superapp;
CREATE USER newuser WITH PASSWORD --------;
ALTER ROLE newuser SET client_encoding TO 'utf8';
ALTER ROLE newuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE newuser SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE superapp TO newuser;
\q
Step 6: setup your Python virtual environment – which means, your Django project will have its own little ecosystem of libraries etc..
To align with the database settings, I’ve called our virtual environment superappenv and we’ve put the virtualenv files inside our superapp directory.
sudo -H pip3 install --upgrade pip
sudo -H pip3 install virtualenv
mkdir ~/superapp
cd ~/superapp
virtualenv superappenv
source superappenv/bin/activate
A bit of Django
Step 7: Let’s install Django and the gunicorn web server. As per Heroku “Gunicorn is a pure-Python HTTP server for WSGI applications. It allows you to run any Python application concurrently by running multiple Python processes within a single dyno. It provides a perfect balance of performance, flexibility, and configuration simplicity.“
As part of this phase, also create a new project, giving it a name (here, I have used superapp, to align with my virtual environment).
pip install django gunicorn psycopg2-binary
django-admin startproject superapp ~/superapp
Step 8: Edit settings.py and update the lines shown below. Here, we are updating the allowed hosts, which should be your IP address or domain name. We’ve also setup the database settings, using the database, user and password we defined above.
Next, we set debug to be False – which is required for production systems – leave it as True if you’re still playing around & need the error messages.
We then force SSL redirect (by setting SECURE_SSL_REDIRECT to True) to ensure no HTTP requests are made.
Next, we set SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE to be True – so that cookies are also only sent over HTTPS.
ALLOWED_HOSTS = ['app.example.com']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'superapp',
'USER': 'newuser',
'PASSWORD': ---------,
'HOST': 'localhost',
'PORT': '',
}
}
DEBUG = False
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Step 9: Now run the below to effectively apply any changes. This step is quite useful, as it will throw errors when you’ve imported libraries that aren’t installed, etc..
~/superapp/manage.py makemigrations
~/superapp/manage.py migrate
Step 10: Make a Django admin user (so you can login to the admin panel) & collect static files
~/superapp/manage.py createsuperuser
~/superapp/manage.py collectstatic
Step 11: exit your virtual environment by typing ‘deactivate’. We now need to get Gunicorn to launch & service requests. Create this file: vi /etc/systemd/system/gunicorn.socket
This is a socket file: “A socket is a special file used for inter-process communication, which enables communication between two processes” – Wikipedia.
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target
Step 12: create this file: vi /etc/systemd/system/gunicorn.service with the below contents.
This is a service file “A SERVICE file is a service unit file included with systemd, an init (initialization) system used by various Linux distributions to bootstrap user space and manage processes. It contains information about how to manage a server application or service, including how to start or stop the service and when it should be automatically started.” – fileinfo. So, essentially, this tells us how the service should be executed.
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=newuser
Group=www-data
WorkingDirectory=/home/newuser/superapp
ExecStart=/home/newuser/superapp/superappenv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
superapp.wsgi:application
[Install]
WantedBy=multi-user.target
Step 13: Run the below commands (the final one checks everything is OK)
sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket
curl --unix-socket /run/gunicorn.sock localhost
Step 14: Edit the NGINX config: sudo vi /etc/nginx/sites-available/superapp – this runs your website on port 80 and includes a proxy to your gunicorn socket file. So, when a request is made, it gets handled appropriately.
server {
listen 80;
server_name app.example.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/newuser/superapp;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
Step 15: Run the below to enable your site config on Nginx and reboot Nginx to ensure changes are applied.
sudo ln -s /etc/nginx/sites-available/superapp /etc/nginx/sites-enabled
sudo nginx -t
sudo systemctl restart nginx
sudo ufw allow 'Nginx Full'
Step 16: Start your Django app and get developing.
python3 manage.py startapp superapp
Now you have Django installed & ready to use! Phew! Next steps:
- Configure UFW firewall
- Configure cloud firewall
- Further security hardening on application & server layers
See you in the next one!