Deploying Wagtail on CentOS8 with MariaDB/Nginx/Gunicorn

March 7, 2021

Wagtail is an actively developed, open source CMS built on Python and the Django web framework. In this tutorial, we will go over how to deploy Wagtail on a CentOS 8 server with MariaDB, Nginx, and Gunicorn.

Getting Started

Start with a fresh install of CentOS 8 and make sure your server is up to date.

$ dnf update -y
# reboot the server to apply the updates if needed.

Create a non-root sudo user, if you haven't already

$ useradd <username>
$ passwd <username> #enter new password
$ usermod -aG wheel <username> # add the user to the wheel group for sudo access.

Now we need to install both Nginx and MariaDB using our newly created user account.

$ su <username>
$ sudo dnf install nginx git gcc mariadb-server mariadb-common mariadb-connector-c-devel python3-devel

Setup the environment

At this point I like to create a Wagtail user account for managing the project code.

$ sudo groupadd --system wagtail
$ sudo useradd --system --gid wagtail --shell /bin/bash --home /opt/wagtail wagtail
$ sudo mkdir /opt/wagtail
$ sudo chown wagtail:wagtail /opt/wagtail

If you're hosting your project on GitHub or GitLab you can create an SSH key using the following commands.

$ sudo su wagtail
$ ssh-keygen -t rsa -b 2048 -C "<your email address>"
Generating public/private rsa key pair.
Enter file in which to save the key (/opt/wagtail/.ssh/id_rsa): <enter>
Created directory '/opt/wagtail/.ssh'.
Enter passphrase (empty for no passphrase): <enter password and/or press enter>
Enter same passphrase again: <enter password and/or press enter>
Your identification has been saved in /opt/wagtail/.ssh/id_rsa.
Your public key has been saved in /opt/wagtail/.ssh/id_rsa.pub.

You can get the key by running the following command.

$ cat /opt/wagtail/.ssh/id_rsa.pub

Setting up the database

Before we do anything else, lets start the MariaDB server and enable MariaDB on boot. Run these commands as root.

$ systemctl start mariadb
$ systemctl enable mariadb

Now, to give ourselves a nice, secure default setup run the following command.

$ mysql_secure_installation


Enter current password for root (enter for none): <enter>
Switch to unix_socket authentication [Y/n] n
Change the root password? [Y/n] n
Remove anonymous users? [Y/n] <enter>
Disallow root login remotely? [Y/n] <enter>
Remove test database and access to it? [Y/n] <enter>
Reload privilege tables now? [Y/n] <enter>

Log in to MariaDB and create the database and user.

$ mysql -u root -p
Enter password: <enter>
> CREATE DATABASE wagtail CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
> CREATE USER 'wagtaildb'@'localhost' IDENTIFIED BY '<db user password';
> GRANT ALL PRIVILEGES ON wagtail.* to wagtaildb@localhost;
> FLUSH PRIVILEGES;
> exit

Deploying your Project

The next step is to pull the code down from your repository. This guide assumes GitHub or GitLab is being used but feel free to use whatever you prefer. For this example I am using an empty project hosted on GitLab named Hello.

Switch to the wagtail user and create the Python virtual environment in the wagtail users home directory.

$ su - wagtail
$ python3.8 -m venv .venv
$ source .venv/bin/activate

Upgrade setuptools and pip

$ pip install --upgrade setuptools pip

Create a directory to store the logs.

$ mkdir logs

Clone your git repositiory and cd into the project directory.

$ git clone https://gitlab.com/Skriptmonkey/hello.git
$ cd hello

I have a 'my.cnf' file in my settings folder for the database credentials. This is what the file looks like.

[client]
database = wagtail
user = wagtaildb
password = <db user password>
default-character-set = utf8

The database section in my base.py file looks like this.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '/opt/wagtail/hello/hello/settings/my.cnf',
        },
    }
}

Install the dependancies.

$ pip install -r requirements.txt

Migrate your database.

$ python manage.py migrate

And finally, collect your static assets. Your STATIC_ROOT variable in the base.py settings file should be set to someplace that Nginx can easily access. I go with '/var/www/static'. Run the following commands as root.

$ cd /opt/wagtail
$ source .venv/bin/activate
$ cd hello
$ python manage.py collectstatic --noinput

Let's also create a wagtail super user so that we can access the admin pages.

