FastAPI has become the default choice for new Python APIs — async from the ground up, typed, fast, and pleasant to work in. But a FastAPI deployment is more than one uvicorn process: it is a pool of workers behind a reverse proxy, an async database engine with a finite connection pool, usually Redis, and background work running in Celery, ARQ, or APScheduler. Any of those can fail while GET / keeps returning 200 — and async apps add a failure mode all their own, where one blocking call quietly degrades every request at once.
This guide walks through monitoring a FastAPI app the way it actually breaks. We will build a proper async health endpoint (FastAPI does not ship one), enumerate the FastAPI-specific failure modes worth alerting on, cover background work, and finish with a concrete CronAlert setup you can copy. CronAlert is agentless and runs on Cloudflare's edge, so everything here is just HTTP checks, content assertions, and heartbeats — nothing to install in your app.
Build a health endpoint (FastAPI doesn't ship one)
Unlike Rails or Laravel, which now include an /up route out of the box, FastAPI leaves health checks to you. That is five minutes of work, and worth doing properly. Add an async def route at /healthz that verifies the dependencies your app cannot serve traffic without, and returns 200 when they pass and 503 when any fail. For the general pattern — shallow vs deep checks, what to include and exclude — see our guide to HTTP health check endpoints.
A good FastAPI health check does three things. First, it runs a trivial query — SELECT 1 — through your async engine (SQLAlchemy's async session or an asyncpg pool), which confirms both that the database is reachable and that the connection pool can still hand out a connection. Second, it does a quick round-trip against Redis or your cache with an async client. Third, it touches any external service the app is useless without. Wrap each in try/except so a failing dependency yields a clean 503 rather than an unhandled 500, and keep every call async — a health check that blocks the event loop degrades the very app it vouches for. Our database health endpoint deep-dive covers the query-level details.
Two more tips. Use FastAPI's lifespan context to create the engine and pool at startup, so a failed startup means the app never reports healthy in the first place. And keep the response body tiny and free of diagnostic detail — or gate a richer version behind a static token your monitor sends in a header.
What to monitor in a FastAPI app
One health endpoint is the foundation, not the whole story. Spread checks across the surfaces your users and integrators actually touch:
- The API root or homepage. Whatever real clients hit first, checked for a 200 and for expected content — not just that something responded.
- Your
/healthzendpoint. The dependency-aware signal that catches database and pool trouble. - Your most important endpoints. The two or three routes your product actually depends on, each with a keyword assertion on the JSON — a field name that only appears in a correct response. See monitoring API endpoints for assertion patterns.
- The docs endpoint, if you publish one. If integrators rely on
/docs, a broken OpenAPI schema (which can fail independently of your routes) deserves its own check. - HTTPS and the SSL certificate. An expired certificate takes down every client no matter how healthy uvicorn is; see SSL certificate monitoring.
All of this fits comfortably inside CronAlert's free plan — 25 monitors at a 3-minute interval with SSL and content checks included. Pro tightens the interval to 1 minute and unlocks keyword monitoring across every monitor.
FastAPI-specific failure modes
Generic uptime advice misses the ways async Python apps actually break. Design your checks around these:
- Event-loop blocking. One synchronous call inside an
async defroute —requestsinstead ofhttpx, a sync driver, CPU-bound work — stalls every request on that worker. The signature from outside: all endpoints slow down together, then time out together. A response-time threshold catches this while it is still degradation rather than downtime. - Async pool exhaustion. A connection leak or traffic spike empties the asyncpg/SQLAlchemy pool; every request awaits a connection that never arrives. Only a health check that actually acquires a connection sees it.
- Silent BackgroundTasks loss. FastAPI's
BackgroundTasksare in-process and fire-and-forget: a worker restart or an exception mid-task and the work is simply gone, with nothing in your logs' critical path. - Dead workers behind a healthy proxy. uvicorn/gunicorn workers crash or OOM; the reverse proxy answers 502/503 while the process manager flaps. Your monitor sees it instantly;
docker psmay not. - Failed lifespan startup. A bad migration or unreachable dependency during the
lifespanstartup leaves the app half-alive — importable but refusing connections. - TrustedHostMiddleware 400s. Host headers not in
allowed_hostsget rejected with a 400 — FastAPI's version of the DjangoALLOWED_HOSTS/ Railsconfig.hostssurprise when you monitor a new domain. - Stalled schedulers. APScheduler, ARQ cron jobs, or Celery beat stop firing after a deploy or a config error; nothing errors, work just stops happening.
Most of these never change your homepage's status code. That is the recurring lesson across every framework we have covered: status codes lie, and you need dependency-aware checks, content assertions, and response-time thresholds to see the truth.
Monitoring background work: Celery, ARQ, and APScheduler
Background processing is where API monitoring most often falls short. Your endpoints can be flawless while every Celery worker is dead, ARQ's queue is backing up in Redis, or APScheduler silently stopped after an exception in a job — and no HTTP check will notice, because nobody makes a request that fails. Emails stop, reports stop, syncs stop, and you find out from a customer.
The fix is heartbeat monitoring: your job reaches out instead of the monitor reaching in. Every time a periodic task completes successfully, it makes a quick HTTP request to a unique CronAlert heartbeat URL. You configure the expected interval plus a grace period, and if the ping stops arriving, CronAlert alerts you that the pipeline went silent — silence is the alarm. A practical setup is one lightweight scheduled task on the same cadence as your most critical periodic work, whose only job is to confirm it can reach the broker and database, then ping the heartbeat.
This is also the answer for anything you were tempted to run in BackgroundTasks: move work that matters to a real worker, and put a heartbeat on it. Our guides to cron job heartbeat monitoring and background worker monitoring cover the patterns in depth, and for one-shot jobs see batch job monitoring. Heartbeats are on every CronAlert plan, including free.
Catching "up but wrong" with content checks
A FastAPI response can be a perfect 200 and still be wrong: an endpoint returning an empty list because a silent exception handler swallowed the real error, a stale cache serving yesterday's data, or a misrouted deploy serving the wrong service entirely. To catch these, assert on the response body.
For JSON APIs the pattern is simple: require a string that only a correct response contains — a known field name, an expected enum value, a version marker. If the response shape changes or an error payload takes its place, the keyword disappears and the check fails despite the 200. You can also assert that an error marker is absent, so a leaked traceback or a default error JSON trips the alarm. Regex matching (Pro) handles dynamic payloads. For endpoints that should be byte-stable, a SHA-256 content-hash check alerts the instant the output drifts. See the keyword monitoring guide for building robust assertions.
Response-time thresholds matter more for async apps
In a threaded app, one slow endpoint mostly hurts itself. In an async app, event-loop blocking makes slowness global — which means response time is not just a performance metric but an availability early-warning. Set a response-time threshold on your health endpoint and your key routes so that a creep past, say, two seconds alerts you while the app is still up. The same threshold catches degrading databases, cold caches, and saturated workers before they become 504s. Our timeout thresholds guide covers picking numbers that alert early without generating noise.
A concrete CronAlert setup
Here is an end-to-end setup you can replicate in a few minutes after you create a free CronAlert account:
- Add a
/healthzroute. AsyncSELECT 1through your engine, async cache ping, 200/503. Wire the engine vialifespanso failed startups never report healthy. - Create an HTTP monitor for it. Point CronAlert at
https://api.yourapp.com/healthz, expect 200, set the interval (3 minutes free, 1 minute on Pro). - Monitor your key endpoints with JSON keyword assertions. Require a field name only present in a correct response.
- Monitor
/docsif integrators use it. The OpenAPI schema can break independently of your routes. - Add a heartbeat per scheduler/worker. A periodic Celery, ARQ, or APScheduler task pings its CronAlert heartbeat URL on every successful run, with a sensible grace period.
- Turn on SSL and response-time checks. Certificate monitoring on the domain; a response-time threshold on the health endpoint and key routes.
- Wire up alert channels. Email, Slack, Discord, Teams, Telegram, PagerDuty, Opsgenie, Splunk On-Call, webhooks, or PWA push.
Every plan includes the full REST API — natural for FastAPI developers; you can script your whole monitor fleet with httpx — and CronAlert ships an MCP server so Claude Code, Cursor, or Windsurf can manage monitors for you. Teams needing multi-region coverage can move to the Team plan, which checks from five edge regions with quorum so a single regional blip never pages you.
Frequently asked questions
Does FastAPI have a built-in health check endpoint?
No — unlike Rails and Laravel's /up, you add your own. Five lines: an async def route at /healthz that runs SELECT 1 through your async engine, pings the cache, and returns 200 or 503. Keep it fully async so the check never blocks the loop it vouches for.
Should a FastAPI health check verify the database?
Yes. Execute a trivial query through the async engine so an unreachable database or exhausted pool surfaces as a failure. Async apps are especially prone to quiet pool exhaustion — every request awaits a connection that never comes while process-liveness checks keep passing. Return 503 on any dependency failure.
How do I monitor FastAPI background tasks and workers?
Move important work out of in-process BackgroundTasks (which vanish silently on restarts or exceptions) into Celery, ARQ, or APScheduler — then put heartbeats on it: each periodic job pings a CronAlert heartbeat URL on success, and silence beyond the grace period triggers an alert.
Why is my FastAPI app slow or timing out for every request at once?
Almost always event-loop blocking: a sync call (requests, a sync driver, CPU-heavy work) inside an async def route stalls every request on that worker. A response-time threshold catches the global degradation early; the fix is moving sync work to def routes or a real worker.
Why does my FastAPI app return 400 Invalid host header to a monitor?
That is TrustedHostMiddleware rejecting a Host header missing from allowed_hosts — FastAPI's equivalent of Django's ALLOWED_HOSTS 400 and Rails' blocked-hosts 403. Add every hostname you monitor to allowed_hosts.
Start monitoring your FastAPI app
FastAPI apps fail in ways a single ping never catches: a blocked event loop degrading everything at once, an exhausted async pool, dead Celery workers, stalled schedulers, and 200s wrapping wrong responses. A deep async health endpoint, keyword assertions on key routes, heartbeats on background work, and SSL plus response-time checks give you a signal you can trust — and all of it fits in CronAlert's free plan. Create a free CronAlert account and point your first check at /healthz in the next five minutes.
Related reading: HTTP health check endpoints, building a database health endpoint, API endpoint monitoring, and our companion guides for Django, Rails, and Laravel applications.