How to host Django With Nginx On Ubuntu 18.04 Bionic Beaver Linux

Objective

Install and configure Ubuntu 18.04 to host the Django framework.

Distributions

Ubuntu 18.04

Requirements

A working install of Ubuntu 18.04 with root privileges

Difficulty

Medium

Conventions

  • # – requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
  • $ – requires given linux commands to be executed as a regular non-privileged user

Introduction

If you’re looking to build and host web applications with Python, Django is the most popular choice. Since Python is so tightly integrated into the Linux, it’s not too hard to get a Django server set up on Ubuntu.

There isn’t one set way to host Django projects, but a stack consisting of PostgreSQL, Nginx, Gunicorn, and Django is pretty much the standard.

Install The Packages

Before you get started, you need to install the required packages. There aren’t that many, but make sure that you disable Apache or any other web server running on port 80 before starting.

$ sudo apt install python3 python3-venv nginx postgresql


Create The Database

You’re also going to need to create a database to store the information from your Django application. PostgreSQL is going to fill that role. If you’ve never used PostgreSQL before, it’s not quite the same as MySQL. Its syntax is different, and it handles user logins differently too.

To log in to PostgreSQL and manage it, you need to use the postgres user on your machine that was created when you installed the PostgreSQL package. Switch to that user with su.

$ sudo su postgres

Once you’re on the postgres user, you access your database with the psql command.

After you log in, you should add a password to your admin user before you do anything else.

postgres=# ALTER USER postgres WITH ENCRYPTED PASSWORD 'yourpassword';

Next, create your database.

postgres=# CREATE DATABASE your_db;

Create a new regular user to manage the database. This is the user that Django will sign in with.

postgres=# CREATE ROLE django_user WITH ENCRYPTED PASSWORD 'yourpassword';

Then, grant that user permissions to use the database.

postgres=# GRANT ALL PRIVILEGES ON DATABASE your_db TO django_user;

When you’re done, exit with \q. Exit the postgres user too.

Set Up Your Directory

It’s usually not a great idea to install Python packages system-wide. It’s much harder to manage package versions and keep everything stable.

Python 3 supports virtual environments that allow you to compartmentalize your Python projects by directory. Each environment has its own set of Python packages, and you can install and manage them as a regular user.

Choose the place where you want to host your website. /var/www/yoursite is usually a good choice. Use the built-in command to create your virtual environment.

$ sudo python3 -m venv /var/www/yoursite

Go to your directory and activate it.

$ cd /var/www/yoursite
$ source bin/activate

When you’re done in the directory, you can easily deactivate it.

$ deactivate


Install Django

With your virtual environment started up, you can install Django itself along with a couple of other Python packages that you’ll need to connect everything.

$ pip install django psycopg2 gunicorn

It’ll take a few seconds, but Pip will install everything that you need to set up your Django project.

Create A Django Project

Now that you have Django, you can actually create your project. Make sure that you’re in your virtual environment and have it activated.

$ django-admin startproject your-project

Once you have your project, you’ll need to change the configuration to set up your database. By default, Django is set up to use sqlite3 as its database. That’s more for development purposes. To use PostgreSQL, you’re going to need to edit the main Django configuration at your-project/your-project/settings.py. Find that file, and open it. Look for the DATABASES block, and edit to look like the one below.

DATABASES = {
    'default': {
        #'ENGINE': 'django.db.backends.sqlite3',
        #'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'your_db',
        'USER': 'django_user',
        'PASSWORD': 'yourpassword',
        'HOST': 'localhost',
        'PORT': '',
    }
}

Save and exit. Now, now you can apply the initial migrations and create your admin user. Return to the root directory of your project, and run the following linux commands.

$ python manage.py migrate
$ python manage.py createsuperuser

Configure Gunicorn

The Gunicorn configuration is fairly simple, but it’s still important to get done. Create a gunicorn directory in your site’s root. You essentially need to tell it where to run its socket, how many workers to spawn, and where to log. Create a Python file called gunicorn-config.py, and make it look something like the one below.

import multiprocessing

bind = 'unix:/tmp/gunicorn.sock'
workers = multiprocessing.cpu_count() * 2 + 1
reload = True
daemon = True
accesslog = './access.log'
errorlog = './error.log'

Once you have it set the way you like, save and exit.

You can start up Gunicorn from your project’s root directory with a command similar to this:

$ gunicorn -c gunicorn/gunicorn-config.py your-project.wsgi


Configure Nginx

All of the Nginx configuration rests in /etc/nginx. There are a ton of files in that directory, but you don’t need to worry about all of them. You only really need /etc/nginx/nginx.conf and the site-specific file that you’ll create at /etc/nginx/sites-available/your-site. Actually, the main Nginx configuration isn’t all that necessary unless you want to optimize your site in production. You don’t really need to mess with it just to get your site running.

So, create a file for your site at /etc/nginx/sites-available/your-site.

The first piece of the file that you need is the upstream block. This block tells Nginx that the web application code is being run somewhere else(Gunicorn, in this case), and it should exchange requests with that socket or address.

upstream your-gunicorn {
	server unix:/tmp/gunicorn.sock fail_timeout=0;
}

This block more or less creates a variable based on the name that you specified after upstream and assigns it the value of the destination server. The server can either be a Unix socket or an IP address and port number. Since Gunicorn will be running locally, using a Unix socket is better. Remember that you set that up in the Gunicorn configuration earlier, so point your Nginx configuration at it.

Next, you can move on to the main block for Nginx, the server block. Add that in.

server {

}

The basic options tell Nginx what port to listen on and what URL to look out for.

listen 80 default;
client_max_body_size 4G;
server_name your-site.com;
keepalive_timeout 70;

Then, add in your log locations.

access_log /var/log/nginx/your-site.access_log main;
error_log /var/log/nginx/your-site.error_log info;

Point Nginx at the root directory of your site.

root /var/www/virtualenv/your-site;

Gunicorn doesn’t serve static files, so you’re going to need to set up Nginx to serve your site’s static files. Exactly where those files are located is determined in your Django settings file. Usually, there are two directories, one for the site’s static files and another for uploaded files. The blocks share the same structure. The example below assumes that your static files exist in a directory called static in your project’s root.

location /static/ {
    autoindex on;
	alias /var/www/virtualenv/your-site/static/;
    expires 1M;
    access_log off;
    add_header Cache-Control "public";
    proxy_ignore_headers "Set-Cookie";
}

There are some other options there that make good defaults for caching.

The next location block that you’ll need will actually handle the connection with Gunicorn. Like upstream it sets another variable of sorts and tells it to pass off connections to your upstream block.

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://your-gunicorn;
}

Finally, set up a block that tells Nginx to look for static files to match any incoming requests. If none are found, pass it off to Gunicorn.

location / {
    try_files $uri @proxy_to_app;
}

That’s all that you absolutely need. You can do a lot more performance tuning, but it’s not essential to getting Django running. Save and exit.

Create a link between your new file and the sites-enabled folder. Remove the existing default file in there.

$ sudo rm /etc/nginx/sites-enabled/default
$ sudo ln -s /etc/nginx/sites-available/your-site /etc/nginx/sites-enabled/

Restart Nginx.

$ sudo systemctl restart nginx

By now, you should be able to open your browser and see the default Django page.

Closing Thoughts

Alright, so this was sort of a long road. Whether or not you want to go through this much configuration on a development server is entirely up to you. For production, though, it provides a solid basis for hosting your Django projects. Keep in mind, though, there is definitely more optimization that you can do in both the Django configuration and Nginx.