Skip to main content

Flask Production Server

Introduction

When you're developing a Flask application, you typically use the built-in development server that comes with Flask. This server is perfect for development but has limitations that make it unsuitable for production environments:

  • It's single-threaded and can only handle one request at a time
  • It lacks security features needed in production
  • It's not optimized for performance or stability
  • Flask itself warns against using it in production with the message: "Do not use the development server in a production environment"

In this tutorial, you'll learn how to set up a production-ready server for your Flask application using industry-standard tools that provide better performance, security, and reliability.

Why Use a Production Server?

Before diving into implementation, let's understand why a production server is essential:

  1. Performance: Production servers can handle multiple concurrent requests efficiently
  2. Reliability: They're designed to run continuously without downtime
  3. Security: They include security features to protect against common web vulnerabilities
  4. Scalability: They can be configured to scale as your application grows

WSGI - The Bridge Between Flask and the Web

WSGI (Web Server Gateway Interface) is a specification that describes how a web server communicates with web applications. Flask applications need a WSGI server in production to handle requests properly.

Popular WSGI servers include:

  • Gunicorn (Green Unicorn)
  • uWSGI
  • Waitress (Windows-friendly)

For this tutorial, we'll focus on Gunicorn, which is widely used in the Python community.

Setting Up a Production Server with Gunicorn and Nginx

We'll create a production setup using:

  • Gunicorn: A WSGI HTTP server for running the Flask application
  • Nginx: A high-performance web server that acts as a reverse proxy

This is a common and robust setup used by many production Flask applications.

Step 1: Install Required Packages

First, make sure you have your Flask application ready. Then install Gunicorn:

bash
pip install gunicorn

Step 2: Create a WSGI Entry Point

Create a file named wsgi.py in your project's root directory:

python
from your_app_name import app

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

Replace your_app_name with the name of your Flask application.

Step 3: Run Your Application with Gunicorn

Now you can start your Flask application with Gunicorn:

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

Here's what each part means:

  • --workers=3: Run 3 worker processes (typically 2x number of CPU cores + 1)
  • --bind=0.0.0.0:8000: Listen on all network interfaces on port 8000
  • wsgi:app: Use the app object from the wsgi.py file

Your Flask application is now running with Gunicorn and can handle multiple requests concurrently!

Step 4: Install and Configure Nginx

While Gunicorn is much better than Flask's development server, using Nginx in front of it adds even more benefits:

  1. Efficient serving of static files
  2. Load balancing
  3. SSL/TLS termination
  4. Protection against certain types of attacks

First, install Nginx:

bash
# For Ubuntu/Debian
sudo apt-get update
sudo apt-get install nginx

# For CentOS/RHEL
sudo yum install epel-release
sudo yum install nginx

Step 5: Configure Nginx to Proxy Requests to Gunicorn

Create a new Nginx configuration file:

bash
sudo nano /etc/nginx/sites-available/flask_app

Add the following configuration:

nginx
server {
listen 80;
server_name your_domain.com www.your_domain.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;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /static {
alias /path/to/your/static/files;
expires 30d;
}
}

Create a symbolic link to enable the site:

bash
sudo ln -s /etc/nginx/sites-available/flask_app /etc/nginx/sites-enabled/

Test the Nginx configuration and restart it:

bash
sudo nginx -t
sudo systemctl restart nginx

Now, visitors to your domain will be served by Nginx, which forwards requests to your Gunicorn server running the Flask application.

Running Gunicorn with Systemd for Persistence

To ensure your application starts automatically after server reboots, you can create a systemd service:

bash
sudo nano /etc/systemd/system/flask_app.service

Add the following content:

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

[Service]
User=username
Group=username
WorkingDirectory=/path/to/your/app
Environment="PATH=/path/to/your/venv/bin"
ExecStart=/path/to/your/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 wsgi:app

[Install]
WantedBy=multi-user.target

