Skip to main content

Flask Gunicorn Setup

Introduction

When you're developing Flask applications, you typically use Flask's built-in development server. However, this server isn't designed for production environments – it's single-threaded, can't handle concurrent requests efficiently, and lacks security features necessary for real-world deployments.

This is where Gunicorn (Green Unicorn) comes in. Gunicorn is a Python WSGI (Web Server Gateway Interface) HTTP server that acts as a middle layer between your Flask application and the internet. By using Gunicorn with Flask, you create a robust, production-ready architecture capable of handling multiple concurrent requests with better performance and stability.

In this tutorial, we'll learn how to configure Gunicorn to serve your Flask application in a production environment.

Prerequisites

Before we begin, make sure you have:

  • A working Flask application
  • Python 3.6+ installed
  • Basic understanding of command line operations
  • Understanding of virtual environments in Python

What is Gunicorn?

Gunicorn (Green Unicorn) is a Python WSGI HTTP server for UNIX systems. It:

  • Implements the WSGI specification to interface with Python web applications
  • Provides a pre-fork worker model (spawning multiple processes to handle requests)
  • Supports various worker configurations for different workloads
  • Is lightweight, easy to configure, and production-ready

Installing Gunicorn

Let's start by installing Gunicorn in your project's virtual environment:

bash
# Activate your virtual environment first
# For example:
# source venv/bin/activate # On Linux/Mac
# venv\Scripts\activate # On Windows

pip install gunicorn

Make sure to add Gunicorn to your requirements.txt file:

bash
pip freeze > requirements.txt

Basic Gunicorn Configuration with Flask

Step 1: Prepare Your Flask Application

Your Flask application should be structured in a way that separates the Flask instance from the run command. Here's a common structure:

myapp/
├── app/
│ ├── __init__.py # Contains your Flask application instance
│ ├── routes.py
│ ├── models.py
│ └── ...
├── wsgi.py # Entry point for Gunicorn
└── requirements.txt

Inside app/__init__.py, create your Flask application:

python
from flask import Flask

def create_app():
app = Flask(__name__)
# Configure your app

from app.routes import main_bp
app.register_blueprint(main_bp)

return app

Step 2: Create a WSGI Entry Point

Create a wsgi.py file in the root of your project:

python
from app import create_app

app = create_app()

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

This file serves as the entry point for Gunicorn.

Step 3: Run Your Application with Gunicorn

Now you can run your Flask application using Gunicorn:

bash
gunicorn wsgi:app

This command tells Gunicorn to look for the app object inside the wsgi.py file.

By default, Gunicorn will:

  • Listen on localhost (127.0.0.1) at port 8000
  • Run with a single worker process
  • Not daemonize (run in the foreground)

Advanced Gunicorn Configuration

You can customize Gunicorn's behavior using command-line arguments:

Setting Worker Processes

The number of worker processes determines how many concurrent requests your application can handle:

bash
gunicorn --workers=4 wsgi:app

A common formula for determining the optimal number of workers is (2 × number_of_cores) + 1.

Binding to a Different Host/Port

To make your application accessible from other machines or use a specific port:

bash
gunicorn --bind=0.0.0.0:8000 wsgi:app

Using Worker Classes

Gunicorn supports different types of worker processes:

bash
gunicorn --worker-class=gevent wsgi:app

Common worker classes include:

  • sync (default): Standard synchronous workers
  • gevent: For asynchronous workers with gevent
  • eventlet: For asynchronous workers with eventlet

Running as a Daemon

For production, you'll often want to run Gunicorn as a background process:

bash
gunicorn --daemon --workers=4 --bind=0.0.0.0:8000 wsgi:app

Using a Configuration File

For complex configurations, it's better to use a configuration file:

Create a file named gunicorn_config.py:

python
# Gunicorn configuration file
bind = "0.0.0.0:8000"
workers = 4
worker_class = "gevent"
max_requests = 1000
timeout = 30
keepalive = 2
errorlog = "logs/error.log"
accesslog = "logs/access.log"
loglevel = "info"

Then run Gunicorn with:

bash
gunicorn -c gunicorn_config.py wsgi:app

Working with Nginx and Gunicorn

In production environments, Gunicorn is typically used behind a reverse proxy like Nginx. Nginx handles static files, SSL termination, and load balancing, while Gunicorn focuses on running your Python application.

Here's a basic Nginx configuration to work with Gunicorn:

