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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)