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;
# Prevent invalid HTTP Host errors by stopping
# the request from even getting to the Django app.
if ( $host !~* ^example.com$ ) {
return 444;
}
# 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.