Backend framework

Monitor Django

Monitor Django apps, Celery workers, and management commands

HTTPSSLKeywordHeartbeatInterval: 1 minute

Why monitor Django

Django is unusually easy to misconfigure into "up but silently broken." ALLOWED_HOSTS rejects traffic with a 400 that looks nothing like a Python exception. DEBUG=True in production exposes your database host, name, user, and full stack traces with local variable contents on every error page. Celery workers maintain broker connections long after they've stopped processing tasks. Every one of these is invisible to process-level monitoring: the process is running, the database has connections, the metrics look fine. An external health check hitting a real URL and asserting both the status code and a keyword ("ok") catches the first two categories instantly. Heartbeats on scheduled tasks catch the Celery category. Pairing these with SSL monitoring covers the cert renewal path, which in most Django deployments lives on a reverse proxy (nginx, Caddy) or PaaS (Render, Railway) that your Django metrics never see.

Setting up Django monitoring

  1. 1
    Add a dedicated health app with the view above. Register /health/ in urls.py. Do not require auth; monitoring cannot carry a session.
  2. 2
    Add the hostname to ALLOWED_HOSTS. Hyperping sends a Host header matching the URL; with DEBUG=False, if the domain is missing from ALLOWED_HOSTS, Django returns 400 on every request.
  3. 3
    Ensure DEBUG=False in production so the health endpoint does not leak stack traces.
  4. 4
    Monitor https://yoursite.com/health/ at 1-minute intervals, keyword match on "ok" so a degraded response (503 with status=degraded) triggers an alert.
  5. 5
    Add heartbeat monitors on your critical Celery beat jobs: each task POSTs to Hyperping on success. A stalled beat scheduler is a common and silent failure.
  6. 6
    Add an SSL monitor on the apex domain. Django does not handle certs; your reverse proxy or PaaS does, and those renewal paths fail independently.

Example health endpoint

health/views.py
# health/views.py
from django.db import connection
from django.core.cache import cache
from django.http import JsonResponse

def health(request):
    checks = {}

    try:
        with connection.cursor() as cur:
            cur.execute("SELECT 1")
            cur.fetchone()
        checks["db"] = True
    except Exception as e:
        checks["db"] = False
        checks["db_error"] = str(e)[:80]

    try:
        cache.set("__health", 1, 5)
        checks["cache"] = cache.get("__health") == 1
    except Exception:
        checks["cache"] = False

    ok = all(v is True for k, v in checks.items() if not k.endswith("_error"))
    return JsonResponse(
        {"status": "ok" if ok else "degraded", "checks": checks},
        status=200 if ok else 503,
    )

# urls.py
# path("health/", views.health)

What typically goes wrong with Django

ALLOWED_HOSTS missing the monitored domain
After a domain migration or CDN change, the monitored hostname no longer matches ALLOWED_HOSTS. With DEBUG=False, every request returns 400. This looks like an app bug but is a config issue; external uptime catches it immediately.
Celery worker alive but not processing
Broker connection drops, worker reconnects but its consumer stops. Redis or RabbitMQ shows a connected client; tasks pile up forever. Heartbeat monitors on periodic tasks catch this; nothing else does reliably.
Migration lock during deploy
A long migration holds an exclusive lock; incoming requests queue until the connection pool drains. Health endpoint 503s. External monitoring distinguishes "deploy in progress" from "real outage" only if you have a good alert threshold (2+ failures).
Session backend unavailable
Redis or memcached down. Views using @login_required 500 with "cannot connect to cache". The home page may still work. A keyword monitor on a logged-in-path health check catches this; a plain uptime check does not.
DEBUG=True accidentally left on in prod
A config push flips DEBUG. Error pages leak the full stack trace, every local variable in each frame, and every setting not matching Django's sensitive-name filter (names containing API, KEY, PASS, SECRET, SIGNATURE, or TOKEN are redacted, but an exposed DATABASE_URL env var is not). Health endpoints still return 200. Only a keyword monitor asserting the absence of "Traceback" on an error path catches this.

Frequently asked questions

Where should the health endpoint live in a Django app?
In a dedicated app (health/) so it has no dependencies on your main apps and survives refactors. Register it at /health/ in the project urls.py, not inside a versioned API prefix.
Should the health endpoint require authentication?
No. A monitor cannot carry a session or OAuth token. If you need to prevent abuse, rate-limit the endpoint at the reverse proxy or accept a shared secret via a header.
How do I monitor Celery beat schedules?
Each scheduled task POSTs to a Hyperping heartbeat URL on success. The heartbeat has an expected interval; if it does not arrive, Hyperping alerts. This catches both a stalled beat and a task failing silently.
Does Django have built-in uptime monitoring?
No. Django has logging and the admin, but no production uptime checks. Third-party packages (django-health-check) give you the endpoint; you still need external monitoring to hit it.

Related monitoring guides

Monitor PostgreSQL
Monitor PostgreSQL availability, replication lag, and connection health
Get started

Start monitoring in the next 5 minutes.

Stop letting customers discover your outages first. Set up monitoring, status pages, on-call, and alerts before your next coffee break.

14 days free trial. No card required.