Framework Guide

Flask API Monitoring

Set up health check endpoints and uptime monitoring for your Flask application. Simple patterns for production-ready monitoring.

Why Monitor Your Flask App?

Flask applications are lightweight by design, which means they rely heavily on external services -- databases, caches, task queues, and third-party APIs. When any of these fail, your Flask app may still respond to requests but return errors or incomplete data. External monitoring catches these issues before they affect users.

  • Gunicorn worker crashes -- A segfault or OOM kill takes down a worker silently; other workers keep serving, masking partial failures
  • SQLAlchemy connection issues -- Stale connections after a database restart cause intermittent query errors
  • Extension failures -- Flask extensions (Flask-Mail, Flask-Caching) can fail independently without crashing the app
  • Memory leaks -- Long-running Flask processes with Gunicorn can accumulate memory until workers are killed

Simple Health Endpoint

from flask import Flask, jsonify
from datetime import datetime

app = Flask(__name__)

@app.route('/health')
def health():
    return jsonify({
        'status': 'healthy',
        'timestamp': datetime.utcnow().isoformat()
    })

With Database Check

from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import time

app = Flask(__name__)
db = SQLAlchemy(app)

@app.route('/health')
def health():
    return jsonify({'status': 'healthy'})

@app.route('/ready')
def ready():
    checks = {}
    healthy = True

    # Check database
    try:
        start = time.time()
        db.session.execute('SELECT 1')
        checks['database'] = {
            'status': 'ok',
            'response_time_ms': round((time.time() - start) * 1000, 2)
        }
    except Exception as e:
        healthy = False
        checks['database'] = {
            'status': 'error',
            'message': str(e)
        }

    status_code = 200 if healthy else 503
    return jsonify({
        'status': 'healthy' if healthy else 'unhealthy',
        'timestamp': datetime.utcnow().isoformat(),
        'checks': checks
    }), status_code

Blueprint for Health Checks

# health.py
from flask import Blueprint, jsonify, current_app
from datetime import datetime
import time

health_bp = Blueprint('health', __name__)

@health_bp.route('/health')
def health():
    return jsonify({'status': 'healthy'})

@health_bp.route('/ready')
def ready():
    from extensions import db, redis_client

    checks = {}
    healthy = True

    # Database check
    try:
        start = time.time()
        db.session.execute('SELECT 1')
        checks['database'] = {
            'status': 'ok',
            'response_time_ms': round((time.time() - start) * 1000, 2)
        }
    except Exception as e:
        healthy = False
        checks['database'] = {'status': 'error', 'message': str(e)}

    # Redis check
    try:
        start = time.time()
        redis_client.ping()
        checks['redis'] = {
            'status': 'ok',
            'response_time_ms': round((time.time() - start) * 1000, 2)
        }
    except Exception as e:
        healthy = False
        checks['redis'] = {'status': 'error', 'message': str(e)}

    return jsonify({
        'status': 'healthy' if healthy else 'unhealthy',
        'checks': checks,
        'version': current_app.config.get('VERSION', 'unknown')
    }), 200 if healthy else 503

# app.py
from health import health_bp
app.register_blueprint(health_bp)

Using flask-healthz

# Install
pip install flask-healthz

# app.py
from flask import Flask
from flask_healthz import healthz, HealthError

app = Flask(__name__)

def liveness():
    pass  # Just needs to not raise

def readiness():
    try:
        db.session.execute('SELECT 1')
    except Exception:
        raise HealthError("Database not ready")

app.register_blueprint(healthz, url_prefix="/health")
app.config['HEALTHZ'] = {
    "live": liveness,
    "ready": readiness,
}

Exclude from Logging

# Prevent health checks from cluttering logs
import logging

class HealthCheckFilter(logging.Filter):
    def filter(self, record):
        return '/health' not in record.getMessage()

# Apply to werkzeug logger
logging.getLogger('werkzeug').addFilter(HealthCheckFilter())

Best Practices

  • Use blueprints — Keep health checks organized and reusable
  • Check all dependencies — Database, cache, external APIs
  • Add timeouts — Don't let slow checks hang the endpoint
  • Return proper status codes — 200 for healthy, 503 for unhealthy

Common Flask Production Issues

Running the Dev Server in Production

Flask's built-in server (app.run()) is single-threaded and not designed for production traffic. It can only handle one request at a time, so health checks will timeout while another request is processing. Always use Gunicorn or uWSGI in production.

SQLAlchemy Connection Recycling

SQLAlchemy pools connections by default. After a database restart or network hiccup, stale connections cause OperationalError. Set pool_pre_ping=True in your engine configuration and include a DB check in your readiness endpoint to detect this.

Application Context Errors

Flask requires an application context for database queries and config access. Health checks that import extensions at module level can fail with "Working outside of application context." Use current_app and lazy imports inside your health check functions.

Large Request Body Blocking

File uploads or large JSON payloads can tie up all Gunicorn workers, making your app unresponsive. Health checks with timeouts detect when all workers are occupied. Configure MAX_CONTENT_LENGTH and use async workers for upload-heavy applications.

Frequently Asked Questions

What should a Flask health check endpoint return?
A Flask health check should return a JSON response with HTTP 200 containing a status field. Use Flask's jsonify() to return structured data. For production apps, include database connectivity, Redis status, and response time. Return HTTP 503 with status "unhealthy" if any critical dependency fails.
Should I use a Flask Blueprint for health checks?
Yes, using a Blueprint keeps health check routes organized and reusable across projects. Create a health Blueprint with /health for liveness and /ready for readiness. Register it in your app factory. This pattern also makes it easy to exclude health routes from authentication and logging middleware.
How do I run Flask in production for reliable monitoring?
Never use Flask's built-in development server in production. Use Gunicorn with multiple workers (gunicorn -w 4 app:app) or uWSGI behind Nginx. This ensures health check requests are served even when some workers are busy. UptimeSignal monitors the public URL, so the production WSGI setup is what gets checked.
How do I prevent health check requests from filling Flask logs?
Create a custom logging.Filter class that checks if "/health" appears in the log message, and add it to the werkzeug logger. Alternatively, configure your reverse proxy (Nginx) to not log requests to the health endpoint. This keeps your logs focused on real application traffic.

Monitor your Flask API

Add your health endpoint to UptimeSignal and get alerted when it fails. Free tier includes 25 monitors.

Start monitoring free →

No credit card required. Commercial use allowed.

More Framework Guides