A brief overview of WSGI and usage of Gunicorn and Nginx in Django

This article will briefly explain the functionality and usage of WSGI, Gunicorn and Nginx in collaboration with Django. It will help you to understand the process of deploying Django applications on the server and will clarify the role of WSGI file.

Let’s start this by creating a simple Django server. The first step is to install Python dependencies. We would only need Django and Gunicorn.

pip install django gunicorn

The second step is to create a Django project:

django-admin startproject django_server_application .

The next step that most of the Django developers normally do on local environment is to run the Django project with the following command in the terminal.

python manage.py runserver

It’s a management command built in Django and will simplify the process of running a development server in no time. This server is only suitable for local development and is not a wise option to run applications on a production environment. We won’t dig deep into risks and vulnerabilities here and Django documentation also warns about its implications.

https://docs.djangoproject.com/en/dev/ref/django-admin/#runserver

runserver management command is neither scalable nor reliable. It’s designed to run servers instantly for local development. So, instead of using this management command, we’re going to use Gunicorn to run our Django application.

Gunicorn and WSGI

Gunicorn is a library that is battle-tested, reliable and easily scalable by creating multiple workers. It’s a WSGI server and it’s capable to run any web application that supports WSGI like Django or Flask applications.

So what exactly is WSGI?

It’s the protocol and standard of communication between a web server and a web application. In this example, Gunicorn is the web server while Django and Flask can play the role of web applications. WSGI is like a common language of communication between web servers and web applications. Web server needs a way to communicate messages with web applications and web applications should know how to respond to web server requests. So WSGI is the common medium to support this 2-way communication.

Let’s create a Python application that would be compatible with WSGI. WSGI expects a callable object that takes 2 arguments and it should return an iterable of strings.

Here is a simplified example of WSGI compatible Python script:

# hello_world.py

def process_http_request(environ, start_response):
    status = '200 OK'
    response_headers = [
        ('Content-type', 'text/plain; charset=utf-8'),
    ]
    start_response(status, response_headers)
    text = 'Hello World'.encode('utf-8')
    return [text]

Now save this script file and specify its path to the below Gunicorn server command to test our application script:

$ gunicorn hello_world:process_http_request --bind 127.0.0.1:8000

Now open this URL in your browser:

http://127.0.0.1:8000/

You would a see blank page with a ‘Hello World’ response. Congratulation you’ve successfully deployed a web application on a WSGI compatible Gunicorn server.

Let’s talk about the arguments of our running script. The first argument is environ which is a dictionary that contains information about the request sent by the browser to our server. Whenever a request is made, Gunicorn gets the request, it populates the dictionary, and when it calls the function process_http_request, it passes this dictionary as a parameter.

The second argument start_response is a function that we need to call if we want to send a status code and headers of the response.

Gunicorn & WSGI with Django

Whenever a Django project is created, a wsgi.py file is also generated automatically. Inside this file, there is a function named get_wsgi_application.

import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
application = get_wsgi_application()

Django calls this function to return a WSGI compatible callable object. This callable object also takes two arguments environ and start_response and similarly returns an iterable of strings.

This callable object is similar to the one that we implemented above but this one is much more complicated.

Let’s run Gunicorn WSGI server for our Django application:

$ gunicorn lifecycle.wsgi:application --bind 0.0.0.0:8000

Congratulations, now we’re running our Django application on a WSGI server. This brings us one step closer to safely deploying our web application on a real server. Now let’s take about Nginx server.

Nginx with Django

Nginx will behave as our interface server to the outside world. It will handle and process all the requests sent by the browser.

It’s time to bring together Nginx, Gunicorn and Django in action. Whenever the browser wants dynamically generated content like an HTML page, Nginx will collect the request and forward it to Gunicorn and Django as Django is responsible for generating HTML pages. Once the processing is done, Gunicorn will take the HTML page generated by Django and will send it back to Nginx and Nginx will serve it back to the browser as a response.

We don’t need Gunicorn and Django to process all the user requests. Whenever the browser will request some static content or media files, Nginx can directly server those files without involving Gunicorn and this will save our processing power of Django to serve necessary requests fast and better.

Now let’s try to quickly install Nginx and run a web server. Here’s the command to install Nginx:

$ sudo apt install nginx

Once the installation is done, we need to configure our firewall to allow incoming traffic on port 80 and port 443 if we wanna set up HTTPS. Let’s just settle without HTTPS for now, so we’ll open port 80 only.

$ sudo ufw allow 'Nginx HTTP'

Let’s create a configuration file:

$ vi /etc/nginx/sites-available/django_server_application

upstream server_django {
    server 0.0.0.0:8000;
}

server {
    listen 80;
    location / {
        proxy_pass http://server_django;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }
    location /static/ {
        alias /app/static/;
    }
}

In the above configurations, whenever the location will be root, Nginx will proxy requests to Gunicorn server and if the location is static, then Nginx will take the responsibility of serving static files without disturbing Gunicorn and Django.

With our new configuration file above, it’s time to remove the default Nginx configuration file. Simply replace the default file with our newly created file in the sites-available folder of Nginx.

$ rm -rf /etc/nginx/sites-available/default
$ rm -rf /etc/nginx/sites-enabled/default

The next step is to create the soft link in the sites-enabled folder.

$ sudo ln -s /etc/nginx/sites-available/django_server_application /etc/nginx/sites-enabled/

Once the configuration is done, its time to restart our Nginx server:

$ sudo systemctl restart nginx

Conclusion

So now you’re in a better position to understand the core concepts of WSGI, Gunicorn and Nginx. If you have any questions, leave the comments below.