Skip to content

Cookbook

This cookbook walks through setting up multiple health check endpoints, each aimed at a different audience and use case.

A solid setup exposes 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 confirm the server has enough resources to run the app. Use them for liveness and readiness probes in container orchestrators such as Kubernetes, Docker, and Podman, or for reverse-proxy checks in HAProxy, nginx, Caddy, and Traefik.

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

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

Add the node endpoint to your URL configuration.

# urls.py
import os

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(
        f"health/{os.getenv('HEALTH_CHECK_SECRET', 'dev')}/",
        include(
            [
                path(
                    "node/",
                    HealthCheckView.as_view(checks=node_checks),
                    name="health_check-node",
                ),
            ]
        ),
    )
]

Tip

Protect this endpoint with a secret token. Do not make it publicly accessible. See the Security section of the installation guide.

Kubernetes probes

Kubernetes uses liveness and readiness probes to decide when to restart a pod. We hit the endpoint over HTTP, which also proves the whole HTTP stack works.

Note

Bind your WSGI/ASGI server to 0.0.0.0, not just 127.0.0.1, so the kubelet can reach the httpGet probes.

For our health check endpoint, the configuration looks like this:

# deployment.yaml
livenessProbe:
  httpGet:
    path: /health/${HEALTH_CHECK_SECRET:-dev}/node/
    port: 8000
  initialDelaySeconds: 10
  periodSeconds: 30
readinessProbe:
  httpGet:
    path: /health/${HEALTH_CHECK_SECRET:-dev}/node/
    port: 8000
  initialDelaySeconds: 5
  periodSeconds: 10

See the Kubernetes documentation for more details.

Docker / Podman

Compose has no native HTTP probes, so we use the health_check command instead. It doesn't need CURL in the image, and 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. Once configured, they only route traffic to healthy instances.

In Caddy, the configuration looks like this:

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

… in Traefik, the configuration looks like this:

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

… in Nginx, the configuration looks like this:

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

… and in HAProxy, the configuration looks like this:

# 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/${HEALTH_CHECK_SECRET:-dev}/node/

Application health checks

Application checks make sure every production service your app depends on is reachable and operational. Uptime monitors such as Pingdom, Better Uptime, or StatusCake watch these endpoints and page on-call engineers when something goes down.

To cover the whole stack — databases, caches, message brokers, email, storage — you'll need some extra dependencies:

pip install "django-health-check[redis,rabbitmq,celery]"
# urls.py
import os

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",
]

urlpatterns = [
    # …
    path(
        f"health/{os.getenv('HEALTH_CHECK_SECRET', 'dev')}/",
        include(
            [
                # other endpoints …
                path(
                    "application/",
                    HealthCheckView.as_view(checks=application_checks),
                    name="health_check-application",
                ),
            ]
        ),
    )
]

Point the uptime monitor at https://example.com/health/<HEALTH_CHECK_SECRET>/application/. It returns HTTP 200 when all checks pass and HTTP 500 when any fail — exactly the codes monitors watch for.

Pipeline health checks

Pipeline checks add upstream provider status on top of your application checks — cloud platforms, PaaS providers, third-party services. The dev team watches these via RSS/Atom feeds in Slack or Matrix, so upstream outages surface 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
import os

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(
        f"health/{os.getenv('HEALTH_CHECK_SECRET', 'dev')}/",
        include(
            [
                # other endpoints …
                path(
                    "pipeline/",
                    HealthCheckView.as_view(checks=pipeline_checks),
                    name="health_check-pipeline",
                ),
            ]
        ),
    )
]

Slack or Matrix

You can now alert 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/<HEALTH_CHECK_SECRET>/pipeline/?format=rss
    

... or subscribe in Matrix:

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

Complete example

# urls.py
import os

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",
]

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/{os.getenv('HEALTH_CHECK_SECRET', 'dev')}/",
        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),
                    name="health_check-application",
                ),
                # Tier 3 – pipeline: developer RSS/Atom feeds (Slack, Matrix)
                path(
                    "pipeline/",
                    HealthCheckView.as_view(checks=pipeline_checks),
                    name="health_check-pipeline",
                ),
            ]
        ),
    ),
]