Kubernetes Probes: Liveness vs Readiness, Explained

Abstract technical illustration for the article: Kubernetes Probes: Liveness vs Readiness, Explained

Kubernetes keeps containers alive with two simple reactions: restart what's broken, and stop sending traffic to what isn't ready. The mechanism behind both is the probe — a diagnostic the kubelet runs periodically against a container, either by executing code inside it or by making a network request to it. There are three kinds — startup, liveness, and readiness — and the confusion people run into is rarely about syntax. It's about what each probe's failure actually triggers. Confusing them is one of the most common sources of avoidable production incidents in Kubernetes.

Three probes, three different questions

Every probe runs the same kind of diagnostic on a schedule. What differs is the question it answers and what Kubernetes does with the answer.

Liveness: “Is this container stuck?” It decides whether to restart a container, and exists for one failure mode: a process that has not crashed but is deadlocked, hung, or unable to make progress — a state it would never leave on its own. Past the configured failure tolerance, the kubelet kills the container and restarts it per the pod's restartPolicy. The official docs carry an explicit caution: liveness probes must indicate truly unrecoverable failure, because an aggressive one restarts containers under high load, sheds load onto the remaining pods, and can cascade.

Readiness: “Can this pod serve traffic right now?” It decides whether a pod receives traffic from Services. On failure, the EndpointSlice controller removes the pod's IP from the EndpointSlices of all matching Services. The container keeps running — nothing is restarted. Readiness probes run for the container's whole lifecycle, so they cover not just startup (connections, cache warm-up, migrations) but also later moments when a container should sit out temporarily during overloads or maintenance.

Startup: “Has this container finished booting?” While a startup probe is defined and has not yet succeeded, liveness and readiness do not run at all. It exists for containers with long or unpredictable initialization. Once it succeeds, it steps aside permanently for that container's lifetime. If it fails, the kubelet kills the container, exactly like a liveness failure.

The three exist because “restart this” and “stop routing to this” are different decisions, and “still starting up” is a third state that should not be judged by either rule.

The four check mechanisms

Each probe must define exactly one of four mechanisms:

  • exec — runs a command inside the container; success means exit code 0, e.g. cat /tmp/healthy. Each run forks processes, so frequent exec probes add measurable CPU overhead on high-density nodes — prefer another mechanism when you can.
  • httpGet — HTTP GET against the pod's IP on a port and path; success is any status code from 200 up to 400. Supports scheme: HTTPS and custom httpHeaders. The kubelet ignores the body and cuts connections after 10 KiB, which can surface as broken-pipe errors in app logs behind verbose health endpoints.
  • tcpSocket — opens a TCP connection to a port; success means the port accepted it. It says nothing about the application behind the port — a process listening while wedged passes a TCP probe.
  • grpc — gRPC health check against the standard gRPC health-checking protocol; success means status SERVING. Stable since Kubernetes v1.27, no feature gate needed.

Each execution scores Success, Failure, or Unknown. On Unknown — the diagnostic could not be evaluated — the kubelet takes no action and checks again. A container with no probe defined scores Success always, except readiness, which counts as Failure until the initial delay passes.

The timing and threshold fields

Five fields control probe behavior, shared across all three types with two exceptions:

  • initialDelaySeconds — wait after container start before the first probe. Defaults to 0. With a startup probe defined, liveness and readiness delays do not begin until it succeeds.
  • periodSeconds — how often the probe runs. Defaults to 10, minimum 1.
  • timeoutSeconds — seconds after which the probe times out as a failure. Defaults to 1, minimum 1 — and this default is the single most common source of flapping probes.
  • successThreshold — consecutive successes needed to pass again after a failure. Defaults to 1, and must stay 1 for liveness and startup (the API server enforces it). Only readiness can raise it, which stops a marginal pod flapping in and out of load balancing.
  • failureThreshold — consecutive failures before Kubernetes acts. Defaults to 3, minimum 1.

Also worth knowing: a probe-level terminationGracePeriodSeconds (stable since v1.28) controls how long the kubelet waits between triggering shutdown of a failed container and force-stopping it — but it cannot be set on readiness probes; the API server rejects it.

