Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 18.04

Filed Under: Django

Django is a versatile, powerful, efficient and ever-evolving python-based web application framework that you can use to get your web application up and running. It’s a popular web framework and usually comes with a development server used for locally testing your code. If you intend to push your applications to production, then a more robust and secure web server set up will be required.

In this guide, we will take you through the art of professionally deploying and configuring Django in a more efficient and resilient manner. We will install and configure PostgreSQL database in the place of the SQLite database, and integrate the Gunicorn application server which will interface with the applications. Later, we will proceed with setting up the NGINX web server which will provide a reverse proxy channel to the Gunicorn server, thereby providing us with the required performance and security components to serve our applications.

Prerequisites

Before we get started, let’s perform a quick flight check and ensure we have the following

  • A fresh Ubuntu 18.04 server instance
  • A non-root user with sudo privileges. In this guide we shall use james
  • SSH access to the server

Objectives

Here is a quick walkthrough of what we intend to achieve. We are going to install Django within a virtual environment. This allows for projects to be handled separately from the host environment. We shall then embark on installation and configuration of Gunicorn application server which will interface the application and translate requests in HTTP protocol to Python calls which our application can process. Finally, we will set up Nginx which is a high-performance web server with a myriad of security components which will boost the security of our applications.

Getting started

To start off, we will begin by updating our Ubuntu repositories

$ sudo apt-get update

Next, we will download and install the requisite software packages for all the applications we are going to configure. These will include the pip package manager, PostgreSQL, and Nginx.
If you are running Django with Python3, the syntax will be

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

If your system runs Python2, the command will be:

$ sudo apt-get install python-pip python-dev libpq-dev postgresql postgresql-contrib nginx

Sample output

Install postgreSQL dependencies

The command will install pip package manager, all the necessary Python development files required to build Gunicorn, Nginx web server, Postgres database and the crucial libraries required to interact with the database engine.

After successful installation of all the required software packages, its time now to create a database and a user for the Django web application

Creating PostgreSQL database and database user

Let’s now jump right in and create a database and a user for Django application
Log in to Postgres and execute the command

$ sudo -u postgres psql

You will drop to the Postgres prompt as seen in the output

Postgres Login in Ubuntu 18.04

Create a database for your project. In this case, the database name is “project”.

postgres=# CREATE DATABASE project;

Sample output

Create Database Project

NOTE! All commands in PostgreSQL need to be terminated with a semi-colon.

After successful creation of the database, create a user for the Django project and be sure to assign a secure Password. In this case, the user is projectuser

Replace the ‘password’ attribute with your own strong Password.

postgres=# CREATE USER projectuser WITH PASSWORD 'password';

Sample output

Create User Projectuser With Password

To confirm to Django’s set up parameters for database connectivity, we are going to:

  1. Set default encoding to UTF-8
  2. Set isolation scheme to ‘read committed’ value
  3. Set timezone to UTC

Execute the commands below to fulfill the setup requirements


postgres=# ALTER ROLE projectuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE projectuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE projectuser SET timezone TO 'UTC';

Let’s now grant database access to our newly created user

postgres=# GRANT ALL PRIVILEGES ON DATABASE project TO projectuser;

Sample output

Grant All Privilges On Database Project To Projectuser

Now you can exit from the Postgres environment.

postgres=# \q

Creating a Python Virtual Environment for your Django Project

For easier management of our application, we are going to install the required Python prerequisites in a virtual environment.
But first, let’s install the virtual environment
If your system is running Python3, upgrade pip by executing

$ sudo -H pip3  install --upgrade pip

For a system running on Python2 run

$ sudo -H pip install --upgrade pip

Sample Output

Pip Install Upgrade Pip

Thereafter, install the virtual environment using pip

$ sudo -H pip install virtualenv

Sample Output

Install Virtualenv on Ubuntu 18.04

With our virtual environment in place, we are now going to create and move into it


$ mkdir ~/project
$ cd ~/project

Now create a Python virtual environment

virtualenv projectenv

Sample output

Virtualenv Projectenv Ubuntu 18.04

This creates a directory called projectenv within project directory.

Before installing Python requirements, activate the virtual environment

$ source projectenv/bin/activate

Sample output

Activate Virtual Environment

Notice how the prompt changes to (projectenv)james@ubuntu: ~/project$

With the virtual environment active, let’s install Gunicorn, Django, and Psycopg2 Postgres adapter using pip.

