Hi, I'm Harlin and welcome to my blog. I write about Python, Alfresco and other cheesy comestibles.

Python + Gunicorn + Nginx - A Simpler and More Performant Method to Deploy your Django Projects

Last week, I posted an article that had to do with hosting your Django projects with gunicorn and Apache. Today, I want to mention an alternative to Apache called Nginx. Most of you know that Apache is probably the most used web server on the planet and has been around for some time going back to 1995. It has always had more market share than Microsoft's IIS by a long shot. Like Apache, Nginx is a web server that can also be used as a reverse proxy, load balancer and HTTP cache. It was created by Igor Sysoev and first publicly released in 2004. At the moment, Apache has about a 48% marketshare of web servers on the Internet while Nginx has about 35% which is very impressive.

So, how do Nginx and Apache compare? I won't go into too many details on the subject of the comparison but from my own research, I'll give you some quick bullet points on what I think each's strength is:

Nginx advantages:

  • Better at serving static content
  • Faster throughput
  • Simplest to configure
  • Lightest on resource usage

Apache advantages:

  • More granular configurability
  • Better dynamic processing
  • Better dynamic handling of modules
  • Better overall support

I have heard recently from a commenter that the ultimate way to use Apache and Nginx is to perhaps use them together: Apache handling the dynamic and Nginx handling the static but even then that requires more research and a different article all together. I would encourage you to do your own research and decide what works best for your needs.

Without further to do, let's get on with installing and configuring Nginx to work with Django and gunicorn. So that you can follow along, I recommend building your own Hello World Django app. You can read about how to do that here. So, go take care of that and then come back here when done.

Welcome back. If you've done everything as you should have from the Hello World article, in the browser at http://localhost:8000, you should see "Hello World". Next, we'll add some code to make use of css (this will demonstrate how to use Apache later to serve static content).

Let's create the static folder in our project:

# mkdir -p static/css

Inside static/css open a file called base.css and add the following:

