Monitoring Your FastHTML Application with Vigilmon (Free, Multi-Region)
FastHTML is the Python web framework from Jeremy Howard (fast.ai) that makes HTMX-powered interactivity feel as natural as writing plain Python. If you've built ML-backed apps, internal tools, or rapid prototypes with FastHTML, you know how fast it goes from idea to deployed app.
What's less exciting is finding out your app has been down for two hours because nobody set up monitoring.
This guide covers how to add a health route to a FastHTML app and set up external uptime monitoring with Vigilmon — so you get alerted the moment your app becomes unreachable.
Why FastHTML apps need external monitoring
FastHTML apps are typically deployed as lightweight Python ASGI processes — on a VPS, on Fly.io, on Railway, or on any platform that runs a Docker container. Like any long-running process, they can:
- Crash after an unhandled exception in a route
- Hang when waiting on a blocked database call or external API
- Become unreachable when the deployment fails silently
- Run out of memory and get killed by the OS
When any of these happen, the process stops responding to HTTP requests. Your users get a connection error or a timeout. Without external monitoring, you won't find out until someone tells you.
Step 1: Add a health route to your FastHTML app
FastHTML makes this simple. Add a /health route that returns a JSON response:
from fasthtml.common import *
from starlette.responses import JSONResponse
import time
app, rt = fast_app()
@rt('/health')
def get():
return JSONResponse(
{'status': 'ok', 'timestamp': time.time()},
status_code=200
)
serve()
Verify it locally:
curl http://localhost:5001/health
# {"status":"ok","timestamp":1751280000.0}
Add a database check
If your FastHTML app uses a database (SQLite via MiniDataAPI, or a PostgreSQL/MySQL connection), include a lightweight connectivity check:
from fasthtml.common import *
from starlette.responses import JSONResponse
import time
# Example with MiniDataAPI (built-in SQLite)
db = database('data/app.db')
todos = db.t.todos
app, rt = fast_app()
@rt('/health')
def get():
try:
# Lightweight DB probe
todos.count
db_status = 'ok'
except Exception as e:
return JSONResponse(
{'status': 'error', 'database': 'error', 'detail': str(e)},
status_code=503
)
return JSONResponse(
{
'status': 'ok',
'database': db_status,
'timestamp': time.time()
},
status_code=200
)
serve()
For PostgreSQL via asyncpg or psycopg:
import asyncpg
@rt('/health')
async def get():
try:
conn = await asyncpg.connect(os.environ['DATABASE_URL'])
await conn.execute('SELECT 1')
await conn.close()
db_status = 'ok'
except Exception as e:
return JSONResponse(
{'status': 'error', 'database': 'error'},
status_code=503
)
return JSONResponse({'status': 'ok', 'database': db_status}, status_code=200)
Step 2: Set up Vigilmon monitoring
With the health route live, point Vigilmon at it:
- Sign up at vigilmon.online — free tier, no credit card
- Click New Monitor → HTTP
- Enter
https://your-fasthtml-app.com/health - Set the check interval (5 minutes on free tier)
- Save
Vigilmon probes from multiple geographic regions. If your app is unreachable from any region, it opens an incident immediately.
Add a keyword monitor for response validation
Add a second monitor that asserts the response body:
- New Monitor → Keyword
- URL:
https://your-fasthtml-app.com/health - Keyword:
"status":"ok" - If absent, Vigilmon treats it as a failure
This catches the case where the ASGI server is running but your health handler returns a database error — HTTP 503 with a body that says "status":"error".
Recommended monitor set for FastHTML
| Monitor | URL | Type | What it catches |
|---|---|---|---|
| App health | /health | HTTP | Process down, port not responding |
| Response check | /health | Keyword | Database error, degraded state |
| Main route | / | HTTP | Homepage broken (bad deploy) |
Step 3: Monitor ML model loading (for AI-backed apps)
FastHTML is popular in the Python/ML community for building AI-powered interfaces. If your app loads a model at startup (transformers, scikit-learn, etc.), add a readiness flag so the health check only returns 200 after the model has loaded:
from fasthtml.common import *
from starlette.responses import JSONResponse
import time
import threading
app, rt = fast_app()
model = None
model_ready = False
def load_model():
global model, model_ready
# Replace with your actual model loading
from transformers import pipeline
model = pipeline('text-generation', model='gpt2')
model_ready = True
# Load in background so the app starts fast
threading.Thread(target=load_model, daemon=True).start()
@rt('/health')
def get():
if not model_ready:
return JSONResponse(
{'status': 'starting', 'model': 'loading'},
status_code=503
)
return JSONResponse(
{'status': 'ok', 'model': 'ready', 'timestamp': time.time()},
status_code=200
)
serve()
This prevents Vigilmon from alerting during a normal cold start while the model loads. Once the model is ready, /health starts returning 200 and monitoring proceeds normally.
Step 4: Configure alert delivery
Slack:
- Create an incoming webhook in Slack
- In Vigilmon: Notifications → New Channel → Slack
- Enable it on your FastHTML monitors
Email:
- In Vigilmon: Notifications → New Channel → Email
- Add your address
When your FastHTML app goes down, you'll get:
🔴 DOWN: your-fasthtml-app.com/health
Status: Connection refused
Regions: US-East, EU-West, AP-Southeast
Started: 7 minutes ago
And when it recovers:
✅ RECOVERED: your-fasthtml-app.com/health is back UP
Total downtime: 12 minutes
Step 5: Deploy-time health check
If you deploy via Docker, add a health check so failed containers are restarted automatically:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5001/health')" || exit 1
CMD ["python", "main.py"]
Or in docker-compose.yml:
services:
app:
build: .
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5001/health')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
restart: unless-stopped
ports:
- "5001:5001"
Combined with Vigilmon, you get:
- Docker restarts the container if it fails its health check
- Vigilmon alerts you if the whole host goes unreachable, or the restart loop isn't resolving the issue
Step 6: Create a public status page
If you're sharing your FastHTML app with users or collaborators:
- Status Pages → New Status Page in Vigilmon
- Add your health monitor
- Share the public URL
A status page lets users check current uptime themselves during an incident instead of emailing you.
What you've built
| What | How |
|---|---|
| Health route | /health — plain FastHTML route returning JSON |
| Database check | Lightweight query inside the health handler |
| External monitoring | Vigilmon HTTP monitor (multi-region) |
| Body assertion | Vigilmon keyword monitor on "status":"ok" |
| ML readiness | Startup flag prevents cold-start false positives |
| Container health | Dockerfile / Docker Compose HEALTHCHECK |
| Instant alerts | Slack/email on down + recovery |
Your FastHTML app is now externally observable. The next time the ASGI process crashes, the database connection fails, or a deployment goes wrong, you'll know about it in minutes — not hours.
Get started free at vigilmon.online — your first monitor is running in under a minute.