Deploying Multiple Python Apps with Nginx and uWSGI

This is a guide for setting up a production-ready Django (and Flask) server with Nginx and uWSGI. uWSGI will run the Python applications using the uwsgi protocol. And we’ll use Nginx as a proxy that redirects HTTP requests to uWSGI. Nginx/uWSGI is easy to configure and performs better than an Apache & mod_wsgi server. Deploying multiple applications is also very simple with uWSGI’s Emperor mode.

I’m using Arch Linux for this guide, but the procedure should be similar on other distributions.

Installation

To begin, make sure you have nginx and uwsgi installed.

Nginx: sudo pacman -S nginx

uWSGI via pip: sudo pip install uwsgi

Detailed installation procedures for uWSGI can be found here.

Testing uWSGI

We will create a simple WSGI app to test uWSGI.

# test.py
def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return "Hello World"

Run uwsgi --http :8000 --wsgi-file test.py. You will see a hello world message at example.com:8000.

Starting a Django Project

Create an empty Django project:

django-admin.py startproject <project name>.

The startproject command in Django 1.4+ also creates a projectname/wsgi.py. uWSGI can use this file to interact with your project.

$ django-admin.py startproject projectname
$ tree projectname/
projectname/
|-- manage.py
`-- projectname
    |-- __init__.py
    |-- settings.py
    |-- urls.py
    `-- wsgi.py

Run python manage.py runserver 8000 to test with the development server. Visit example.com:8000, and you will see the default “Welcome to Django” page.

Testing uWSGI + Django

Now you can do the same with uWSGI.

uwsgi --http :8000 --chdir /path/to/your/project --wsgi-file projectname/wsgi.py

You’ll notice that we replaced test.py with projectname/wsgi.py. It is an entry point for your Django project. Visit example.com:8000, and you’ll see the same “Welcome to Django” page served by uWSGI instead of the Django development server. The --http option means uWSGI is listening to port 8000 using the http protocol. In a production environment, it is better to use the uwsgi protocol and let Nginx handle the http requests instead.

uWSGI + Flask

To deploy a Flask app, add --callable and pass your Flask app in the --wsgi-file. Read more about deploying Flask.

Configuring Nginx

Nginx supports the uwsgi protocol out-of-the-box. Just include /etc/nginx/uwsgi_params in the Nginx config file. Open /etc/nginx/nginx.conf and insert the following in the http { } block.

upstream mysite {
    server unix:///tmp/uwsgi.sock; # use Unix socket
    # server 127.0.0.1:8001; # uncomment to use a port instead
}
server {
    listen 80;
    server_name django.example.com;
    charset utf-8;
    location /static  {
        alias /path/to/project/project/static;
    }
    location / {
        uwsgi_pass mysite;
        include /etc/nginx/uwsgi_params;
    }
}

Nginx will now listen to requests on port 80 and send them to uWSGI through a Unix socket at /tmp/uwsi.sock.

Testing Nginx

To start the Nginx daemon, run systemctl start nginx (details on systemd for Arch, Debian). To start the Nginx service at start-up, run systemctl enable nginx.

Create a static file called test.css in /path/to/project/project/static directory and visit django.example.com/static/test.css. If it works, Nginx is serving static files correctly.

Testing Nginx + uWSGI + Django

To run your application with Nginx and uWSGI, run

uwsgi --socket /tmp/uwsgi.sock --chdir=/path/to/projectname --wsgi-file projectname/wsgi.py.

You should see the default “Welcome to Django” page again when you visit django.example.com.

If that doesn’t work, check your Nginx error log (/var/log/nginx/error.log). If you see Permission Denied, Nginx doesn’t have permission to access /tmp/uwsgi.socket. UNIX sockets have to obey permissions just like any other filesystem object. Try running it again with --chmod-socket option:

uwsgi --socket /tmp/uwsgi.sock --chdir=/path/to/projectname --wsgi-file projectname/wsgi.py --chmod-socket=666.

There are infinitely many options to configure uWSGI, so we will keep them all in an .ini file.

Configuring uWSGI

Create a file called mysite.ini:

[uwsgi]
chdir = /path/to/project/
wsgi-file = project/wsgi.py
processes = 2 # number of cores on machine
max-requests = 5000
chmod-socket = 666
master = True
vacuum = True
socket = /tmp/uwsgi.sock

This is your configuration file for uWSGI. Now you can simply run uwsgi --ini mysite.ini to run uWSGI with these options. The result at django.example.com should be the same.

Emperor Mode

uWSGI’s Emperor mode makes deploying multiple apps very simple. Put the configuration files for each app in a folder and run uWSGI with the --emperor option to deploy them. Then uWSGI will search that folder for .ini files and spawn instances (known as vassals) for each one it finds. Whenever you modify the config files, the instances will automatically restart. Even if you only have one app to deploy, the Emperor mode will give you the flexibility of easily adding a second application in the future.

Create a directory called /etc/uwsgi/vassals. This is where uWSGI will look for the .ini files. We can create a symlink for that. If you have mysite.ini and mysite2.ini, for example, run sudo ln -nsf /path/to/mysite.ini /etc/uwsgi/vassals/ and sudo ln -nsf /path/to/mysite2.ini /etc/uwsgi/vassals/ to prepare them.

Finally, run uWSGI with the --emperor option.

sudo uwsgi --emperor /etc/uwsgi/vassals --uid http --gid http --master

If that didn’t work, your user (--uid) or group (--gid) probably doesn’t exist or have appropriate permissions. Arch Linux comes with an http user and group by default, but create them if they don’t already exist. On Debian/Ubuntu, the default http user is called www-data.

We can create an .ini file for the Emperor mode as well. Create a file called emperor.ini in /etc/uwsgi and add the following.

[uwsgi]
emperor = /etc/uwsgi/vassals
uid = http
gid = http
logto = /tmp/uwsgi.log

Now sudo uwsgi --ini /etc/uwsgi/emperor.ini will run uWSGI in Emperor mode and start a process for each .ini file in /etc/uwsgi/vassals/. It will reload the processes whenever they change, so you won’t have to worry about reloading them as long as the main Emperor process is running.

You’ll also have to add the appropriate virtual host blocks in nginx.conf. Visit django.example.com, flask.example.com, django2.example.com, and etc. according to your setup to make sure everything works correctly. Check the log file at /tmp/uwsgi.log if that didn’t work.

Read more about the Emperor Mode here.

Managing the uWSGI Server

Let’s make sudo uwsgi --ini /etc/uwsgi/emperor.ini run when the system boots. We can ‘daemonize’ uWSGI with systemd to do that.

Create a file called uwsgi.service in /etc/systemd/system/.

[Unit]
Description=uWSGI in Emperor mode
After=syslog.target

[Service]
ExecStart = /usr/bin/uwsgi --ini /etc/uwsgi/emperor.ini
ExecStop = kill -INT `cat /run/uwsgi.pid`
ExecReload = kill -TERM `cat /run/uwsgi.pid`
Restart = always
Type = notify
NotifyAccess = main
PIDFile = /run/uwsgi.pid

[Install]
WantedBy=multi-user.target

Finally, we can manage our uWSGI processes with the systemctl command.

Start uWSGI: sudo systemctl start uwsgi

Restart: sudo systemctl restart uwsgi

Stop: sudo systemctl stop uwsgi

Check status: sudo systemctl status uwsgi.service