Skip to content

Cookbook

This cookbook shows how to set up multiple health check endpoints. Each endpoint has a different audience and use case.

A good setup has three tiers of endpoints:

Tier Purpose Consumers
Node Hardware & OS resources Kubernetes, Docker, reverse proxies
Application App-level services Uptime monitors, on-call alerts
Pipeline Upstream dependencies CI/CD, developer Slack/Matrix channels

Node health checks

Node checks make sure that the server has enough resources to run the application. Use them for liveness and readiness probes in container orchestrators, for example Kubernetes, Docker, and Podman. You can also use them for reverse-proxy checks in HAProxy, nginx, Caddy, and Traefik.

The psutil extra provides OS resource checks for CPU, memory, and disk usage.

pip install "django-health-check[psutil]"

Add the node endpoint to your URL configuration.

# urls.py
from django.urls import include, path
from health_check.views import HealthCheckView

node_checks = [
    "health_check.contrib.psutil.CPU",
    "health_check.contrib.psutil.Memory",
    "health_check.contrib.psutil.Disk",
]

urlpatterns = [
    # …
    path(
        "health/",
        include(
            [
                path(
                    "node/",
                    HealthCheckView.as_view(checks=node_checks),
                    name="health_check-node",
                ),
            ]
        ),
    )
]

Tip

See the Security section of the installation guide.

Kubernetes probes

Kubernetes uses liveness and readiness probes to decide when to restart a pod. The probes use HTTP. Thus they also prove that the full HTTP stack operates correctly.

Note

If the WSGI/ASGI server binds to 0.0.0.0, the kubelet can reach the httpGet probes. A bind to 127.0.0.1 is not sufficient.

The configuration for this health check endpoint is:

# deployment.yaml
livenessProbe:
  httpGet:
    path: /health/node/
    port: 8000
  initialDelaySeconds: 10
  periodSeconds: 30
readinessProbe:
  httpGet:
    path: /health/node/
    port: 8000
  initialDelaySeconds: 5
  periodSeconds: 10

See the Kubernetes documentation for more information.

Docker / Podman

Compose has no native HTTP probes. Thus this setup uses the health_check command. The command does not need CURL in the image. It can emulate proxy requests to satisfy HTTPS requirements.

# compose.yml
services:
  web:
    # … your service definition …
    healthcheck:
      test: ["CMD", "python", "manage.py", "health_check", "health_check-node", "web:8000"]
      interval: 30s
      timeout: 10s

Load balancers

Most reverse proxies can load balance. After configuration, they send traffic only to healthy instances.

The configuration for Caddy is:

# Caddyfile
example.com {
    reverse_proxy localhost:8000 {
        health_uri      /health/node/
        health_interval 30s
        health_timeout  5s
    }
}

The configuration for Traefik is:

# compose.yml
services:
  app:
    image: myapp
    labels:
      - "traefik.http.services.myapp.loadbalancer.healthcheck.path=/health/node/"
      - "traefik.http.services.myapp.loadbalancer.healthcheck.interval=30s"
      - "traefik.http.services.myapp.loadbalancer.healthcheck.timeout=5s"

The configuration for Nginx is:

# nginx.conf
upstream myapp {
    server localhost:8000;
    health_check interval=30s timeout=5s uri=/health/node/;
}

The configuration for HAProxy is:

# haproxy.cfg
backend myapp
    server app1 localhost:8000 check inter 30s fall 3 rise 2
    http-check expect status 200
    http-check send meth GET uri /health/node/

Application health checks

Application checks make sure that each production service of your application is reachable and operates correctly. Uptime monitors, for example Pingdom, Better Uptime, or StatusCake, watch these endpoints. They notify on-call engineers when a service fails.

The full stack includes databases, caches, message brokers, email, and storage. To cover the full stack, install these extra dependencies:

pip install "django-health-check[redis,rabbitmq,celery,crontask]"
# urls.py
from datetime import timedelta

from django.urls import include, path
from health_check.views import HealthCheckView
from redis.asyncio import Redis as RedisClient

application_checks = [
    # Django built-ins
    "health_check.Cache",
    "health_check.Database",
    "health_check.Mail",
    "health_check.Storage",
    # Message brokers & caches
    (
        "health_check.contrib.redis.Redis",
        {"client_factory": lambda: RedisClient.from_url("redis://localhost:6379")},
    ),
    (
        "health_check.contrib.rabbitmq.RabbitMQ",
        {"amqp_url": "amqp://guest:guest@localhost:5672//"},
    ),
    "health_check.contrib.celery.Ping",
    "health_check.contrib.crontask.Scheduler",
]

urlpatterns = [
    # …
    path(
        "health/",
        include(
            [
                # other endpoints …
                path(
                    "application/",
                    HealthCheckView.as_view(
                        checks=application_checks,
                        max_age=timedelta(seconds=30),
                        allow_anonymous=True,
                    ),
                    name="health_check-application",
                ),
            ]
        ),
    )
]

