Following my previous post on nginx installation process, I will now describe the process of deploying a Django WSGI application on nginx. Tero Karvinen has a great guide on his website on deploying Django using Apache 2 webserver and mod_wsgi, which I have adapted to use for nginx.

Install virtual environment

The first step is to create a new virtual environment inside your directory. In this example, I will store my Django projects in the /home/evgeni/publicwsgi directory, and activate the virtual environment using the following commands:

sudo apt-get install python3-venv
mkdir /home/evgeni/publicwsgi
cd /home/evgeni/publicwsgi
python3 -m venv env --system-site-packages

--system-site-packages flag will utilize python packages installed using apt when possible.

Next, let us start the virtual environment using:

source env/bin/activate

Install gunicorn

Gunicorn, short for “Green Unicorn”, is a Python Web Server Gateway Interface (WSGI) HTTP server. It is a pre-fork worker model, which means it forks multiple worker processes to handle incoming requests. This is based on Ruby’s Unicorn server and is designed to handle concurrent connections with a small footprint, making it suitable for serving Python web applications in a production environment. It interacts with applications that comply with the WSGI standard, like in our case Django and Flask, serving up your Python web applications.

Gunicorn can be installed using pip within you virtual environment:

pip install gunicorn psycopg2-binary

Make sure that you run pip only inside a virtual environment and by normal user. Never install any pip packages by root!

Now you need to copy your Django project to a working directory, in my case it will be /home/evgeni/publicwsgi/my_project

Configure gunicorn

We will need to configure the gunicorn unix socket to use for gunicorn. Let us first create the gunicorn.socket file:

sudoedit /etc/systemd/system/gunicorn.socket

File content will look as follows:

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Next you need to create a systemd service file for gunicorn:

sudoedit /etc/systemd/system/gunicorn.service

Here is the example config that I use for my service:

[Unit]                                                                                         
Description=gunicorn daemon                                                                    
Requires=gunicorn.socket                                                                       
After=network.target                                                                           
                                                                                                
[Service]                                                                                      
User=evgeni                                                                                    
Group=www-data                                                                                 
WorkingDirectory=/home/evgeni/publicwsgi/my_project                                     
ExecStart=/home/evgeni/publicwsgi/env/bin/gunicorn \                                           
            --access-logfile - \                                                                 
            --workers 3 \                                                                        
            --bind unix:/run/gunicorn.sock \                                                     
            boatrent.wsgi:application                                                            
                                                                                                    
[Install]                                                                                      
WantedBy=multi-user.target                                                                     

As you can see from the config above, we are running the service under our username and www-data group name in order for it to be accessible by nginx. You will need to change permissions of your project to give group www-data access to your project:

sudo chown -R evgeni:www-data /home/evgeni/publicwsgi/my_project

NB! By default sudoedit will use nano as an editor, but you can change it to the editor of your choice by setting the system variable:

export EDITOR=micro

Next let us start our socket:

sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket

And finally the service itself:

sudo systemctl start gunicorn.service
sudo systemctl enable gunicorn.service

You can check the status of service by running a command:

sudo systemctl status gunicorn

If you plan to run more than one wsgi application on your server, you can do so by using different filenames. As an example you could use your corresponding project name as a name of these files: my_project.service, my_project.socket and /run/my_project.sock.

Configuring nginx

Providing that you have followed my previous post and have installed and configured nginx, you will need to make some adjustments into /etc/nginx/sites-available/domain.com config:

server {
    listen 80;
    server_name domain.com www.domain.com;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/evgeni/publicwsgi/my_project;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

Please note

When you are copying the configs above, please make sure to remove the trailing spaces behind the lines. I recently encountered a problem when migrating to Debian 12 for one of my projects that took me a while to solve. All the configs, permissions, etc looked to be ok, but gunicorn.service wouldn’t start. When I removed the trailing spaces, everything started to work.

If you face any permission related errors, make sure that your directories have o+x permissions, this includes the home directory as well! You can change directory permissions with chmod command.

Bonus: Enable HTTPS with certbot

It is a good idea to enable HTTPS and force nginx to forward all connections to HTTPS instead of HTTP. SSL certificates are currently free and can be obtained from Let’s Encrypt using certbot app.

Previously, when using apache with mod_wsgi and Flask applications this was a major source of pain. I couldn’t figure out the way to setup the certificates to be renewed automatically. In nginx however, everything worked pretty much out of the box.

First you need to install the certbot plugin for nginx:

sudo apt-get install python3-certbot-nginx

Next run certbot to obtain the certificates for your domain (replace domain.com with your own domain)

sudo certbot --nginx -d domain.com -d www.domain.com

Certbot will handle all the settings automatically. You should see the updated config file for your domain in /etc/nginx/sites-available/. It even sets up all the necessary redirects. Now, when you browse to http://domain.com (or http://www.domain.com), you will be automatically redirected to https://domain.com. How cool is that?

It’s a good idea to test that the update process is working:

sudo certbot renew --dry-run

If you don’t see any error messages, you won’t have to worry about updating your certificates. Certbot will handle it for you.

NB! You will need to open a hole in your firewall to allow port 443/tcp through. If you are using ufw, you can either open this port or just use the predefined config:

sudo ufw allow 'Nginx Full'

Final words

The simplicity of installing Certbot on nginx+gunicorn compared to Apache2+mod_wsgi is a game changer for me. I’ll be experimenting more with nginx. I’m curious about how difficult it would be to set up more than one WSGI application running on gunicorn socket(s). I’m planning to migrate some of the websites and applications from my other Apache server to this newly configured nginx server, so I’ll probably face some challenges ahead when migrating my SSL certificates and modifying configurations.

Stay tuned for new posts and updates!

P.S. If you want to run your Django WSGI application on a powerful and reliable server, I recommend checking out Digital Ocean. They offer scalable compute platforms with add-on storage, security, and monitoring capabilities.