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.
Quick answer
- Monitor from outside your infrastructure, from more than one location.
- Check the homepage, a lightweight health endpoint, and at least one critical user flow URL.
- Use keyword checks so a
200response with an error page counts as down. - Monitor certificate expiry and domain expiry.
- Add heartbeat (dead man's switch) monitors for scheduled jobs, backups, and Celery Beat.
- Require a failure from multiple locations or consecutive checks before alerting.
- 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:
| Check | Type | Catches |
|---|---|---|
https://www.example.com/ | HTTP, keyword | DNS, TLS, proxy, app, template errors |
https://www.example.com/healthz/ | HTTP, status | app process health behind the proxy |
https://www.example.com/readyz/ | HTTP, status | database connectivity |
https://www.example.com/accounts/login/ | HTTP, keyword | auth pages, static assets referenced |
http://www.example.com/ | HTTP, redirect | HTTP-to-HTTPS redirect working |
| TLS certificate | certificate expiry | failed renewals |
| Domain registration | expiry | lapsed domains |
| Nightly backup | heartbeat | backup job not running |
| Celery Beat | heartbeat | scheduler stopped |
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.
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
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
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:
- Confirm the outage from a second network (phone data, another location).
- Check the hosting provider's status page.
- Check server health:
systemctl status nginx gunicorn postgresql,df -h,free -m. - Check recent deploys and roll back if the outage started right after one.
- Check the error tracker and logs for new exceptions.
- 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:
readyzfails whilehealthzstill 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
200with an error message. - No heartbeat monitoring for backups.
- Alerts sent to an address nobody reads.
Next steps
- Design endpoints carefully with How to Build a Django Health Check Endpoint for Deployments.
- Add internal metrics with Monitor Django with Prometheus and Grafana.
- Review coverage with the Django Monitoring and Alerting Checklist.
- Handle certificate problems with Fix Let's Encrypt Certificate Renewal Failures.
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.