Kubernetes has three probe types (readiness, liveness and startup), and they answer three different questions. Most probe-related outages come from treating them as one mechanism: the same /healthz endpoint pasted into both readinessProbe and livenessProbe, no startupProbe on an application that takes a minute to boot, and default timeouts nobody read. This is a reference for what each probe actually gates, the failure modes that follow from mixing them up, and how to choose the parameters.

Three probes, three different questions

All three probes share the same YAML shape and the same handlers. What differs is what the kubelet does with the result.

readinessProbe: can this pod serve traffic right now?

Readiness gates load balancing. While the probe passes, the pod’s IP is listed in the EndpointSlices of every Service that selects it. When it fails (failureThreshold consecutive times), the pod is marked not-ready and removed from load balancing. The container is not touched: it keeps running, and the moment the probe passes again the pod goes back into rotation.

Readiness is the only probe designed to fail during normal operation. A pod that is momentarily overloaded, waiting on a cache warm-up, or draining before shutdown is exactly what “not ready” means. It runs for the whole life of the container, not just at startup.

Readiness failures also gate rollouts: a rolling update only proceeds past a new pod once that pod reports Ready. A wrong readiness check therefore has a second blast radius: it can let a rolling update replace working pods with broken ones that claim to be fine.

livenessProbe: is this process wedged beyond recovery?

Liveness gates container restarts. When it fails failureThreshold consecutive times, the kubelet kills the container and restarts it according to the pod’s restartPolicy, with exponential backoff (CrashLoopBackOff when it keeps happening).

That is a brutal remedy, which is why the question liveness answers must be narrow: is the process deadlocked, wedged, or otherwise unable to make progress in a way that only a restart fixes? Anything softer than that belongs in readiness. A liveness probe should almost never fail in a healthy fleet. If yours fires regularly, either the app has a real defect or the probe is checking the wrong thing.

startupProbe: has this container finished booting?

Startup gates the other two probes. While a startupProbe is defined and has not yet succeeded, the kubelet does not run liveness or readiness at all. Once it succeeds a single time, it never runs again and the other probes take over.

Its purpose is slow-starting applications. The maximum boot time you grant is failureThreshold × periodSeconds (plus initialDelaySeconds if set); if the probe still has not succeeded by then, the container is killed and restarted. That budget lives in one place, on one probe, instead of being smeared across initialDelaySeconds on the other two.

Probe mechanics and parameters

Each probe uses one of four handlers:

  • httpGet: success is an HTTP status ≥ 200 and < 400. The most common choice for web workloads.
  • tcpSocket: success is completing a TCP connection. Proves a port is open, nothing about the application behind it.
  • exec: runs a command in the container; exit code 0 is success. Flexible, and the most expensive, since each execution forks inside the container.
  • grpc: uses the standard gRPC Health Checking Protocol (GA since Kubernetes 1.27). The right answer for gRPC servers, replacing the old grpc_health_probe exec workaround.

The timing parameters, with their defaults:

ParameterDefaultMeaning
initialDelaySeconds0Wait before the first check
periodSeconds10Interval between checks
timeoutSeconds1Time allowed per check
failureThreshold3Consecutive failures before acting
successThreshold1Consecutive successes to recover (must be 1 for liveness and startup)

Two defaults deserve attention. timeoutSeconds: 1 is aggressive: an exec probe that forks a shell, or an HTTP handler that touches anything slower than memory, can blow a one-second budget under load, and a probe timeout counts as a failure. periodSeconds: 10 with failureThreshold: 3 means detection takes up to 30 seconds, which is often slower than people assume their “health checks” react.

The failure modes of copy-pasted probes

These are the patterns that turn probes from a safety mechanism into an outage amplifier. All of them come from copying a working-looking block without asking which question it answers.

Liveness and readiness pointing at the same endpoint

The most common one. If /healthz fails because a downstream dependency is slow, readiness correctly pulls the pod out of rotation. Liveness, watching the same endpoint, then restarts a process that was never broken. Multiply by every replica watching the same slow dependency and you get a coordinated restart storm: the dependency recovers, but your application tier is now in CrashLoopBackOff.

The two probes need different endpoints because they answer different questions. /ready may consult whatever the app needs to serve a request; /live should verify only that the process itself is responsive.

Dependency checks inside the liveness probe

The generalized version of the previous mistake. A liveness handler that pings the database, checks a message broker, or calls another service converts every dependency blip into a mass restart of the dependents. Restarting your API pods does not fix your database; it adds connection churn and cold caches on top of the original problem, and it does so on every replica at roughly the same time.