p.hello_world {
     font-weight: bold;

Go ahead and save it.

Open web/templates/index.html and change the html in there to this:

<!DOCTYPE html>
    <title>{{ message }}</title>
    <link rel="stylesheet" href="/static/css/base.css">
    <p class="hello_world">{{ message }}</p>

Next, we need to tell Django to use the static folder to serve up static content. Keep in mind, that this is fine for a development environment but in a production environment, you will want to create a folder outside your Django project to store static assets.

Open helloworld/settings.py and add this setting:


    os.path.join(BASE_DIR, 'static'),

Now, let's run the Django development server and take a look at our work.

# ./manage.py runserver

At http://localhost:8000 we should still see "Hello World!" but it should now be using bold face type. If this is what you're seeing, then let's go on to the next part.

Do a control-c to exit the Django development server.

Install and Run Gunicorn

To install Gunicorn, we only need to use pip like so:

# pip install gunicorn

Collecting gunicorn
  Using cached gunicorn-19.7.1-py2.py3-none-any.whl
Installing collected packages: gunicorn
Successfully installed gunicorn-19.7.1

To start Gunicorn, run:

# gunicorn helloworld.wsgi

[2018-02-25 09:41:01 -0700] [16365] [INFO] Starting gunicorn 19.7.1
[2018-02-25 09:41:01 -0700] [16365] [INFO] Listening at: (16365)
[2018-02-25 09:41:01 -0700] [16365] [INFO] Using worker: sync
[2018-02-25 09:41:01 -0700] [16409] [INFO] Booting worker with pid: 16409

If you have output similar to this, open up http://localhost:8000. You should see "Hello World" in your browser but now it will not be using the static css reference. This is because gunicorn doesn't handle static assets. We'll need Apache for that. But, besides not handling the static assets, Gunicorn does handle Python and Django code very well and fairly quickly.

Install, Configure and run Nginx

# sudo apt install nginx
# sudo service nginx start

Test http://localhost to make sure it's working. You can also run:

# lsof -i :80

nginx   29232     root    8u  IPv4 11967580      0t0  TCP *:http (LISTEN)
nginx   29232     root    9u  IPv6 11967581      0t0  TCP *:http (LISTEN)
nginx   29234 www-data    8u  IPv4 11967580      0t0  TCP *:http (LISTEN)
nginx   29234 www-data    9u  IPv6 11967581      0t0  TCP *:http (LISTEN)
nginx   29236 www-data    8u  IPv4 11967580      0t0  TCP *:http (LISTEN)
nginx   29236 www-data    9u  IPv6 11967581      0t0  TCP *:http (LISTEN)
nginx   29239 www-data    8u  IPv4 11967580      0t0  TCP *:http (LISTEN)
nginx   29239 www-data    9u  IPv6 11967581      0t0  TCP *:http (LISTEN)
nginx   29241 www-data    8u  IPv4 11967580      0t0  TCP *:http (LISTEN)
nginx   29241 www-data    9u  IPv6 11967581      0t0  TCP *:http (LISTEN)
nginx   29242 www-data    8u  IPv4 11967580      0t0  TCP *:http (LISTEN)
nginx   29242 www-data    9u  IPv6 11967581      0t0  TCP *:http (LISTEN)
nginx   29243 www-data    8u  IPv4 11967580      0t0  TCP *:http (LISTEN)
nginx   29243 www-data    9u  IPv6 11967581      0t0  TCP *:http (LISTEN)
nginx   29244 www-data    8u  IPv4 11967580      0t0  TCP *:http (LISTEN)
nginx   29244 www-data    9u  IPv6 11967581      0t0  TCP *:http (LISTEN)
nginx   29245 www-data    8u  IPv4 11967580      0t0  TCP *:http (LISTEN)
nginx   29245 www-data    9u  IPv6 11967581      0t0  TCP *:http (LISTEN)

Apache is a little more flexible with regards to serving from areas outside of the document root. Nginx is less so but that's not a problem. For the purposes of demonstration, we'll create a subfolder under /var/www/html where Nginx serves out of the box:

# mkdir /var/www/html/nginx-test

One directory level above your project:

# cp -rfv helloworld /var/www/html/nginx-test/.

Now, let's start gunicorn from the document root:

# cd /var/www/html/nginx-test
# gunicorn helloworld.wsgi

[2018-02-26 12:39:57 -0700] [28155] [INFO] Starting gunicorn 19.7.1
[2018-02-26 12:39:57 -0700] [28155] [INFO] Listening at: (28155)
[2018-02-26 12:39:57 -0700] [28155] [INFO] Using worker: sync
[2018-02-26 12:39:57 -0700] [28199] [INFO] Booting worker with pid: 28199
# sudo su - 
# cd /etc/nginx/site-available
# vi nginx-test.conf
server {
    listen 80;
    server_name nginx-test;

    access_log /var/log/nginx/nginx-test-access.log;
    error_log /var/log/nginx/nginx-test-error.log;

    location /static {
        alias /var/www/html/nginx-test/helloworld/static;

    location / {
# cd /etc/nginx/sites-enabled
# ln -s /etc/nginx/sites-available/nginx-test.conf 
# sudo service nginx restart
# vi /etc/hosts

Add line:       nginx-test

You can now just go to http://localhost (no port number needed unless you configured Nginx to use something besides the default port 80) and you should see "Hello World!" in bold type.

This is a very simple example but the whole idea is to give you an idea of how to deploy a Django app in your own server using gunicorn+Nginx. I do like Heroku and other cloud platforms because they allow you to focus on your app as it handles all of the server stuff. But, I believe it's a good idea to know how to deploy it in your own server and understand how it works there.

Personally, I prefer to use a simple Ubuntu server I am renting from Linode at the moment. Of course, this means I will be responsible for deploying my Django projects and all the server management that comes with it. It's actually not hard and doesn't take up a lot of time especially when you can write bash scripts to handle most of the maintenance work.

You can see also, that each project can have its own version of Python and own version of Gunicorn. So, you will always have options when using this kind of setup. Enjoy!

Any Comments, Always Welcome!