$ python manage.py createsuperuser
Username (leave blank to use 'wagtail'): <username>
Email address: <email address>
Password: <password>
Password (again): <re-enter password>
Superuser created successfully.

Change back to the wagtail user and create a new settings file in the settings folder called local.py.

$ su - wagtail
$ vim /opt/wagtail/hello/hello/settings/local.py

In this settings file we want to import from .base, add a SECRET_KEY, and add in our ALLOWED_HOSTS. How you generate a secret key is up to you. I like to create a "test" project on my local machine and take the SECRET_KEY from that.

from .base import *


SECRET_KEY = '<your secret key>'

# SECURITY WARNING: define the correct hosts in production!
ALLOWED_HOSTS = ['exampleurl.com', 'www.exampleurl.com']

Configuring Gunicorn

If Gunicorn hasn't been added to the requirements.txt file in your project it should be. But if it isn't you can install it through pip as the wagtail user.

$ pip install gunicorn

We need to create a start file for gunicorn. This file will be called whenever you need to start the application server. Feel free to use your favorite text editor.

$ vim /opt/wagtail/gunicorn_start

Paste this into your new file and update the variables to reflect your project. Pay special attention to the DJANGO_SETTINGS_MODULE, Wagtail uses a different convention than Django by default.

#!/bin/bash

NAME="hello"
DJANGODIR=/opt/wagtail/hello
USER=wagtail
GROUP=wagtail
WORKERS=3
BIND=unix:/opt/wagtail/run/gunicorn.sock
DJANGO_SETTINGS_MODULE=hello.settings.production
DJANGO_WSGI_MODULE=hello.wsgi
LOGLEVEL=error

cd $DJANGODIR
source /opt/wagtail/.venv/bin/activate

export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

exec /opt/wagtail/.venv/bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $WORKERS \
  --user=$USER \
  --group=$GROUP \
  --bind=$BIND \
  --log-level=$LOGLEVEL \
  --log-file=-

Make the file executable.

$ chmod +x ~/gunicorn_start

Make the run directory to store the unix socket file.

$ mkdir ~/run

Set the permissions on the new directory.

$ chmod 755 ~/run

Now exit out of the wagtail user so we can setup the systemd gunicorn service.

First create the systemd service file using your favorite editor.

$ sudo vim /etc/systemd/system/gunicorn.service

Add the following to the file:

[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=wagtail
Group=wagtail
WorkingDirectory=/opt/wagtail
ExecStart=/opt/wagtail/gunicorn_start

[Install]
WantedBy=multi-user.target

Start the gunicorn service and enable the service to start on boot.

$ sudo systemctl start gunicorn
$ sudo systemctl enable gunicorn

Configuring Nginx

Lets get SELinux squared away before we start on configuring Nginx. We need to install the SELinux utilities.

$ sudo dnf install policycoreutils-python-utils -y

Now we add httpd_t to the permissive domains in SELinux.

$ sudo semanage permissive -a httpd_t

While we're allowing things to happen on our server lets also allow both http and https through the server firewall.

$ sudo firewall-cmd --zone=public --permanent --add-service=http
$ sudo firewall-cmd --zone=public --permanent --add-service=https

Change into the Nginx conf.d directory.

$ cd /etc/nginx/conf.d/

Once again, using your favorite editor create a config file for Nginx.

$ sudo vim wagtail.conf

Add this into the file:

upstream app_server {
    server unix:/opt/wagtail/run/gunicorn.sock fail_timeout=0;
}

server {
    listen 80;
    #listen [::]:80; # <- Uncomment this if you also have AAAA DNS record for IPV6.
    server_name IP_ADDRESS_OR_DOMAIN_NAME;  # <- insert here the ip address/domain name

    keepalive_timeout 5;
    client_max_body_size 4G;

    access_log /opt/wagtail/logs/nginx-access.log;
    error_log /opt/wagtail/logs/nginx-error.log;

    location /static/ {
        autoindex on;
        alias /opt/wagtail/hello/static/;
    }

    location /media/ {
        alias /opt/wagtail/hello/media/;
    }

    location / {
        try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
        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;
    }
}

Test your Nginx configuration.

$ sudo nginx -t

Start the service and enable it on boot.

$ sudo systemctl start nginx
$ sudo systemctl enable nginx

Reboot the server.

$ sudo reboot

That's it!

You should now have a functional Wagtail site deployed.

Next I'd recommend configuring your webserver with an SSL certificate. Let's Encrypt is a good, free option.

Return to blog index