Dependency awareness belongs in readiness, and even there with care: if every replica’s readiness fails simultaneously because a shared dependency is down, the Service ends up with zero endpoints and clients get connection refused instead of a degraded response. For a dependency the app could partially work without, returning 200 with degraded behavior is often better than failing the probe.

No startupProbe on a slow-starting application

Without a startup probe, liveness starts checking after initialDelaySeconds. For an application with variable boot time (JVMs warming up, migrations running, large caches loading), that forces a bad choice: either set initialDelaySeconds high enough for the worst case, and eat that delay on every restart even the fast ones, or set it optimistically and watch the kubelet kill containers that were 5 seconds from finishing boot. The kill leads to a restart, which boots slowly again, which gets killed again: a startup crash loop caused entirely by probe configuration.

A startupProbe with a generous failureThreshold solves this cleanly:

startupProbe:
  httpGet:
    path: /live
    port: 8080
  periodSeconds: 5
  failureThreshold: 24      # up to 120 s to boot

The app gets up to two minutes to come up, checked every five seconds, so a fast boot is detected fast. Once it is up, liveness runs with tight, steady-state settings instead of settings inflated to cover booting.

No readinessProbe at all

Without a readiness probe, a pod counts as Ready as soon as its containers are running. Traffic arrives before the application listens, and rolling updates make progress based on that lie: old pods get killed because new ones claim to be “Ready”. The result is a burst of errors on every deploy that no amount of liveness tuning can fix.

tcpSocket where httpGet was needed

A tcpSocket readiness probe on a web application proves the socket is open. An application can accept TCP connections long before its routes are wired, and long after its event loop has wedged. If the app speaks HTTP, probe it over HTTP.

A complete example

The three probes together, on a hypothetical app with an occasionally slow boot:

containers:
  - name: app
    ports:
      - containerPort: 8080
    startupProbe:
      httpGet:
        path: /live
        port: 8080
      periodSeconds: 5
      failureThreshold: 24        # boot budget: 120 s
    readinessProbe:
      httpGet:
        path: /ready              # may check what serving requires
        port: 8080
      periodSeconds: 5
      timeoutSeconds: 2
      failureThreshold: 2         # out of rotation within ~10 s
    livenessProbe:
      httpGet:
        path: /live               # process responsive, nothing else
        port: 8080
      periodSeconds: 10
      timeoutSeconds: 2
      failureThreshold: 3         # restart only after ~30 s of silence

Note the asymmetry: readiness reacts faster than liveness (failureThreshold: 2 vs 3, shorter period). Being pulled from load balancing is cheap and reversible; a restart is neither. You want the cheap remedy to trigger first.

Decision table

QuestionProbeOn failureEndpoint should check
Can this pod take traffic now?readinessProbeRemoved from Service endpointsApp fully initialized; critical dependencies, with care
Is the process wedged beyond recovery?livenessProbeContainer killed and restartedProcess/event loop responsiveness only, never dependencies
Has the container finished booting?startupProbeContainer killed and restartedSame handler as liveness, generous failureThreshold

And for the parameters:

SituationSetting
Boot time is variable or > ~10 sAdd a startupProbe; keep initialDelaySeconds at 0 elsewhere
Handler does real work (exec, disk, downstream call)Raise timeoutSeconds above the default 1 s
Traffic must leave a bad pod quicklyLower readiness periodSeconds × failureThreshold product
Restarts are expensive (caches, connections)Raise liveness failureThreshold; make the handler trivial
gRPC serverUse the grpc handler, not an exec workaround

What probes do not solve

Two boundaries worth knowing. First, a correct readinessProbe does not give you zero-downtime deploys: pod termination during a rollout is a propagation race that readiness cannot influence, and fixing it involves preStop hooks and SIGTERM handling — the moving parts are summarized in the glossary entry on rolling deployments (in French). Second, probes keep working under a default-deny NetworkPolicy: the kubelet probes pods from the node itself and CNIs exempt that host-local traffic, a detail that matters when rolling out a default-deny NetworkPolicy.

The mental model that survives contact with production is small: readiness is a routing decision, liveness is a restart decision, startup is a boot-time budget. Configure each one to answer only its own question, give the cheap remedy a faster trigger than the brutal one, and read the defaults before trusting them. Getting probes to answer honestly is one small piece of a larger discipline, making a platform surface its own failures before users do, which is what my reliability and observability work (in French) is about. More Kubernetes material in the kubernetes category.