Replace username and paths with your actual values. Then enable and start the service:

bash
sudo systemctl enable flask_app
sudo systemctl start flask_app

Check its status:

bash
sudo systemctl status flask_app

Real-World Example: Deploying a To-Do List Application

Let's walk through deploying a simple To-Do List Flask application to production.

The Flask Application

Here's our example application (app.py):

python
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todos.db'
db = SQLAlchemy(app)

class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
task = db.Column(db.String(200), nullable=False)
completed = db.Column(db.Boolean, default=False)

@app.route('/')
def index():
todos = Todo.query.all()
return render_template('index.html', todos=todos)

@app.route('/add', methods=['POST'])
def add_todo():
task = request.form.get('task')
new_todo = Todo(task=task)
db.session.add(new_todo)
db.session.commit()
return redirect(url_for('index'))

@app.route('/complete/<int:todo_id>')
def complete(todo_id):
todo = Todo.query.get_or_404(todo_id)
todo.completed = not todo.completed
db.session.commit()
return redirect(url_for('index'))

if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)

Create WSGI Entry Point

Create wsgi.py:

python
from app import app

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

Deployment Steps

  1. Upload your code to your server (using Git, SCP, or SFTP)

  2. Create a virtual environment and install dependencies:

    bash
    python -m venv venv
    source venv/bin/activate
    pip install flask flask_sqlalchemy gunicorn
  3. Initialize the database:

    bash
    python -c "from app import app, db; app.app_context().push(); db.create_all()"
  4. Test with Gunicorn:

    bash
    gunicorn --bind 127.0.0.1:8000 wsgi:app
  5. Create systemd service file as shown earlier

  6. Configure Nginx as shown earlier

  7. Start services:

    bash
    sudo systemctl start flask_app
    sudo systemctl restart nginx

After completing these steps, your To-Do List application should be accessible via your domain, running on a production-ready server setup.

Best Practices for Flask in Production

To make your Flask application truly production-ready, consider these additional best practices:

  1. Use environment variables for configuration:

    python
    import os

    app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'fallback-key-for-dev')
  2. Implement proper logging:

    python
    import logging
    from logging.handlers import RotatingFileHandler

    if not app.debug:
    file_handler = RotatingFileHandler('app.log', maxBytes=10240, backupCount=10)
    file_handler.setFormatter(logging.Formatter(
    '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
    ))
    file_handler.setLevel(logging.INFO)
    app.logger.addHandler(file_handler)
    app.logger.setLevel(logging.INFO)
    app.logger.info('Application startup')
  3. Use HTTPS by configuring SSL/TLS in Nginx (you can use Let's Encrypt for free certificates)

  4. Implement rate limiting to prevent abuse:

    bash
    pip install flask-limiter
    python
    from flask_limiter import Limiter
    from flask_limiter.util import get_remote_address

    limiter = Limiter(
    app,
    key_func=get_remote_address,
    default_limits=["200 per day", "50 per hour"]
    )
  5. Monitor your application using tools like Prometheus, Grafana, or simpler tools like Datadog or New Relic

Summary

In this tutorial, you've learned how to deploy a Flask application to a production environment using industry-standard tools:

  • Why the Flask development server is not suitable for production
  • How to use Gunicorn as a WSGI server for better performance and reliability
  • How to set up Nginx as a reverse proxy for added security and features
  • How to ensure your application runs persistently using systemd
  • Best practices for running Flask applications in production

By following these steps, you've transformed your development Flask application into a robust, production-ready web service that can handle real-world traffic efficiently and securely.

Additional Resources

Exercises

  1. Deploy a simple Flask application using the methods described in this tutorial.
  2. Configure your production server to handle static files efficiently.
  3. Implement HTTPS using Let's Encrypt.
  4. Set up a monitoring solution for your Flask application to track performance metrics.
  5. Implement a CI/CD pipeline that automatically deploys your Flask application when you push to a Git repository.


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