nginx
server {
listen 80;
server_name yourdomain.com;

location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location /static {
alias /path/to/your/static/files;
}
}

Real-World Example: Deploying a Flask Blog

Let's look at a complete example of deploying a simple Flask blog with Gunicorn.

Sample Application Structure

flask_blog/
├── blog/
│ ├── __init__.py
│ ├── routes.py
│ ├── models.py
│ ├── templates/
│ └── static/
├── wsgi.py
├── gunicorn_config.py
├── requirements.txt
└── start.sh

Application Code (blog/init.py)

python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db.init_app(app)

from blog.routes import main
app.register_blueprint(main)

with app.app_context():
db.create_all()

return app

WSGI File (wsgi.py)

python
from blog import create_app

app = create_app()

if __name__ == "__main__":
app.run(debug=True)

Gunicorn Configuration (gunicorn_config.py)

python
bind = "0.0.0.0:8000"
workers = 3
worker_class = "sync"
timeout = 120
errorlog = "logs/error.log"
accesslog = "logs/access.log"
loglevel = "info"

Start Script (start.sh)

bash
#!/bin/bash
mkdir -p logs
gunicorn -c gunicorn_config.py wsgi:app

Make the script executable:

bash
chmod +x start.sh

Running the Application

bash
./start.sh

Monitoring and Managing Gunicorn

For a production deployment, you'll want to ensure your Gunicorn process stays running and restarts if it crashes. Several tools can help with this:

Using Systemd

Create a systemd service file /etc/systemd/system/flask-blog.service:

[Unit]
Description=Gunicorn instance to serve Flask blog
After=network.target

[Service]
User=yourusername
Group=yourgroup
WorkingDirectory=/path/to/flask_blog
ExecStart=/path/to/flask_blog/venv/bin/gunicorn -c gunicorn_config.py wsgi:app
Restart=always

[Install]
WantedBy=multi-user.target

Enable and start the service:

bash
sudo systemctl enable flask-blog
sudo systemctl start flask-blog

Check the status:

bash
sudo systemctl status flask-blog

Using Supervisor

Supervisor is another popular process manager. First, install it:

bash
pip install supervisor

Create a configuration file /etc/supervisor/conf.d/flask-blog.conf:

[program:flask-blog]
directory=/path/to/flask_blog
command=/path/to/flask_blog/venv/bin/gunicorn -c gunicorn_config.py wsgi:app
autostart=true
autorestart=true
stderr_logfile=/path/to/flask_blog/logs/supervisor.err.log
stdout_logfile=/path/to/flask_blog/logs/supervisor.out.log
user=yourusername

Update supervisor and start the application:

bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start flask-blog

Performance Tuning

To optimize Gunicorn for production environments:

  1. Worker Type: Choose the right worker type for your application's workload:

    • sync: For standard, mostly synchronous applications
    • gevent or eventlet: For applications with many concurrent requests
  2. Worker Count: Follow the formula (2 × CPU cores) + 1 as a starting point

  3. Worker Timeout: Adjust the timeout value based on your application's needs

    bash
    gunicorn --timeout 60 wsgi:app
  4. Max Requests: Configure workers to restart after handling a certain number of requests to prevent memory leaks

    bash
    gunicorn --max-requests 1000 wsgi:app
  5. Keepalive: Set a keepalive value to handle keep-alive connections

    bash
    gunicorn --keepalive 5 wsgi:app

Summary

In this tutorial, we've covered:

  • Why you need Gunicorn for production Flask deployments
  • How to install and configure Gunicorn with your Flask application
  • Advanced Gunicorn configuration options including workers, binding, and worker classes
  • Integrating Gunicorn with Nginx for a production setup
  • A complete real-world example of deploying a Flask blog
  • Monitoring and managing Gunicorn with systemd and Supervisor
  • Performance tuning tips for production deployments

By using Gunicorn to deploy your Flask applications, you significantly improve performance, security, and stability. This setup forms the foundation of a robust production environment that can handle real-world traffic and demands.

Additional Resources

Practice Exercises

  1. Deploy a simple Flask "Hello World" application using Gunicorn
  2. Configure Gunicorn with 4 worker processes and gevent worker class
  3. Create a systemd service file for your Flask application
  4. Set up Nginx as a reverse proxy in front of your Gunicorn server
  5. Create a monitoring script that checks if your Gunicorn process is running and sends an alert if it's not


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)