Reference Guide Detailed deployment notes with production context and concrete examples.

Flask Monitoring and Error Tracking Setup

If you're trying to add monitoring and error tracking to a Flask production deployment, this guide shows you how to set it up step-by-step. The goal is to capture application errors, verify service health, and make production issues visible before they become outages.

Quick Fix / Quick Setup

Install a basic error tracker, add a health endpoint, and make Gunicorn logs visible in journalctl.

bash
pip install sentry-sdk gunicorn
python
# app.py
from flask import Flask
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration

sentry_sdk.init(
    dsn="https://YOUR_KEY@o0.ingest.sentry.io/0",
    integrations=[FlaskIntegration()],
    traces_sample_rate=0.0,
)

app = Flask(__name__)

@app.get("/health")
def health():
    return {"status": "ok"}, 200
ini
# /etc/systemd/system/flask.service
[Service]
Environment="GUNICORN_CMD_ARGS=--access-logfile - --error-logfile - --capture-output --log-level info"
bash
sudo systemctl daemon-reload
sudo systemctl restart flask
sudo systemctl status flask
curl -I http://127.0.0.1:8000/health
journalctl -u flask -n 100 --no-pager

This gives you a working baseline: Flask error tracking with Sentry, a health endpoint, and Gunicorn logs visible in the systemd journal. Expand it with uptime checks, Nginx access logs, and alerting.

What’s Happening

A Flask app can fail at multiple layers: application code, Gunicorn workers, systemd, Nginx, the database, or external APIs. Without health checks, logs, and exception reporting, production failures usually appear as generic 500 or 502 errors with little context. A minimal monitoring setup should provide three signals: health status, logs, and exception reporting.

Step-by-Step Guide

  1. Install an error tracking SDK
    Install sentry-sdk in the same environment as your Flask app.
    bash
    pip install sentry-sdk
    
  2. Initialize error tracking at application startup
    Add the SDK before handling requests.
    python
    # app.py
    from flask import Flask
    import sentry_sdk
    from sentry_sdk.integrations.flask import FlaskIntegration
    
    sentry_sdk.init(
        dsn="https://YOUR_KEY@o0.ingest.sentry.io/0",
        integrations=[FlaskIntegration()],
        traces_sample_rate=0.0,
        send_default_pii=False,
    )
    
    app = Flask(__name__)
    

    If you load the DSN from environment variables:
    python
    import os
    sentry_sdk.init(
        dsn=os.environ.get("SENTRY_DSN"),
        integrations=[FlaskIntegration()],
        traces_sample_rate=0.0,
    )
    
  3. Add a lightweight health endpoint
    Keep this route fast and predictable.
    python
    @app.get("/health")
    def health():
        return {"status": "ok"}, 200
    

    Test it locally:
    bash
    curl -i http://127.0.0.1:8000/health
    
  4. Enable Gunicorn access and error logs
    If Gunicorn runs under systemd, send logs to stdout/stderr so journald captures them.
    ini
    # /etc/systemd/system/flask.service
    [Unit]
    Description=Gunicorn instance for Flask app
    After=network.target
    
    [Service]
    User=www-data
    Group=www-data
    WorkingDirectory=/var/www/flask-app
    Environment="PATH=/var/www/flask-app/venv/bin"
    Environment="SENTRY_DSN=https://YOUR_KEY@o0.ingest.sentry.io/0"
    Environment="GUNICORN_CMD_ARGS=--access-logfile - --error-logfile - --capture-output --log-level info --timeout 30"
    ExecStart=/var/www/flask-app/venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 app:app
    Restart=always
    RestartSec=5
    
    [Install]
    WantedBy=multi-user.target
    
  5. Reload systemd and restart the Flask service
    Apply the updated service definition.
    bash
    sudo systemctl daemon-reload
    sudo systemctl restart flask
    sudo systemctl status flask
    
  6. Verify Gunicorn logs in journald
    Confirm startup output and request logs are visible.
    bash
    journalctl -u flask -n 200 --no-pager
    journalctl -u flask -f
    

    Look for:
    • startup failures
    • import errors
    • worker boot failures
    • incoming GET /health
    • Python tracebacks
  7. Enable Nginx access and error logs
    Make sure your site config logs requests and upstream errors.
    nginx
    server {
        listen 80;
        server_name your-domain.com;
    
        access_log /var/log/nginx/flask_access.log;
        error_log /var/log/nginx/flask_error.log warn;
    
        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 /health {
            proxy_pass http://127.0.0.1:8000/health;
            proxy_set_header Host $host;
        }
    }
    

    Validate and reload Nginx:
    bash
    sudo nginx -t
    sudo systemctl reload nginx
    
  8. Test the health endpoint through both paths
    Test Gunicorn directly:
    bash
    curl -i http://127.0.0.1:8000/health
    

    Test through Nginx/public routing:
    bash
    curl -i https://your-domain/health
    
  9. Verify service environment variables
    Confirm the DSN and Gunicorn args are actually loaded.
    bash
    sudo systemctl show flask --property=Environment
    sudo systemctl cat flask
    
  10. Trigger a controlled test exception
    Use a non-public route or staging environment to confirm error delivery.
    python
    @app.get("/debug-error")
    def debug_error():
        raise RuntimeError("test sentry error")
    

    Trigger it:
    bash
    curl -i http://127.0.0.1:8000/debug-error
    

    Confirm the event appears in your error tracking dashboard. Remove or protect this route after testing.
  11. Add an uptime monitor
    Point your uptime provider or internal monitoring system at:
    text
    https://your-domain/health
    

    Alert on:
    • endpoint unavailable
    • non-200 responses
    • repeated timeouts
  12. Document where each signal lives
    Use one source for each type of issue:
    • app exceptions → error tracking dashboard
    • process logs → journalctl -u flask
    • reverse proxy failures → Nginx logs
    • public availability → uptime monitor