$ pip install django gunicorn psycopg2-binary

Sample Output

Pip Install Django Gunicorn Psycopg2

Creating and configuring a new Django project

At this point, we shall instruct Django to install project files in the project directory that we already created. A second level directory will be created alongside a management script.
To achieve this execute the command below.

$ django-admin.py startproject project ~/project 

Our project directory, in this case, ~/project should have the following contents


manage.py      -  Django’s Python management script
project             - directory containing Django’s project package
projectenv      - Virtual environment directory that was earlier created

 

Sample output

project files in virtual environment

Adjusting Project files configuration

We are going to open the settings file

vim ~/project/settings.py

Scroll down and be on the lookout for the ‘ALLOWED HOSTS’ attribute. Within the square brackets, specify the server’s IP address and append the ‘localhost’ attribute.

Sample output

Allowed Hosts

Next, locate the ‘DATABASES’ section. Adjust the settings to conform to the PostgreSQL database information. This includes the database name, user and password of the user.

Sample output

Databases Section

Next, scroll down and specify the location of the static files. This is important so that Nginx can seamlessly handle requests for these items. From the snippet below, static files will be placed in a directory called ‘static’

Save and Exit.

Completing initial project setup

Let us now migrate the database scheme to our PostgreSQL database


~/project/manage.py makemigrations

Sample output

Makemigrations


~/project/manage.py migrate
 

Sample output

 nginx postgres and gunicorn
Next, we are going to create a super user for the Django project by executing the following command

~/project/manage.py createsuperuser 

You will be prompted for a username, email, password as shown below

Sample output

Create Superuser

Upon successful creation of the superuser, we can now collect static content into the directory location

~/project/manage.py collectstatic

Sample output

Collect Static gunicorn

These static files will be placed in the ‘static’ directory in the project folder.

To test the development server, we are going to allow a port, in this case, port 8000 which will be used for accessing the application via a web browser.

To open the port, we will execute

sudo ufw allow 8000

Finally, start the Django development server in the virtual environment by running

~/project/manage.py runserver 0.0.0.0:8000

Open your web browser and visit your server’s address

In this case, our server’s address is

http://38.76.11.180/

You should be able to see the following Django’s index page

Sample output

gunicorn page worked

Let’s now append /admin at the end of the URL to get to the login page

http://38.76.11.180/admin

Sample output

Django Login page Ubuntu 18.04

Provide the credentials you provided when creating the superuser account and hit ‘Login’

This will take us to the Django’s Admin panel

Sample output

Django Admin Panel

Great! Now that we have confirmed that Django is up and running, let’s hit CTRL + C on the terminal to exit the application

Confirming Gunicorn’s ability to test the Project

Before exiting the virtual environment, let’s verify that our Gunicorn application server can serve the Django.
While still in the project directory, let’s load the WSGI module

gunicorn --bind 0.0.0.0:8000 project.wsgi

This fires up Gunicorn on the same interface and port that the Django server was running on. You can go back and verify that the Django application is running on the web browser.

With that done hit CTRL + C to stop the application and run the deactivate command to exit the virtual environment.

Having tested that the Gunicorn application server can serve our Django application, it’s time to implement a more robust avenue of starting and stopping the application server.
We are going to create a systemd service file with Gunicorn using the following command

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

Starting with the [Unit] section, paste the following content


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

This section specifies dependencies and metadata

Next, create the [service] section.

In this section, we shall specify the user and group that the process should run under. In this case, the user is root and the group is www-data. The group is specified as www-data so that Nginx can seamlessly communicate with Gunicorn.

The full path to Gunicorn executable will then be indicated. Since Nginx is installed within the same server, we will bind it to a Unix socket.

Paste the following content


[Service]
User=james
Group=www-data
WorkingDirectory=/home/james/project
ExecStart=/home/james/project/projectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/james/project/project.sock project.wsgi:application

Finally, create [Install] section and append the following lines


[Install]
WantedBy=multi-user.target

Great! Now our systemd service file is complete.

Save and close the text editor. Restart Gunicorn service and enable it to start on boot


$ sudo systemctl start gunicorn
$ sudo systemctl enable gunicorn

To check the status of gunicorn run

$ systemctl status gunicorn

Sample output

Status Gunicorn

Verify for the presence of Gunicorn Socket file

Now that we have verified that Nginx is up and running, let us verify the existence of the project.sock file in our project directory

$ ls -l /home/james/project

Sample output

Project Sock file

