How to Run Flask Applications with Nginx Using Gunicorn

We have recently bought a VPS for İTÜ24, the online newsletter of Istanbul Technical University. The server is running on Ubuntu Server 12.04 operating system. Due to limited memory resources and performance concerns, we preferred to setup nginx as web server.

Our server will serve several web pages and applications developed in various programming languages, such as PHP, Python, Ruby (on Rails). Currently, we have one Python application, which is using Flask framework.

How we run Flask application with nginx, step by step…

Step 0: Requirements

We work on a virtual Python environment, using virtualenv, and install Python packages with pip. Install these by typing:

sudo apt-get install python-virtualenv python-pip

Create a virtual environment and activate it.

virtualenv hello
source hello/bin/activate

Notice, your prompt is now prefixed with the name of the virtual environment.

Step 1: The Application

After installing Flask with pip, you can save the following code as “hello.py” and run it.

pip install Flask
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello world!"

if __name__ == '__main__':
    app.run()

Flask has a built-in web server that allows you to run your application. However, it is not scalable and production ready. On the other hand, there is gunicorn which is a production ready Python WSGI server that also provides scalability.

Step 2: Gunicorn

pip install gunicorn

In order to make our application to work with gunicorn, we have to add two lines to it:

from flask import Flask
from werkzeug.contrib.fixers import ProxyFix
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello world!"

app.wsgi_app = ProxyFix(app.wsgi_app)

if __name__ == '__main__':
    app.run()

Notice the 2nd and 9th lines. Now we can serve our application with Gunicorn.

gunicorn hello:app

“hello” is the name of the file (without extension). And “app” is the name of the Flask object. You can find more info about configuration of gunicorn on their web pages.

Step 3: Nginx

Create new server configuration and save the file in /etc/nginx/sites-available/hello.conf.

server {
    listen 80;
    server_name hello.itu24.com;

    root /path/to/hello;

    access_log /path/to/hello/logs/access.log;
    error_log /path/to/hello/logs/error.log;

    location / {
        proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        if (!-f $request_filename) {
            proxy_pass http://127.0.0.1:8000;
            break;
        }
    }
}

Enable new configuration by creating a symbolic link in sites-enabled directory.

sudo ln -s /etc/nginx/sites-available/hello.conf /etc/nginx/sites-enabled/

Check configuration for errors:

nginx -t

If your configuration is ok, you can reload nginx and access your application now.

sudo service nginx reload

Update 2012/09/07: In order to start gunicorn automatically when the system reboots, check my new blog post!

Update 2012/10/05: You had better not use if statements in Nginx configurations, because if is evil! See this comment

  • http://www.istihza.com istihza

    This is a very nice and informative article, but one thing not mentioned is how you manage processes started by gunicorn. The tag list of the post makes me think that it is supervisor that controls the processes. However supervisor part is missing in the article.

    • http://onurguzel.wordpress.com/ Onur Güzel

      I changed my mind while writing this post in order to keep this tutorial as simple as possible. And I wrote about supervisor in a different blog post. You can find it in the update section of the post.

  • Pingback: Managing Gunicorn Processes With Supervisor | Onur Güzel

  • http://www.facebook.com/eptitude Brian Visel

    Using the ‘if’ statement in a location like that is considered a bad idea by the nginx people.. ..they suggest instead using something like:
    location / {
    try_files $uri @gunicorn_proxy;
    }
    location @gunicorn_proxy {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://127.0.0.1:8000;
    }

    ..I don’t know if it matters that much, but it *is* one of their official pitfalls they warn about.

    • http://onurguzel.wordpress.com/ Onur Güzel

      Posted an update in the post. Thanks Brian!

  • Luis Alberto Romero Calderon

    why you add the lines 2 and 9 it will works without them.

  • kimjongwho

    got a question

    why do we use gunicorn? what happens when flask stops responding or goes down?

    isn’t gunicorn running on port 80 enough? why do we add nginx?

    I am trying to understand. I am using flask it’s great.

    • http://www.onurguzel.com Onur Güzel

      Gunicorn is a production ready WSGI server. Unlike the built-in development server in flask, gunicorn provides better management and distribution of the request.

      For managing flask/gunicorn processes, I use supervisord. You can check my related blog post here: http://www.onurguzel.com/managing-gunicorn-processes-with-supervisor/

      It is enough to run gunicorn on port 80. The reason I use nginx that there are several different web applications on the same server. Nginx passes request to them according to the hostname in the request.

      • kimjongwho

        thanks for the answer. recently i read that uWSGI performs better (according to benchmark ) than gunicorn, should I be using uWSGI instead?

        • http://www.onurguzel.com Onur Güzel

          gunicorn and uWSGI are both perfectly suitable for projects which will serve several hundreds of requests in a second. In this case the choice really depends on your preference. I think you may want to use gunicorn, since it is a bit easier to setup. However, for the projects that will serve more than a thousand requests per second, I would recommend you to use uWSGI as the only choice.

  • Pingback: Deploy Flask on Raspberry Pi with Nginx - Nginx Solutions - Developers Q & A

  • Pingback: Breadcrumbs | Serving Files with Flask behind nginx & gunicorn

  • Pingback: Using a boolean charge in an Angular Dart Component - Ziccardi Gyan