Django Health Check: Liveness vs Readiness
Design a Django health check for liveness, readiness, PostgreSQL, Redis, workers, safe responses, and deployment probes.
A production Django health check should answer one operational question at a time. Use liveness to decide whether the process should restart. Use readiness to decide whether the instance should receive traffic. Check worker freshness and optional providers separately. Driving one automated action from the aggregate failure of a large dependency checklist turns unrelated outages into the same response.
The endpoint is small. The failure policy is the real design. A database outage should usually remove an instance from traffic, not restart every healthy web process. A dead web process may need a restart even when PostgreSQL and Redis are healthy. A stopped background worker will not be discovered by a web-only probe at all.
This guide was verified against Django 6.0, current Kubernetes and AWS load
balancer documentation, the maintained django-health-check project, and
Djass's current PostgreSQL-and-cache health endpoint on August 3, 2026.
On this page: failure actions · probe contracts · dependency matrix · Django code · packages · deployment · workers · security · tests · checklist
Start with the failure action
A health check is an input to an automated decision. Name that decision before adding any dependency:
| Failed signal | Consumer | Intended action | What belongs in the check |
|---|---|---|---|
| Liveness | Process supervisor or orchestrator | Restart this web process | Conditions a restart can repair |
| Readiness | Load balancer or orchestrator | Stop new traffic to this instance | Dependencies required for the core request path |
| Startup | Orchestrator | Delay liveness and readiness during initialization | Whether this process has completed startup |
| Deep diagnostics | Operator or internal monitor | Investigate or page someone | Wider dependencies, capacity, provider state, and details |
| Worker heartbeat | Scheduler monitor | Alert on stale background execution | Last successful run or worker-owned heartbeat |
This table prevents the most common health-check mistake: assuming that more checks produce more safety. A liveness endpoint that queries PostgreSQL turns a database incident into web-process restarts. Those restarts do not repair the database. They consume capacity while the remaining processes receive more traffic.
Kubernetes makes the distinction explicit in its probe documentation. A failed liveness probe can restart the container. A failed readiness probe marks the Pod unready so matching Services stop sending it traffic. A startup probe delays the other two until initialization succeeds. The documentation also warns that a badly designed liveness probe can cause cascading failures under load.
The useful rule is short:
Put a condition in liveness only when restarting this Django process is a reasonable response to that condition.
That normally means liveness is cheap and local. It proves that the application server can execute a Django route and return promptly. It does not need to prove that every system the product has ever integrated is available.
Separate liveness, readiness, and diagnostics
The three contracts can share helpers, but their success criteria differ.
Liveness proves the web process can respond
A basic liveness route can return a fixed, minimal response. If Gunicorn or the ASGI server is deadlocked, saturated beyond the configured timeout, or unable to execute the route, the probe fails without calling PostgreSQL, Redis, S3, a payment provider, or an email API.
Audit the complete request path before calling it shallow. A fixed-response Django view still passes through the proxy and middleware. Session, authentication, tenant, or custom middleware can touch a database or provider before the view runs. Exempt the route deliberately where that behavior would turn liveness back into a dependency check.
Kubernetes notes that a separate liveness probe may be unnecessary when the process already exits on failure and the platform's restart policy handles it. Do not add a probe because the field exists. Add it when it detects a failure mode that the process supervisor would otherwise miss.
Readiness proves the instance can serve its core path
Readiness can include PostgreSQL when nearly every useful request needs the database. It can include Redis when sessions, authorization, throttling, or other required request behavior fails without the cache. If Redis is used only as an optional performance cache and Django has a correct fallback, Redis may not belong in readiness.
Returning HTTP 503 is useful here because it communicates that the instance is temporarily unavailable. The platform decides how many consecutive failures remove it and how many successes restore it. The application should not invent its own retry loop inside the route.
Deep diagnostics explain more without controlling traffic
A diagnostic endpoint or internal command can check object storage, queue depth, email configuration, disk space, provider APIs, and other signals. It should not automatically become the high-frequency load-balancer path. A slow third-party API can make the diagnostic yellow while the application continues to serve a useful degraded experience.
The maintained django-health-check
project bundles database, cache,
storage, disk, memory, DNS, email, Celery, Kafka, RabbitMQ, Redis, and cloud
provider checks. That breadth is valuable for diagnostics. The failure-action
matrix still decides which subset is allowed to take an instance out of
service.
Decide which dependencies belong in readiness
Classify each dependency by user impact and by whether removing one instance helps:
| Dependency | Put in liveness? | Put in readiness? | Better separate signal |
|---|---|---|---|
| Django route execution | Yes | Yes | Request latency and error rate |
| Primary database | No | Usually | Database saturation, replicas, slow queries |
| Redis used for required sessions or policy | No | Usually | Cache latency, evictions, memory |
| Redis used only for optional caching | No | Usually not | Degraded-mode metric |
| Queue broker | No | Only if requests must enqueue synchronously | Enqueue failures and queue depth |
| Background worker | No | No | Worker heartbeat and oldest-job age |
| S3-compatible media storage | No | Only when the core request path requires it | Upload/download synthetic check |
| Stripe, email, analytics, or another provider | No | Rarely | Provider-specific alert and degraded-mode metric |
"Usually" is deliberate. A marketing page may remain useful during a database incident while an authenticated SaaS dashboard may not. One global readiness contract should represent the service behind that load-balancer target. If two routes have materially different dependencies, consider separate services or a clear degraded mode instead of pretending one boolean captures both.
Database tests need a precise claim. SELECT 1 proves that the configured
connection can execute a minimal query at that moment. It does not prove that
migrations are current, writes are authorized, replicas are caught up, or
business data is correct. Those are different checks with different costs.
For Redis, a write/read round trip proves more than opening a TCP connection.
Django's cache
API provides set() and
get() across supported backends. Use a short-lived marker so the route tests
the operation the application actually depends on without leaving permanent
keys.
Treat every green signal as bounded evidence:
| Signal | What it proves | What it does not prove |
|---|---|---|
| HTTP response | One web process executed the route before the timeout | Capacity under real traffic or another process's state |
Database SELECT 1 |
This process completed one trivial query | Applied migrations, writes, replica freshness, or business correctness |
| Cache set/get | This process completed one cache write/read round trip | Queue consumption, eviction safety, or every Redis-backed feature |
| Worker heartbeat | One queued job completed recently | Every queue is draining or all task types work |
This proof ladder is more useful than a single healthy: true label. It tells
an operator which conclusion the evidence supports and where another signal is
still required.
Build a small Django readiness endpoint
Djass's current /api/healthcheck applies a compact contract: it runs
SELECT 1, writes and reads a short-lived cache value, reports database and
Redis booleans, logs failures, and returns HTTP 503 if either required
dependency fails. The implementation is intentionally narrower than a full
monitoring dashboard.
The same pattern in a plain Django view looks like this:
import uuid
from django.core.cache import cache
from django.db import connection
from django.http import JsonResponse
from django.views.decorators.http import require_GET
@require_GET
def live(request):
return JsonResponse({"status": "ok"})
@require_GET
def ready(request):
checks = {"database": False, "cache": False}
try:
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
cursor.fetchone()
checks["database"] = True
except Exception:
pass
try:
key = f"healthcheck:{uuid.uuid4()}"
cache.set(key, "ok", timeout=10)
checks["cache"] = cache.get(key) == "ok"
except Exception:
pass
healthy = all(checks.values())
return JsonResponse(
{"healthy": healthy, "checks": checks},
status=200 if healthy else 503,
)
This example keeps exception details out of the public response. Production code should log a controlled event for each failed dependency, using exception information only in the restricted log destination. The Django logging guide shows how to keep event names and safe context stable without logging credentials or request bodies.
Do not copy the broad except Exception blocks into ordinary business logic.
They are appropriate at this boundary because the route must convert diverse
dependency failures into a small status contract. The failure logs and tests
make the swallowed exceptions observable.
The route also needs dependency-level time budgets. A load balancer timeout will abandon a slow check, but it does not stop a database call that continues inside the worker. Configure database and cache connection/query timeouts so the application finishes before the platform's probe timeout. Keep the endpoint free of retries; repeated platform probes already provide temporal confirmation.
Choose a package or a small custom view
Use a custom view when you have a small dependency set and a deliberate failure policy. The code above is easy to review, test, and keep aligned with your deployment target.
Use django-health-check when its maintained plugin set matches the diagnostic
coverage you need, or when several teams need a consistent extension API. The
current 4.4.4 release on
PyPI, published July 28, 2026,
supports Django 5.2 and 6.0. Its
4.x migration guide
uses explicit HealthCheckView.as_view(checks=[...]) configuration, so avoid
older tutorials that install retired sub-apps or include the old URL module.
Read each enabled backend as a policy choice. The package's checks
reference says its database
check executes SELECT 1; its cache check writes and reads a key; its storage
check writes, reads, and deletes a file; and separate Celery plugins can ping
workers or enqueue a task. Installing every plugin and attaching the aggregate
result to liveness recreates the coupling problem in a more configurable form.
The package cookbook is an implementation example, not a universal failure policy. Before putting a CPU, memory, or disk check in liveness, ask whether a restart repairs the pressure. If it does not, the restart can amplify the incident Kubernetes warns about.
Django's own system check framework is not a substitute for either option. The
Django 6.0 system-check
documentation describes
project validation that runs before many management commands but, for
performance, is not part of the deployed WSGI request stack. Database-specific
checks can run when aliases are supplied explicitly. Run manage.py check and
manage.py check --deploy in CI or deployment validation. Use a runtime route
for current process and dependency state.
The two mechanisms complement each other:
- System checks reject known-invalid configuration before deployment.
- Runtime probes tell the platform what this running instance can do now.
- Synthetic tests exercise a real user path from outside the application.
- Metrics and alerts explain trends before a boolean threshold flips.
Django's CONN_HEALTH_CHECKS=True setting is another separate mechanism. The
database documentation
explains that it checks a reused persistent connection once per request when
that request accesses the database. It can recover cleanly after a database
restart, but it does not expose a health endpoint or tell a load balancer
whether to route traffic.
Set timeouts, cadence, and thresholds together
A correct route can still cause trouble when the platform calls it too often or reacts too quickly.
For Kubernetes HTTP probes, status codes from 200 through
399 count as
success. Kubernetes defaults periodSeconds to 10, timeoutSeconds to 1, and
failureThreshold to 3, though production values should follow the service's
measured startup and latency behavior. Its documentation recommends a
dedicated endpoint with a minimal response body.
Status-code rules vary by platform:
| Consumer | Default HTTP success | Redirect behavior |
|---|---|---|
| Kubernetes HTTP probe | 200-399 | A redirect can count as success |
| AWS Application Load Balancer | 200; matcher configurable from 200 to 499 | Depends on the configured matcher |
| Google Cloud HTTP health check | Exactly 200 | 301 and 302 are unhealthy |
Returning exactly HTTP 200 on success and 503 on failure is a portable contract across those defaults. This is a cross-platform inference, not a universal HTTP health-check standard. Google Cloud documents its rule in the health-check success criteria.
AWS Application Load Balancers use a similar set of controls. The ALB target health documentation defines the path, timeout, interval, success-code matcher, and consecutive healthy/unhealthy thresholds. The defaults for instance or IP targets are a 30-second interval, a 5-second timeout, five successes to recover, and two failures to become unhealthy. Treat those as documented defaults, not universal recommendations.
Choose the values as one failure budget:
- Set application dependency timeouts below the probe timeout.
- Set the probe timeout below the interval.
- Require enough consecutive failures to ignore a brief latency spike.
- Require enough successes to avoid flapping back into traffic.
- Allow startup enough time without weakening steady-state liveness.
Remember what happens at fleet level. AWS documents that an ALB with no healthy targets fails open and routes to all registered targets. Kubernetes warns that restarting containers during load can move more work onto the remaining Pods. The endpoint, thresholds, instance count, and degraded mode form one system.
Monitor background workers with freshness
A healthy web route says nothing about whether scheduled or queued work is moving. A Django Q2, Celery, or RQ worker can stop consuming jobs while the web process and broker continue to answer readiness checks.
Use a worker-owned freshness signal:
- Schedule a small heartbeat task through the same queue and worker pool as production work.
- Record its last successful completion in a bounded store or ping an external heartbeat monitor.
- Alert when the timestamp exceeds the expected interval plus a realistic grace period.
- Track oldest-job age and failure rate separately; one heartbeat does not prove every queue is draining.
For recurring jobs, the Django scheduled-tasks operations guide adds the intended-slot, missed-run, overlap, and completion-evidence contract that turns freshness into a useful business signal.
Djass-generated projects can include Django Q2 support. The background-jobs architecture explains the worker boundary, while the project-generation pipeline shows why durable project state remains in the database rather than in a health response.
This separation prevents a worker incident from restarting web processes and prevents a green web endpoint from hiding stalled background work.
Keep the response small and safe
Load balancers need a status code, not an internal inventory. Return stable dependency labels and booleans only when they help operations. Do not return exception messages, hostnames, database names, credentials, provider payloads, queue contents, version-control SHAs for private releases, or user data.
Whether the route is public depends on the platform. A load balancer may need an unauthenticated path reachable only inside a network boundary. An internal diagnostic endpoint can require stronger access control. Avoid redirects to a login page. Kubernetes treats 200-399 as probe success, so a redirect can mark the container healthy even though the intended view did not execute. Keep an ALB success matcher narrow for the same reason.
The django-health-check security
guide supports a
secret token in the URL and explicitly warns not to reuse Django's
SECRET_KEY. If you choose that pattern, account for URL logging at proxies
and application servers. Network restriction or a dedicated header may fit
your platform better. The minimal liveness response should not contain anything
sensitive even when access control is misconfigured.
Test the Host header seen in production too. AWS documents that an ALB can
send the target's private IP and health-check port as Host. Django requires a
suitable ALLOWED_HOSTS value when DEBUG=False; its deployment
checklist
warns that using a wildcard requires independent host validation. Configure the
proxy or probe host deliberately instead of weakening validation to make the
check green.
Log failures with a controlled event name, dependency name, outcome, and safe correlation context. Successful checks happen frequently, so sample them or record them as metrics if per-request success logs create noise. Health-check traffic should not dominate the logs used to investigate real requests.
Test the failure policy
Test the contract at the HTTP boundary, not only the helper functions:
| Scenario | Liveness | Readiness | Evidence to assert |
|---|---|---|---|
| Django route executes; dependencies healthy | 200 | 200 | Bounded success body |
| Database query raises | 200 | 503 | database: false; safe failure log |
| Cache write raises or marker mismatches | 200 | 503 | cache: false; no raw value in response |
| Both dependencies fail | 200 | 503 | Both labels false; one stable status |
| Diagnostic provider is down but core path degrades correctly | 200 | 200 | Separate provider alert |
| Worker heartbeat is stale | 200 | Depends on request contract | Worker alert, not web restart |
Also assert that:
- the routes allow only the intended HTTP methods;
- the response never includes exception text or settings;
- the platform path does not redirect;
- the endpoint finishes within the application time budget;
- a startup delay does not trigger a premature liveness restart;
- repeated failures and recoveries behave correctly at the platform threshold.
Mocks are appropriate for dependency exceptions and cache mismatches. Keep at least one environment-level check that reaches the real database and cache used in CI. That catches configuration drift a unit test cannot see.
Production Django health check checklist
- [ ] Name the automated action each endpoint controls.
- [ ] Keep liveness local to the Django process.
- [ ] Put only hard request-path dependencies in readiness.
- [ ] Use a startup probe when initialization needs a separate budget.
- [ ] Keep broad diagnostics off the high-frequency load-balancer path.
- [ ] Test the database operation you claim to test, and no more.
- [ ] Verify required cache behavior with a short-lived marker.
- [ ] Monitor worker freshness through the worker queue.
- [ ] Bound dependency calls below the platform timeout.
- [ ] Configure cadence and consecutive-failure thresholds from measured latency.
- [ ] Return a minimal response with no exception or secret data.
- [ ] Log failures as controlled events and prevent success-log noise.
- [ ] Test success, exception, mismatch, timeout, and recovery behavior.
- [ ] Run Django system checks in CI as a separate deployment gate.
Djass-generated repositories include a database-and-Redis readiness endpoint
independently of the optional outbound heartbeat setup. The use_healthchecks
choice in the Django starter module catalog
adds the outbound ping helper and its environment configuration for scheduled
work. The generated request middleware and Sentry sampler also suppress routine
probe traffic while retaining dependency failures as structured events.
That split was audited against the starter on August 3, 2026. It keeps inbound readiness, worker freshness, and telemetry hygiene as different contracts you can change as the product grows. You can review Djass pricing when you want that operational baseline generated with the rest of the repository.
Django health check FAQ
What is a Django health check?
A Django health check is a small runtime contract that reports whether a web process or its required dependencies can perform a defined job now. It is usually consumed by a load balancer, orchestrator, uptime monitor, or operator. The returned status must map to a specific action such as restart, remove from traffic, or alert.
What is the difference between liveness and readiness?
Liveness asks whether the Django process should be restarted. Readiness asks whether this instance should receive new traffic. A database failure can make an instance unready without making the web process dead. Keeping the probes separate avoids restart loops during dependency incidents.
Should a Django health check query the database and Redis?
Query PostgreSQL and round-trip Redis in readiness only when the core request path requires them. Keep both out of liveness because restarting Django does not repair an external database or cache. Optional caches and third-party providers usually belong in diagnostics or degraded-mode monitoring.
Is manage.py check a runtime health check?
No. Django's system check framework validates project configuration and code through management commands, including deployment and database-specific checks when requested. Django documents that these checks are not run inside the deployed WSGI stack. A runtime HTTP endpoint answers a different question about the current process and dependencies.
Should a health endpoint require authentication?
Use the access model your platform supports. A load balancer may need a minimal unauthenticated endpoint inside a private network, while a detailed diagnostic route should be restricted. In either case, return no exception messages, credentials, infrastructure identifiers, or user data.
Should a background worker share the web health endpoint?
No. A web endpoint cannot prove that a worker is consuming jobs. Send a small task through the real queue and alert when its completion heartbeat becomes stale. Track queue age and failures separately so one successful heartbeat does not hide a blocked queue.