How to Deploy Django with Nginx and uWSGI

Introduction

This tutorial demonstrates how to deploy a Django application for production in Linux using nginx and uWSGI. uWSGI will listen on a local-only TCP port and nginx will reverse proxy all requests to the uWSGI listener. Nginx will also be used to serve all the static files.

This was tested with Django 2, Python 3.7 in Fedora 30 server with uWSGI version 2. Slight modifications might be necessary for different versions or Linux distributions, but the general concepts remain the same.

To learn more about nginx and see more tips in general, like how to force SSL, see my Nginx Tutorial.

Install nginx

In Fedora, you can use dnf to install nginx. For most other Linux distributions, an nginx package exists.

dnf install -y nginx

Install uWSGI

Install uWSGI and the Python plugin. If you want to browse other plugins, use dnf search uwsgi.

dnf install -y uwsgi uwsgi-plugin-python3

Prepare your Django app

This assumes you already have a Django application, that you have already have a database with the migrations performed, and have already tested your application and are ready for deployment.

The uWSGI configuration used below assumes you have a virtual environment for your project. If you don't already have one, check out my Python Virtual Environments Tutorial. Make sure the virtual environment has all the packages needed like django installed.

Check permissions

The permissions will be specific to how you decide to set up your system, and what files your project in particular needs to write. Here are some basic and generalized tips though.

If you use a SQLite database, the uwsgi user/group will need write access to it. The project directory will also need to be read/executable by uwsgi. You can set pretty tight permissions by doing something like the following:

# Give uwsgi group ownership of the Django project dir
chown -R root:uwsgi /path/to/myproject

# Remove all permissions, except allowing uwsgi group to read only
chmod -R 750 /path/to/myproject

# Let uwsgi write and execute in the directory
# and write any other specific files
chmod 770 /path/to/myproject
chmod 770 /path/to/myproject/db.sqlite3
# If there is an upload directory that the server needs to write
chmod -R 770 /path/to/myproject/upload-dir

Collect static files

Django, and manage.py provide the collectstatic command. This packages up all the static files across the various apps and puts them in to a single directory. If you wanted, you could serve the static files from a CDN or an AWS s3 bucket. You can let Django serve the static files by modifying the app urls which is convenient, but slow. If you are already using nginx with uWSGI to handle the dynamic Django requests, then you might as well let nginx take care of serving the static files. After all, nginx is very fast at serving static files, and even offers caching and rate-limiting.

First, you need to update your Django app to specify where the static files should go. Then you will need to collect all of the static files.

In your settings.py, set STATIC_ROOT = '/srv/django-static' or wherever you want static files to be served by nginx. Alternatively, you can set the static root to a directory inside the project and then manually take care of copying the static files directory to the final destination.

python manage.py collecstatic

This will put your files in the directory specified in the settings.py file from the previous step. Later, when we configure nginx, we will point it to the directory with the static files.

Configure uWSGI

Before configuring nginx, we need to configure uwsgi. We will tell uwsgi to set up a local TCP listener and also specify which Python virtual environment to use, and where the wsgi app (Django) is located.

Create an ini file in /etc/uwsgi.d/ like this:

# /etc/uwsgi.d/myproject.ini

[uwsgi]
plugins=python3
chdir=/path/to/myproject
module=myproject.wsgi:application
# Settings module, relative to the chdir path
env='DJANGO_SETTINGS_MODULE=myproject.settings'
# Python virtual env path
home=/path/to/venv
# File used for uwsgi to send signals and start/stop
pidfile=/run/uwsgi/myproject.pid
socket=127.0.0.1:8001
master=True
processes=5
harakiri=20
max-requests=5000
vacuum=True

Replace directories and modules as needed to match your project.

Check /etc/uwsgi.d/ permissions

Ensure the uwsgi user can read the files in the /etc/uwsgi.d/ directory

You can set pretty tight permissions on the config files like this:

# Set user and group ownership to root:uwsgi
sudo chown -R uwsgi:uwsgi /etc/uwsgi.d/
# Allow read and execute on the directory for uwsgi
sudo chmod 750 /etc/uwsgi.d/
# Allow read only on ini files for uwsgi
sudo chmod 640 /etc/uwsgi.d/*

Restart uWSGI service

The uwsgi service that must be running in order for nginx to use it. Both nginx and uwsgi service will need to be running together for everything to work. After modifying uwsgi configuration you need to restart the uwsgi service.

systemctl restart uwsgi

# Check status with
systemctl status uwsgi

Verify uWSGI socket is listening

You can verify that the local TCP socket is listening with netstat. You should see the socket configured listed, in this case, 127.0.0.1 port 8001.

# List TCP ports that are listening
netstat -ntl

Check uWSGI logs

You can check for errors in the uWSGI logs with journalctl:

# Be sure to go to the END of the log using `G`
journalctl -u uwsgi

Configure nginx for Django

Now that uWSGI is set up and listening, and we have the static files collected, we are ready to configure nginx to serve the static files and act as a reverse proxy for the Django application that is listening locally via uwsgi.

Create an nginx configuration entry in /etc/nginx/conf.d/

# /etc/nginx/conf.d/mydjango.conf
server {
    listen 0.0.0.0:80;
    server_name example.com;

    # Serve static files
    location /static/ {
        alias /srv/django-static/;
    }

    # Reverse proxy the uwsgi Django request
    location / {
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:8001;
    }
}

Restart nginx

Make sure to restart the nginx server to reload all configs.

sudo systemctl restart nginx
# Check status with
sudo systemctl status nginx

Read nginx logs

To review the nginx logs use journalctl or view logs in /var/log/nginx/.

journalctl -u nginx
ls /var/log/nginx

Test out the server

Everything should be ready to go now. View your server in a web browser or try using curl to test out a basic HTTP request. If there are any issues, try reviewing the uwsgi logs, nginx logs, and the Django project logs. Also review user/group ownership and permissions of all files and directories involved.

Further reading

Nginx has many other features like the ability to rate-limit requests, perform caching, and load balancing. See my Nginx Tutorial for other tips like forcing SSL and redirecting non-www to www. Check out the official Nginx Documentation for a full list of features.

Conclusion

After reading this you should be able to deploy your Django application with Python 3 using nginx and uWSGI.

References