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.
pip install sentry-sdk gunicorn
# 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
# /etc/systemd/system/flask.service
[Service]
Environment="GUNICORN_CMD_ARGS=--access-logfile - --error-logfile - --capture-output --log-level info"
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
- Install an error tracking SDK
Installsentry-sdkin the same environment as your Flask app.bashpip install sentry-sdk - 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:pythonimport os sentry_sdk.init( dsn=os.environ.get("SENTRY_DSN"), integrations=[FlaskIntegration()], traces_sample_rate=0.0, ) - Add a lightweight health endpoint
Keep this route fast and predictable.python@app.get("/health") def health(): return {"status": "ok"}, 200
Test it locally:bashcurl -i http://127.0.0.1:8000/health - Enable Gunicorn access and error logs
If Gunicorn runs under systemd, send logs to stdout/stderr sojournaldcaptures 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 - Reload systemd and restart the Flask service
Apply the updated service definition.bashsudo systemctl daemon-reload sudo systemctl restart flask sudo systemctl status flask - Verify Gunicorn logs in journald
Confirm startup output and request logs are visible.bashjournalctl -u flask -n 200 --no-pager journalctl -u flask -f
Look for:- startup failures
- import errors
- worker boot failures
- incoming
GET /health - Python tracebacks
- Enable Nginx access and error logs
Make sure your site config logs requests and upstream errors.nginxserver { 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:bashsudo nginx -t sudo systemctl reload nginx - Test the health endpoint through both paths
Test Gunicorn directly:bashcurl -i http://127.0.0.1:8000/health
Test through Nginx/public routing:bashcurl -i https://your-domain/health - Verify service environment variables
Confirm the DSN and Gunicorn args are actually loaded.bashsudo systemctl show flask --property=Environment sudo systemctl cat flask - 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:bashcurl -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. - Add an uptime monitor
Point your uptime provider or internal monitoring system at:texthttps://your-domain/health
Alert on:- endpoint unavailable
- non-200 responses
- repeated timeouts
- 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
500responses → 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
/healthendpoint → Uptime tools cannot distinguish app availability from page-level failures → Add a lightweight route that returns200. - systemd environment variables not loaded → DSN, API keys, or config values are missing in production → Use
Environment=orEnvironmentFile=and restart the service. - Nginx logs disabled or using wrong paths → Reverse proxy failures are hard to diagnose → Enable
access_loganderror_login the site config. - Alerting not configured → Monitoring exists but nobody is notified → Add alerts for downtime,
5xxspikes, 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:
pip show sentry-sdk
Check Flask and Gunicorn service status:
sudo systemctl status flask
sudo systemctl restart flask
Check recent application and Gunicorn logs:
journalctl -u flask -n 200 --no-pager
journalctl -u flask -f
Check service environment and full unit definition:
sudo systemctl show flask --property=Environment
sudo systemctl cat flask
Check local health endpoint:
curl -i http://127.0.0.1:8000/health
Check public health endpoint:
curl -i https://your-domain/health
Check Nginx configuration and reload status:
sudo nginx -t
sudo systemctl reload nginx
Check Nginx logs:
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:
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 Gatewayupstream failures in Nginx403or404on/health- app listening on the wrong port or socket
- test exception not reaching the error tracker
Checklist
- Flask has a working
/healthendpoint returning200 - 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
Related Guides
- Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)
- Flask Production Logging Setup
- Fix Flask 502 Bad Gateway (Step-by-Step Guide)
- Flask Production Checklist (Everything You Must Do)
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.