What the kubelet does on each failure

  • Liveness failure (consecutive failures reaching failureThreshold): the kubelet kills the container and restarts it per restartPolicy. Events show Warning Unhealthy ... Liveness probe failed then Normal Killing ... failed liveness probe, will be restarted. The docs' common pattern: reuse the same low-cost HTTP endpoint as readiness, but with a higher failureThreshold, so the pod sits not-ready for a while before anything gets hard-killed.
  • Readiness failure: the kubelet marks the container not ready; the pod stops receiving traffic. The container keeps running, probing continues, and traffic resumes the moment the probe passes. Nothing restarts — the only non-destructive failure.
  • Startup failure: the kubelet kills the container and restarts it, exactly like a liveness failure — except liveness and readiness never ran, so they cannot be the cause.

When debugging, use kubectl describe pod (events at the bottom) and kubectl logs --previous. A readiness-only failure produces Unhealthy warnings with no Killing event — because nothing was killed.

A complete example: all three probes on one container

A realistic manifest for a service with a slow, variable startup — a container that loads a large dataset into memory before it can serve:

apiVersion: v1kind: Podmetadata:  name: catalog-apispec:  containers:  - name: api    image: registry.example.com/catalog-api:2.4.1    ports:    - containerPort: 8080    startupProbe:      httpGet:        path: /healthz        port: 8080      periodSeconds: 10      failureThreshold: 30    # up to 300s to finish booting    livenessProbe:      httpGet:        path: /healthz        port: 8080      periodSeconds: 10      timeoutSeconds: 3      failureThreshold: 3     # ~30s of sustained failure before restart    readinessProbe:      httpGet:        path: /ready        port: 8080      periodSeconds: 5      timeoutSeconds: 2      failureThreshold: 3     # out of rotation ~15s after trouble starts

The startup probe grants a 300-second boot budget (failureThreshold × periodSeconds) during which liveness cannot kill the container. Once it succeeds, liveness takes over on the same cheap /healthz endpoint, requiring 3 consecutive failures before restarting. Readiness watches a separate /ready endpoint on a tighter 5-second cycle, because traffic eligibility changes faster than basic health. The official rule of thumb: if a container usually takes longer than initialDelaySeconds + failureThreshold × periodSeconds to start, add a startup probe on the same endpoint as liveness, and leave liveness timings alone.

Common misconfigurations

  1. Liveness probing a dependency. A liveness endpoint that checks database connectivity restarts every pod talking to that database — all at once — on a database blip, turning a brief outage into a much larger one. Liveness should check whether the process itself is responsive; dependency checks belong in readiness, where failure merely pauses traffic.
  2. No startup probe on a slow starter. The classic CrashLoopBackOff with no crash: a four-minute initialization is killed by default liveness settings at ~30 seconds, restarts, and is killed again forever. Fix it with a startup probe whose failureThreshold covers real startup time — not a longer initialDelaySeconds on liveness, which wastes that delay on every restart.
  3. timeoutSeconds at the 1-second default. A health endpoint answering in 50 ms can occasionally take longer under CPU pressure or GC pauses; a 1-second timeout reads that as failure and pods flap in and out of readiness — or restart. Set the timeout from measured endpoint latency under load; a few seconds is a common starting point.
  4. failureThreshold: 1 on liveness. One transient miss — a slow scrape, a GC pause — triggers a restart. React to sustained failure, not one slow check.
  5. Wrong port or path. A probe on a port the app never opens, or a path returning 404, reads as permanent failure and restarts the container in a loop. With clean logs and no crash, verify the target: kubectl exec <pod> -- curl -v localhost:8080/healthz answers in seconds.
  6. A health endpoint that always returns 200. A probe is only as good as what it probes. An endpoint that exercises nothing beyond “the HTTP server accepted the connection” reports a wedged application as healthy. The liveness endpoint must exercise the failure mode you guard against — a deadlock, a stuck event loop.
  7. No probes at all. Without any probe, Kubernetes marks the pod ready the moment the container starts, and alive forever unless the process crashes. Nothing ever takes a hung container out of rotation.

Rules that hold up

  • Keep liveness minimal. One question: is the process irrecoverably stuck such that restart is the best remediation? If the process reliably crashes on its own when unhealthy, skip liveness — the restart policy already handles it.
  • Keep readiness honest about traffic. Can this replica safely accept new work right now? It may include critical local state and indispensable dependencies — but probing every dependency indiscriminately creates cascades, where one database slowdown marks every replica unready at once.
  • Use startup probes for slow or variable boot. Migrations, model loads, cache warm-up — anything past ~30 seconds of startup deserves a startup probe instead of a guessed initialDelaySeconds.
  • Separate the concerns. Readiness should react quickly to short-lived trouble; liveness should be forgiving. Reusing the exact same check with identical thresholds for both is usually a sign the distinction was not thought through.

Further reading

Similar Posts

Leave a Reply