Point the uptime monitor at https://example.com/health/application/. The endpoint returns HTTP 200 when all checks pass. It returns HTTP 500 when a check fails. Uptime monitors expect these codes.

Anonymous requests can share a cached response for 30 seconds. Send Authorization: Bearer $HEALTH_CHECK_AUTH_TOKEN to bypass the cache. The view runs every check. The monitor always sees a fresh result. See Security to create the token.

Pipeline health checks

Pipeline checks add upstream provider status to your application checks. This status includes cloud platforms, PaaS providers, and third-party services. The development team watches these checks with RSS/Atom feeds in Slack or Matrix. Then upstream outages become known before they become support tickets.

Install the extras that read the feeds from cloud providers:

pip install "django-health-check[rss,atlassian]"

Add the pipeline endpoint to your URL configuration.

# urls.py
from datetime import timedelta

from django.urls import include, path
from health_check.views import HealthCheckView

application_checks = [
    # configured previously …
]

pipeline_checks = [
    # You may want to include application health alerts here too
    *application_checks,
    # Cloud provider status (pick the ones relevant to your stack)
    # GitHub status; to filter by a specific component, use a
    # tuple like ("health_check.contrib.atlassian.GitHub", {"component": "<exact name from githubstatus.com>"})
    "health_check.contrib.atlassian.GitHub",
    "health_check.contrib.atlassian.Cloudflare",
    (
        "health_check.contrib.rss.AWS",
        {"region": "eu-west-1", "service": "s3"},
    ),
]

urlpatterns = [
    # …
    path(
        "health/",
        include(
            [
                # other endpoints …
                path(
                    "pipeline/",
                    HealthCheckView.as_view(
                        checks=pipeline_checks,
                        max_age=timedelta(seconds=30),
                        allow_anonymous=True,
                    ),
                    name="health_check-pipeline",
                ),
            ]
        ),
    )
]

The pipeline endpoint is public because Slack and Matrix cannot send an Authorization header. Set allow_anonymous=True here. The short max_age applies to the cached feed while authenticated probes stay fresh.

Slack or Matrix

You can now tell engineers about upstream outages in Slack or Matrix.

Subscribe to the RSS feed in Slack:

  1. Install the Slack RSS App.
  2. In your #ops or #incidents channel, run:
    /feed subscribe https://www.example.com/health/pipeline/?format=rss
    

Or subscribe in Matrix:

# config.toml
[[bridge]]
    name = "Pipeline Health Monitor"
    feed_url = "https://example.com/health/pipeline/?format=rss"
    room_id = "!YourRoomId:matrix.org"

Complete example

# urls.py
from datetime import timedelta

from django.urls import include, path
from health_check.views import HealthCheckView
from redis.asyncio import Redis as RedisClient

node_checks = [
    "health_check.contrib.psutil.CPU",
    "health_check.contrib.psutil.Memory",
    "health_check.contrib.psutil.Disk",
]

application_checks = [
    "health_check.Cache",
    "health_check.Database",
    "health_check.Mail",
    "health_check.Storage",
    (
        "health_check.contrib.redis.Redis",
        {"client_factory": lambda: RedisClient.from_url("redis://localhost:6379")},
    ),
    (
        "health_check.contrib.rabbitmq.RabbitMQ",
        {"amqp_url": "amqp://guest:guest@localhost:5672//"},
    ),
    "health_check.contrib.celery.Ping",
    "health_check.contrib.crontask.Scheduler",
]

pipeline_checks = [
    *application_checks,
    (
        "health_check.contrib.atlassian.GitHub",
        {"component": "Actions"},
    ),
    "health_check.contrib.atlassian.Cloudflare",
    (
        "health_check.contrib.rss.AWS",
        {"region": "eu-west-1", "service": "s3"},
    ),
]

urlpatterns = [
    path(
        "health/",
        include(
            [
                # Tier 1 – node: liveness & readiness probes
                path(
                    "node/",
                    HealthCheckView.as_view(checks=node_checks),
                    name="health_check-node",
                ),
                # Tier 2 – application: uptime monitors & on-call alerts
                path(
                    "application/",
                    HealthCheckView.as_view(
                        checks=application_checks,
                        max_age=timedelta(seconds=30),
                        allow_anonymous=True,
                    ),
                    name="health_check-application",
                ),
                # Tier 3 – pipeline: developer RSS/Atom feeds (Slack, Matrix)
                path(
                    "pipeline/",
                    HealthCheckView.as_view(
                        checks=pipeline_checks,
                        max_age=timedelta(seconds=30),
                        allow_anonymous=True,
                    ),
                    name="health_check-pipeline",
                ),
            ]
        ),
    ),
]