Back to blog
By Rasul

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:

  1. Set application dependency timeouts below the probe timeout.
  2. Set the probe timeout below the interval.
  3. Require enough consecutive failures to ignore a brief latency spike.
  4. Require enough successes to avoid flapping back into traffic.
  5. 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:

  1. Schedule a small heartbeat task through the same queue and worker pool as production work.
  2. Record its last successful completion in a bounded store or ping an external heartbeat monitor.
  3. Alert when the timestamp exceeds the expected interval plus a realistic grace period.
  4. 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.