This tutorial explains how to set up Gunicorn, Nginx and Supervisor on a Linux server to serve a Django Wagtail site.
In a previous tutorial we have set up our project to run locally on a DigitalOcean server. We are now going to install Gunicorn and Nginx to serve our site to the internet, as is also described in a DigitalOcean tutorial. However, our setup will be slightly different: we will use a Gunicorn configuration file and use Supervisor to manage both Gunicorn as well as Nginx. There are more tutorials out there, such as this comprehensive guide. Linux tutorials can be found here and here.
Gunicorn will serve as application server, Nginx as web server and reverse proxy and Supervisor as management tool to run these. Gunicorn will run our site and serve dynamic content (from the database) and will be installed in the same virtual environment as our site. Nginx will serve static content (media and images) and will delegate requests for other content to Gunicorn. Nginx will be installed on the server, outside of the virtual environment. Supervisor will also be installed on the server and will be used to start, stop and configure both Nginx and Gunicorn. The steps will be:
Until now we have only used our project's development settings. With Gunicorn and Nginx we will use the production settings, where debug = False
. We then need to set the parameter ALLOWED_HOSTS
, otherwise Django will generate a Bad Request error. We'll change the settings on our development computer, push them to the repository and on our server pull them from there. In your production.py
settings file add the line:
ALLOWED_HOSTS = ['pythoneatstail.com', 'www.pythoneatstail.com',]
Push the change to the repository:
git add .
git commit -m "added allowed hosts"
git push
Go to the server, activate the virtual environment, enter the project's directory and type:
git pull origin master
Review the file production.py
to check that the change is made. Let's also collect all static files in the directory /static/
, so that they can be picked up by the webserver later:
python3 manage.py collectstatic
Log in at the server as the user that we created (use your own IP address):
ssh usr_pet@165.22.199.4
Enter the virtual environment and install Gunicorn:
source env/bin/activate
pip3 install gunicorn
To test that Gunicorn can serve the site, go into your project's directory and type:
gunicorn --bind 0.0.0.0:8000 pet.wsgi
Visiting your site with the browser on http://165.22.199.4:8000 (substitute your IP address) should give the same result as in the previous tutorial with the runserver
command. Shut down Gunicorn with ctrl-C
.
There's several ways to define the configuration for Gunicorn. We could create a service file for the systemd
command, or create a bash script. Here we will create a Gunicorn configuration file as described in the Gunicorn docs. We can place the file anywhere; to stay close to Linux's file organization we will create a directory /etc
for configuration files with a subdirectory /gunicorn
:
mkdir -p ~/env/etc/gunicorn
cd ~/env/etc/gunicorn
touch conf.py
The Gunicorn repository contains an example configuration file. We will copy some of the settings that we need. Let's start with a few simple ones, which are explained in the example file. Open conf.py
with vi
or another editor and add:
workers = 3
keepalive = 5
user = 'usr_pet'
proc_name = 'pet'
We also add information on how we want to log:
loglevel = 'error'
errorlog = '/home/usr_pet/env/var/log/gunicorn-error.log'
accesslog = '/home/usr_pet/env/var/log/gunicorn-access.log'
The specified files don't exist yet. We don't need to create the files, but we do need to make sure the directory exists, so exit the editor temporarily and create a directory ~/env/var/log
. Also create a directory ~/env/run
for the socket file (see below).
mkdir -p ~/env/var/log
mkdir ~/env/run
Return to editing the configuration file conf.py
. Essential is the bind
parameter, which tells Gunicorn what the interface is with which to communicate to the outside world. Above we used an IP-address, now we will use a file (a unix socket file) via which Gunicorn will interface with Nginx (that in turn will connect to an IP-address). Gunicorn will create that file itself, but the directory needs to exist. The syntax is:
bind = 'unix:/home/usr_pet/env/run/gunicorn.sock'
We need a way to tell Gunicorn to use the production settings of our project. The Django parameter for this is DJANGO_SETTINGS_MODULE
and we can set it using the Gunicorn parameter raw_env
(see also example file):
raw_env = ['DJANGO_SETTINGS_MODULE=pet.settings.production',]
To make sure Gunicorn can find the Django settings file, we need to specify the Python path:
pythonpath = '/home/usr_pet/pet'
That concludes the Gunicorn configuration file.
Exit the virtual environment if needed and install Nginx:
sudo apt-get update
sudo apt-get install nginx
Nginx creates a generic configuration file at /etc/nginx/nginx.conf
, and two directories /sites-available
and /sites-enabled
. We will create a server block configuration file in the /sites-available
directory. Create the file (we call it pet
) and open it with your editor (in our case vi
; use sudo
for write permission):
sudo vi /etc/nginx/sites-available/pet
In this file we will put a server block with a number of settings (see "Configure Nginx to Proxy Pass to Gunicorn"):
Put in the following content and save the file; see below for the explanation.
server {
server_name pythoneatstail.com www.pythoneatstail.com;
access_log /home/usr_pet/env/var/log/nginx-access.log;
error_log /home/usr_pet/env/var/log/nginx-error.log;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/usr_pet/pet;
}
location /media/ {
root /home/usr_pet/pet;
}
location / {
include proxy_params;
proxy_pass http://unix:/home/usr_pet/env/run/gunicorn.sock;
}
}
The first line specifies the domain names of our site, the second and third the log files. The first location
statement tells Nginx to ignore any potential problems with finding a favicon. The second and third location
statement specify the location of the static and media files in our project, so that Nginx can serve these directly if asked for it. The final statement matches all other requests and points to the socket file we created earlier, thereby proxy passing the request to Gunicorn. It includes a parameter file proxy_params
created during install in the directory /etc/nginx
.
The actual Nginx settings will be picked up from the directory /sites-enabled
, so we make a symbolic link from /sites-available
to there:
sudo ln -s /etc/nginx/sites-available/pet /etc/nginx/sites-enabled
Test the Nginx configuration for errors with:
sudo nginx -t
To check that Nginx is running, use the systemctl
command; it should display that Nginx is "active (running)"
.
sudo systemctl status nginx
We can now check whether Gunicorn and Nginx can exchange data via the socket file, by running Gunicorn with its configuration file:
sudo /home/usr_pet/env/bin/gunicorn pet.wsgi:application --config /home/usr_pet/env/etc/gunicorn/conf.py
Here the first path leads to the Gunicorn executable, the argument pet.wsgi:application
is the variable application
within the file pet.wsgi
in your project, and the second path leads to the configuration file that we created above. This command will start Gunicorn in the foreground, temporarily occupying your terminal window. In your browser visit your domain (www.pythoneatstail.com); if all is right you will be able to access your site. You can check that Gunicorn has created the socket file by opening a second terminal and listing the contents of the directory /home/usr_pet/env/run
. Stop the Gunicorn process in the first terminal with ctrl-C
. The socket file will be gone.
We will use Supervisor to monitor and control both the Gunicorn and the Nginx process. It is slightly simpler and more user friendly than the built-in Systemd and can delegate to non-root users. Supervisor could be installed in the virtual environment, but since we intend to use it to manage Nginx, which is installed on the server, this is not very logical and requires more configuration. Installing can be done via pip, but we will use apt-get
as recommended by DigitalOcean, despite the fact that it gives us an older version:
sudo apt-get install supervisor
Supervisor has created a configuration file in /etc/supervisor
. If you inspect it, you can see that it includes all application-specific configuration files in the subdirectory /conf.d
. Go into that subdirectory, create a file guni-pet.conf
and paste the following content:
[program:guni-pet]
command=/home/usr_pet/env/bin/gunicorn pet.wsgi:application --config /home/usr_pet/env/etc/gunicorn/conf.py
user=usr_pet
autostart=true
autorestart=true
All configuration parameters are in the docs. The first line specifies the execution command that we used before, the second line specifies the user and the third and fourth line tell Supervisor to automatically start and restart the process after an exit. For Nginx create a file nginx-pet.conf
and paste:
[program:nginx-pet]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
stderr_logfile=/home/usr_pet/env/var/log/nginx-error.log
stdout_logfile=/home/usr_pet/env/var/log/nginx-access.log
In the command we set "daemon off"
with the -g
switch because Supervisor needs subprocesses to run in the foreground. This time we don't specify a user, since Nginx was installed by root and therefore needs to be run by root. For the same reason we repeat the logfile locations, to make sure the logs end up there.
That completes the setup of Supervisor. The command line client part of Supervisor is supervisorctl
. Add the new configurations to Supervisor with:
sudo supervisorctl reread
sudo supervisorctl update
This should now be as simple as:
sudo supervisorctl start all
which starts Gunicorn and Nginx. Go to your domain address in your browser (in our case www.pythoneatstail.com) and see if it works. Stop either one of the processes, start them again, check the status, using the various Supervisor commands (with <process-name>
either guni-pet
or nginx-pet
):
sudo supervisorctl status [optional: <process-name>]
sudo supervisorctl start <process-name>
sudo supervisorctl stop <process-name>
sudo supervisorctl restart <process-name>
If we haven't created any content, then our site is still empty. We can fill it manually, or use a backup; read another tutorial explaining how to do that. We will also set up a firewall, enable https and set ssh access keys.
Comment on this article (sign in first or confirm by name and email below)