Common Causes

  • No application error tracking initialized → Exceptions only appear as generic 500 responses → Install and initialize an error tracking SDK at app startup.
  • Gunicorn logs not enabled → Failures happen but nothing useful appears in journalctl → Start Gunicorn with access logs, error logs, and output capture enabled.
  • Missing /health endpoint → Uptime tools cannot distinguish app availability from page-level failures → Add a lightweight route that returns 200.
  • systemd environment variables not loaded → DSN, API keys, or config values are missing in production → Use Environment= or EnvironmentFile= and restart the service.
  • Nginx logs disabled or using wrong paths → Reverse proxy failures are hard to diagnose → Enable access_log and error_log in the site config.
  • Alerting not configured → Monitoring exists but nobody is notified → Add alerts for downtime, 5xx spikes, and new unhandled exceptions.
  • Health checks pointed at a heavy route → Monitoring causes load or false failures → Point checks at a lightweight endpoint with predictable output.
  • Testing only local logs → Public routing or TLS issues are missed → Validate both the local Gunicorn listener and the public domain.

Debugging Section

Check package installation:

bash
pip show sentry-sdk

Check Flask and Gunicorn service status:

bash
sudo systemctl status flask
sudo systemctl restart flask

Check recent application and Gunicorn logs:

bash
journalctl -u flask -n 200 --no-pager
journalctl -u flask -f

Check service environment and full unit definition:

bash
sudo systemctl show flask --property=Environment
sudo systemctl cat flask

Check local health endpoint:

bash
curl -i http://127.0.0.1:8000/health

Check public health endpoint:

bash
curl -i https://your-domain/health

Check Nginx configuration and reload status:

bash
sudo nginx -t
sudo systemctl reload nginx

Check Nginx logs:

bash
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/nginx/access.log
sudo tail -n 100 /var/log/nginx/flask_error.log
sudo tail -n 100 /var/log/nginx/flask_access.log

Check Gunicorn listener or socket:

bash
ss -ltnp | grep gunicorn || ss -lx | grep gunicorn
ps aux | grep gunicorn

What to look for:

  • missing SENTRY_DSN
  • worker boot errors
  • import/module errors
  • 502 Bad Gateway upstream failures in Nginx
  • 403 or 404 on /health
  • app listening on the wrong port or socket
  • test exception not reaching the error tracker

Checklist

  • Flask has a working /health endpoint returning 200
  • Gunicorn access and error logs are enabled and visible in journalctl
  • systemd restart policy is enabled for the Flask service
  • Nginx access and error logs are enabled for the site
  • Error tracking SDK is initialized with the correct production DSN
  • A test exception reaches the error tracking dashboard
  • An uptime monitor checks the public health endpoint
  • Alerts are configured for downtime and repeated application errors

FAQ

Q: Do I need both logging and error tracking?
A: Yes. Error tracking captures exceptions well, while logs help diagnose startup failures, routing issues, upstream errors, and non-exception events.

Q: What is the minimum production monitoring setup for Flask?
A: A health endpoint, Gunicorn and Nginx logs, systemd service visibility, one uptime check, and one exception tracking tool.

Q: Should health checks go through Nginx or directly to Gunicorn?
A: Use both during setup. Direct checks validate the app process; public checks validate the full production path.

Q: Why am I not seeing Flask exceptions in the dashboard?
A: The SDK may not be initialized, the DSN may be missing, outbound network access may be blocked, or the exception is being handled before reporting.

Q: Should /health check the database?
A: Use a shallow health check for load balancers and uptime probes. Add a separate deep check if you need dependency validation.

Q: Why log Gunicorn to stdout/stderr?
A: It keeps logs visible in the systemd journal and simplifies log collection on VPS and container-based deployments.

Final Takeaway

A usable monitoring setup for Flask is not one tool. Combine a health endpoint, visible Gunicorn and Nginx logs, systemd service visibility, and exception tracking so you can detect, locate, and fix failures quickly.