Set Up Uptime Monitoring and Alerts for Django

Set up external uptime monitoring for Django: what to check, health endpoints, keyword and SSL checks, heartbeats for jobs, and alert routing.

Problem statement

Many Django sites learn about outages from users: an email saying the checkout page is broken, a social media post about the site being down, or a client asking why nobody replied to a form. Internal logs and error tracking cannot detect every failure, because some failures stop requests from reaching Django at all: DNS problems, expired certificates, a crashed server, a misconfigured load balancer, or a full disk that stops the database.

External uptime monitoring checks the site the way users reach it, from outside your infrastructure, and alerts someone when it fails. It is the cheapest monitoring to set up and often the most valuable. Done carelessly, it creates noise: alerts for single timeouts, checks against pages that return 200 while showing an error message, or nobody receiving the alert at 3 a.m.

From failure to a humanProbeexternal checkDetectionrepeated failuresAlertrouted to on-callResponserunbook stepAlert on user-visible symptoms so every page has an action attached.

Quick answer

  1. Monitor from outside your infrastructure, from more than one location.
  2. Check the homepage, a lightweight health endpoint, and at least one critical user flow URL.
  3. Use keyword checks so a 200 response with an error page counts as down.
  4. Monitor certificate expiry and domain expiry.
  5. Add heartbeat (dead man's switch) monitors for scheduled jobs, backups, and Celery Beat.
  6. Require a failure from multiple locations or consecutive checks before alerting.
  7. Route alerts to a channel someone actually watches, with an escalation path for real outages.

Step-by-step solution

1. Choose what to monitor

A small, deliberate set of checks covers most failure modes:

CheckTypeCatches
https://www.example.com/HTTP, keywordDNS, TLS, proxy, app, template errors
https://www.example.com/healthz/HTTP, statusapp process health behind the proxy
https://www.example.com/readyz/HTTP, statusdatabase connectivity
https://www.example.com/accounts/login/HTTP, keywordauth pages, static assets referenced
http://www.example.com/HTTP, redirectHTTP-to-HTTPS redirect working
TLS certificatecertificate expiryfailed renewals
Domain registrationexpirylapsed domains
Nightly backupheartbeatbackup job not running
Celery Beatheartbeatscheduler stopped
what each check catchesCatchesHTTP statussite down, 5xxKeyword✓blank or error page with 200Health endpointdatabase or dependency failureCertificate expiryrenewal that silently failedHeartbeat✓cron or Celery jobs that stoppedStatus checks alone miss the most embarrassing failuresdjango-deployment.com
Start with status, keyword, and certificate checks; add heartbeats for jobs.

Most hosted uptime services (UptimeRobot, Better Stack, Pingdom, Healthchecks.io for heartbeats, and cloud provider synthetics) support these check types. Self-hosted options such as Uptime Kuma work too, as long as they run outside the infrastructure they monitor.

2. Build health endpoints

A shallow health endpoint confirms that the request reached Django:

# core/views.py
from django.db import connection
from django.http import HttpResponse, JsonResponse
from django.views.decorators.cache import never_cache


@never_cache
def healthz(request):
    return HttpResponse("ok", content_type="text/plain")


@never_cache
def readyz(request):
    try:
        with connection.cursor() as cursor:
            cursor.execute("SELECT 1")
    except Exception:
        return JsonResponse({"status": "error"}, status=503)
    return JsonResponse({"status": "ok"})
# config/urls.py
from django.urls import path

from core.views import healthz, readyz

urlpatterns = [
    path("healthz/", healthz),
    path("readyz/", readyz),
    # ...
]

Keep the responses minimal. Do not expose versions, hostnames, or dependency details publicly. never_cache prevents a CDN or proxy cache from serving a stale "ok".

3. Use keyword checks

A page that renders "Server Error (500)" with a 200 status (for example a custom error page returned incorrectly, or a CDN serving a cached error page) passes a status-only check. Configure the monitor to look for text that only appears when the page works, such as a heading or a footer string:

  • homepage: a stable phrase from the navigation or footer
  • login page: the text of the submit button

Avoid text that changes with content updates or A/B tests.

4. Certificate and domain expiry

Certificate renewal can fail silently. Configure a certificate check that alerts 14 to 21 days before expiry. Also monitor the domain's registration expiry, or enable auto-renew with a valid payment method and a monitored billing contact.

5. Heartbeat monitoring for jobs

Uptime checks cannot detect a job that never runs. Heartbeat monitors invert the model: your job pings a unique URL when it succeeds, and the service alerts if a ping does not arrive on schedule.

alerting on silence02:00 job runspings on success03:00 job runspings on success04:00 job failsno pingGrace period ends04:15Alert sentmissing heartbeatPing only after the job succeeds, not when it startsdjango-deployment.com
A grace period absorbs normal variation in job run time.

At the end of a backup script:

#!/bin/bash
set -euo pipefail
pg_dump --format=custom --file="/var/backups/myapp-$(date +%F).dump" "$DATABASE_URL"
aws s3 cp "/var/backups/myapp-$(date +%F).dump" "s3://myapp-backups/"
curl -fsS -m 10 --retry 3 "$BACKUP_HEARTBEAT_URL" > /dev/null

Because of set -e, the ping only happens if every previous command succeeded.

For a Celery Beat heartbeat, schedule a small task that pings the URL every few minutes:

import requests
from celery import shared_task
from django.conf import settings


@shared_task(ignore_result=True)
def scheduler_heartbeat():
    requests.get(settings.BEAT_HEARTBEAT_URL, timeout=10)

A missing ping then means Beat, the broker, or workers stopped.

Treat heartbeat URLs as secrets; anyone with the URL can report false successes.

6. Reduce false alarms

Single failed checks happen: brief network issues between the monitoring location and your site, a deploy restart, a slow response. Configure:

  • check interval: one to five minutes for most sites
  • confirmation: alert only after two or three consecutive failures, or failures from multiple locations
  • timeouts: longer than your normal response time but short enough to detect hangs (for example 10 to 30 seconds)
  • maintenance windows: pause alerts during planned maintenance
alert on confirmed failuresNoisy✕one location✕alert on the first failure✕5 s timeout on a slow pageReliable✓two or more locations agree✓2 consecutive failures✓timeout well above p99A pager that cries wolf gets muteddjango-deployment.com
Each false alarm makes the next real one slower to get attention.

7. Route alerts to people

An alert that goes to an unread mailbox is not monitoring. Decide:

  • Where: a team chat channel for warnings; a paging tool or phone notification for outages
  • Who: an on-call person or rotation, even if it is one person for a small project
  • Escalation: who is contacted if the first person does not acknowledge within a set time
  • Status page: optionally, publish status automatically from checks so users see known incidents
who gets paged, and whenPrimary on-callpush and phone, immediatelySecondaryif not acknowledged in 10 minTeam channelevery alert, for contextStatus pageupdated for user-facing outagesEmail alone is not an alert routedjango-deployment.com
Test the route on a quiet day by triggering a harmless alert.

Test the whole path by triggering a real alert, for example by pausing a staging site or pointing a check at a URL that returns 503.

8. Write a short runbook

Attach a link in the alert to a runbook with the first steps:

  1. Confirm the outage from a second network (phone data, another location).
  2. Check the hosting provider's status page.
  3. Check server health: systemctl status nginx gunicorn postgresql, df -h, free -m.
  4. Check recent deploys and roll back if the outage started right after one.
  5. Check the error tracker and logs for new exceptions.
  6. Post an update in the team channel and on the status page.

9. Verify

  • Stop Gunicorn in a staging environment: the health check fails and an alert arrives within the expected time.
  • Break the database connection in staging: readyz fails while healthz still passes.
  • Skip a backup run: the heartbeat monitor alerts after its grace period.

Verification check:

  • alerts reach the intended person or channel
  • a single transient failure does not page anyone
  • certificate expiry is monitored with enough lead time
  • every scheduled critical job has a heartbeat monitor

Explanation

External checks measure what users experience, independent of the internal systems that might themselves be broken during an incident. Health endpoints separate "the process answers" from "dependencies work", keyword checks catch pages that load but are wrong, and heartbeats cover work that happens without any request. Confirmation rules and clear alert routing turn those signals into a small number of actionable notifications.

Edge cases / notes

Sites behind authentication

For internal apps, monitor the login page publicly and use a dedicated monitoring account with minimal permissions for authenticated checks, if your monitoring service supports it.

CDNs and caching

A CDN can serve cached pages while the origin is down, which hides outages from homepage checks. Monitor a non-cached health endpoint as well.

Regional outages

Monitoring from several regions shows whether an outage is global or limited to one network path.

Synthetic checks for critical flows

Simple URL checks confirm pages load; they do not confirm that users can complete important actions. For revenue-critical or support-critical flows, add a scripted synthetic check that runs every few minutes:

# scripts/synthetic_login_check.py
import os
import re
import sys

import requests

BASE = os.environ["SITE_URL"]
session = requests.Session()

login_page = session.get(f"{BASE}/accounts/login/", timeout=10)
login_page.raise_for_status()
match = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', login_page.text)
if not match:
    sys.exit("login page missing CSRF token")

response = session.post(
    f"{BASE}/accounts/login/",
    data={
        "csrfmiddlewaretoken": match.group(1),
        "username": os.environ["SYNTHETIC_USER"],
        "password": os.environ["SYNTHETIC_PASSWORD"],
    },
    headers={"Referer": f"{BASE}/accounts/login/"},
    timeout=10,
    allow_redirects=False,
)
if response.status_code != 302:
    sys.exit(f"login failed with status {response.status_code}")

dashboard = session.get(f"{BASE}/dashboard/", timeout=10)
if dashboard.status_code != 200 or "Sign out" not in dashboard.text:
    sys.exit("dashboard not reachable after login")

print("synthetic login check passed")

Run it from a scheduler outside your infrastructure, and ping a heartbeat URL on success. Use a dedicated test account with no access to real customer data, exclude it from analytics and rate limits where appropriate, and rotate its password like any other credential.

Choosing alert thresholds

  • Response time: alert when a check exceeds several times the normal response time for multiple consecutive checks, not on single slow responses.
  • Certificate expiry: 21 days gives time to fix renewal problems during working hours.
  • Heartbeat grace period: the job interval plus the job's typical runtime plus a margin, for example a nightly backup expected at 02:00 that takes 30 minutes gets a grace period until 04:00.
  • Maintenance: schedule monitor pauses together with planned maintenance so alerts stay meaningful.

Common mistakes

  • Monitoring from the same server or network as the site.
  • Status-only checks on pages that can return 200 with an error message.
  • No heartbeat monitoring for backups.
  • Alerts sent to an address nobody reads.

Next steps

FAQ

What should I monitor for a Django website?

The homepage with a keyword check, a health endpoint, a critical page such as login, the HTTP-to-HTTPS redirect, certificate expiry, and heartbeats for backups and scheduled jobs.

What is heartbeat monitoring?

A monitor that expects a periodic ping from your job and alerts when the ping is missing. It detects jobs that fail or never run, which uptime checks cannot see.

How often should uptime checks run?

Every one to five minutes is typical. Require consecutive failures or multiple locations before alerting to avoid noise from brief network issues.

Should the health check endpoint test the database?

Keep one shallow endpoint for basic availability and a separate one that checks the database. Monitoring both helps distinguish application failures from dependency failures.

Learning path · step 5 of 13

Keeping it running

Keep going