NOTE:

If project.sock file is missing, this is an indicator that gunicorn was not able to start correctly.
Additionally, you can check the gunicorn logs by executing the command below

$ sudo journalctl -u gunicorn

There are a number of reasons why Nginx could not have created the project.sock file. Some include

  1. Project files being owned by root user instead of sudo user
  2. The working directory within the /etc/systemd/system/gunicorn.service not pointing to the project directory.
  3. Incorrect configurations in the ExecStart directive

If all configurations are okay, then you should not get any errors after running

$ sudo journalctl -u gunicorn

Once you make changes to the /etc/systemd/system/gunicorn.service file, ensure that you reload the daemon service for the changes to take effect



$ sudo systemctl daemon-reload

$ sudo systemctl restart gunicorn

Configuring Nginx to direct traffic to Gunicorn

The last phase in this guide is configuring Nginx web server to channel web traffic to Gunicorn service

We shall create and open a new server block in sites-available directory

$ sudo vim /etc/nginx/sites-available/project

Begin by specifying that this block should be listening to port 80 and should respond to the server’s IP address.


server {
    listen 80;
    server_name server_domain_or_IP;
}

 

Next, we shall instruct Nginx to ignore any issues with locating the favicon. Additionally, we will tell it where to locate static assets collected in ~/project/static directory


location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/james/project;
    }

Lastly, we will create a block location / { } which will match the rest of the requests.
Within this location, we shall define proxy_params file and later direct traffic to the socket created by the Gunicorn process


location / {
        include proxy_params;
        proxy_pass http://unix:/home/james/project/project.sock;
    }
 

The final configuration file should look something like this

Save and Exit the configuration file

Let’s Enable the file by linking it to the sites-enabled directory

$ sudo ln -s /etc/nginx/sites-available/project /etc/nginx/sites-enabled

Next, let us test our configuration for any errors

$ sudo nginx -t

If all went well, the output should be as shown below

After testing the Nginx configuration

$ sudo systemctl restart nginx

Now that we no longer need access to the development server on port 8000, let us remove the rule on the firewall.

$ sudo ufw delete allow 8000

Let us also allow port 80

$ sudo ufw allow 'Nginx Full'

You should now be able to go to your server’s domain or IP address to view your application without specifying port 8000

Troubling tips

1.Server displaying default Nginx page

If Nginx displays the default page instead of proxying to the Django application, you need to check the /etc/nginx/sites-available/project file and ensure your server’s IP or domain name is correctly indicated. The default page implies that Nginx wasn’t able to match your request and instead was falling back on /etc/nginx/sites-available/default

2. 502 Bad gateway error instead of Django application

A 502 error is an indication that Nginx web server was not able to successfully proxy the request. To accurately troubleshoot the problem, check out the logs in the error log file as shown and this could give you a clue as to what could be wrong.

$  sudo tail -F /var/log/nginx/error.log

3. “Could not connect to server” Connection refused

An error you may come across when trying to access some certain components in your web browser is

OperationalError at /admin/login/
could not connect to server: Connection refused
    Is the server running on host "localhost" (127.0.0.1) and accepting
    TCP/IP connections on port 5432?

This shows that the Django application is unable to connect to the Postgres database
Ensure that Postgres is running by executing

$ sudo systemctl status postgresql

If it’s not running, start it and enable it to start on boot


$ sudo systemctl start postgresql
$ sudo systemctl enable postgresql

If you are still having issues, ensure that database settings in
~/myproject/myproject/settings.py are correct.

For further troubleshooting, examine the following logs


Nginx process logs                 sudo journalctl -u nginx

Nginx access logs                    sudo tail -f  /var/log/nginx/access.log

Nginx error logs	          sudo less /var/log/nginx/error.log


Gunicorn Application logs     sudo journalctl -u gunicorn

As you make changes to the configuration files, ensure that you restart them for the changes to take effect.

If you decide to make changes to the gunicorn systemd service file, make sure you reload the daemon and restart gunicorn


$ sudo systemctl daemon-reload
$ sudo systemctl restart gunicorn

When you make changes to the Nginx server block configuration test the config

$ sudo nginx -t && sudo systemctl restart nginx

Conclusion

Thank you for taking the time in this article. In this article, we set up a Django project in a virtual environment and set up Gunicorn to translate client requests and Nginx to proxy traffic to our Django application.

Comments

  1. Simon says:

    Great Tutorial, thanks. You saved my day.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages