Liveness vs Readiness vs Startup Probe: Which One Restarts the Pod
6 min readBytePatterns
Which Kubernetes probe restarts a container and which only removes it from traffic, the seconds until each one acts, and how liveness turns load into a cascade.
Liveness vs Readiness vs Startup Probe: Which One Restarts the Pod, answered in one line: only a failing liveness or startup probe gets the container restarted; a failing readiness probe only takes the Pod out of its Services' endpoints and leaves it running. Even the restart is narrower than the question suggests: the kubelet restarts the container in place, on the same node, and the Pod object stays. This article is about consequences and timing, and about how an eager probe turns a slow afternoon into an outage. Facts come from the Kubernetes documentation listed at the end, as of October 2026.
| Probe fails | Consequence | Seconds until it acts |
|---|---|---|
| Liveness | container killed and restarted | up to periodSeconds × failureThreshold |
| Readiness | removed from Service endpoints, keeps running | up to periodSeconds × failureThreshold |
| Startup | container killed; the other two never started | the boot budget, failureThreshold × periodSeconds |
The problem it solves
The general explanation of the three probes, their mechanisms and defaults, is in Kubernetes liveness, readiness and startup probes. Here the question is operational: when a Pod misbehaves, predict three things.
- What happens to it: restarted, taken out of traffic, or held back while it boots.
- When: after how many seconds of failed checks.
- What happens to everyone else: whether one Pod's restart pushes its traffic onto the others.
The intuition
Each probe has exactly one consequence.
- Liveness failing: the kubelet kills the container, and the Pod's restart policy brings it back. Process state and warm caches are lost. If it keeps failing, restarts back off exponentially, reported as
CrashLoopBackOff. - Readiness failing: the Pod's address leaves the EndpointSlices of every matching Service. Nothing is killed, and the probe keeps running for the container's whole life, so traffic returns when it passes again.
- Startup not yet passing: liveness and readiness do not run at all. If it fails
failureThresholdtimes, the container is killed and the restart policy applies, just as for liveness.
The timing is arithmetic. A probe acts after failureThreshold failures in a row, one check per periodSeconds. If every check fails from the moment the probe starts, it acts within initialDelaySeconds + periodSeconds × failureThreshold seconds. With the defaults, a period of 10 and a threshold of 3, that is 30 seconds. A container that breaks mid-life is caught within periodSeconds × failureThreshold.
The startup probe is a boot budget. The documentation's example, failureThreshold: 30 with periodSeconds: 10, gives an app a maximum of 5 minutes to start. Its rule of thumb: add a startup probe when the app usually needs longer than initialDelaySeconds + failureThreshold × periodSeconds of the liveness probe.
Restarts move load. A restarted container serves nothing until it boots again, and its share of the traffic lands on the rest. The documentation warns that misconfigured liveness probes can cause cascading failures for exactly this reason.
Watch it run
The lesson animation follows one container with its three probes as rows. While it boots, only the startup probe runs, failing harmlessly inside its 30 × 2 s budget. Once startup succeeds, readiness and liveness take turns. Readiness fails while caches load, so the Pod stays out of the endpoints, and a dependency blip later stops traffic again with nothing restarted. Then the process deadlocks: liveness fails twice, the kubelet restarts the container, and repeated crashes back off into CrashLoopBackOff. The closing frames show the risk this article is about: a hair-trigger liveness probe pushing load onto the remaining Pods.
Probes & Self-Healing
Step 1 of 12
The container is booting. Only the startup probe runs; liveness and readiness wait for it.
The same interactive animation as the lesson — step through it with the controls.
The code
The lesson's probes, with the failure thresholds the defaults supply written out:
# illustrative: one container, three probes
startupProbe:
httpGet: { path: /healthz, port: 8000 }
failureThreshold: 30 # 30 × 2 s: up to 60 s to boot
periodSeconds: 2
readinessProbe:
httpGet: { path: /ready, port: 8000 }
periodSeconds: 5
failureThreshold: 3 # the default: out of endpoints within 15 s
livenessProbe:
httpGet: { path: /healthz, port: 8000 }
periodSeconds: 10
failureThreshold: 3 # the default: restarted within 30 s
A toy model of the kubelet, not the real one: one tick per second, each probe checking at the end of its period, restarts without back-off. Three runs show the three consequences and when they land:
PROBES = { # the lesson's three probes; failureThreshold defaults to 3
"startup": {"period": 2, "failure_threshold": 30, "initial_delay": 0},
"readiness": {"period": 5, "failure_threshold": 3, "initial_delay": 0},
"liveness": {"period": 10, "failure_threshold": 3, "initial_delay": 0},
}
def seconds_until_action(p):
"""If every check fails: seconds from the probe starting to the kubelet acting."""
return p["initial_delay"] + p["period"] * p["failure_threshold"]
def kubelet(boot_s, ready_after_s, deadlock_at=None, horizon=200, probes=PROBES):
"""Toy model of the kubelet probing one container, one tick per second.
Each probe checks at the end of its period; restarts ignore back-off."""
events, t = [], 0
while t < horizon:
started, fails, in_endpoints = t, {k: 0 for k in probes}, False
active = {k: (started if k == "startup" or "startup" not in probes else None)
for k in probes}
restarted = False
while t < horizon and not restarted:
t += 1
alive = t - started >= boot_s and not (deadlock_at and t >= deadlock_at)
for name, p in probes.items():
since = None if active[name] is None else t - active[name] - p["initial_delay"]
if since is None or since <= 0 or since % p["period"]:
continue # not active yet, or not its turn
ok = alive and (name != "readiness" or t - started >= ready_after_s)
fails[name] = 0 if ok else fails[name] + 1
if name == "startup" and ok:
active.update(startup=None, readiness=t, liveness=t)
events.append((t, "startup passed, the other two begin"))
elif name == "readiness" and ok and not in_endpoints:
in_endpoints = True
events.append((t, "added to endpoints"))
elif name == "readiness" and fails[name] == p["failure_threshold"] and in_endpoints:
in_endpoints = False
events.append((t, "removed from endpoints, still running"))
elif name != "readiness" and fails[name] == p["failure_threshold"]:
events.append((t, f"{name} failed {p['failure_threshold']}x: restart"))
restarted = True
break
return events
print({k: seconds_until_action(p) for k, p in PROBES.items()})
runs = {
"boots in 24 s, deadlocks at 120 s": kubelet(24, 40, deadlock_at=120, horizon=160),
"boots in 70 s, budget is 60 s": kubelet(70, 70, horizon=200),
"boots in 45 s, no startup probe": kubelet(45, 45, horizon=100,
probes={k: p for k, p in PROBES.items() if k != "startup"}),
}
for label, events in runs.items():
print(label)
for t, e in events:
print(f" {t:3}s {e}")
# {'startup': 60, 'readiness': 15, 'liveness': 30}
# boots in 24 s, deadlocks at 120 s
# 24s startup passed, the other two begin
# 44s added to endpoints
# 134s removed from endpoints, still running
# 144s liveness failed 3x: restart
# boots in 70 s, budget is 60 s
# 60s startup failed 30x: restart
# 120s startup failed 30x: restart
# 180s startup failed 30x: restart
# boots in 45 s, no startup probe
# 30s liveness failed 3x: restart
# 60s liveness failed 3x: restart
# 90s liveness failed 3x: restart
In the first run the deadlock starts at 120 s. Readiness notices first and pulls the Pod out of traffic at 134 s; liveness restarts it at 144 s, 24 seconds in, inside its 30-second worst case. The other two runs are one mistake: a boot longer than the budget, killed before it can ever pass. Because this toy checks at the end of each period, it lands exactly on the formula; the real kubelet can act up to a period sooner, so treat the formula as the upper bound.
Next, a check that the formula holds for 300 random probe settings, and the cascade. Four Pods share 440 requests per second and can each serve 100 to 130. An aggressive liveness probe, every 2 seconds with a threshold of 1, fails whenever a Pod is overloaded, and a restarted Pod is gone for 20 seconds:
import random
random.seed(2)
ok = True
for _ in range(300):
p = {"period": random.randint(1, 10), "failure_threshold": random.randint(1, 30),
"initial_delay": random.randint(0, 20)}
first = kubelet(boot_s=10**9, ready_after_s=10**9, horizon=1000, probes={"startup": p})[0][0]
ok &= first == seconds_until_action(p)
print(ok)
def cascade(load, capacity, boot_s=20, liveness_fails_when_slow=True, horizon=60):
"""Toy model: total load spreads over ready Pods. A Pod pushed past its
capacity answers slowly; an aggressive liveness probe (period 2 s,
threshold 1, timeout 1 s) then restarts it, and it is gone for boot_s."""
down_until = [0] * len(capacity)
log = []
for t in range(0, horizon, 2): # one liveness check every 2 s
ready = [i for i in range(len(capacity)) if down_until[i] <= t]
per_pod = load / len(ready) if ready else float("inf")
restarted = [i for i in ready if per_pod > capacity[i]] if liveness_fails_when_slow else []
for i in restarted:
down_until[i] = t + boot_s
served = sum(min(per_pod, capacity[i]) for i in ready) if ready else 0
log.append((t, len(ready), round(served)))
if restarted:
print(f"t={t:2}s ready={len(ready)} load/pod={per_pod:.0f} restart {restarted}")
return log
capacity = [100, 110, 120, 130] # requests per second each Pod can serve
aggressive = cascade(load=440, capacity=capacity)
tolerant = cascade(load=440, capacity=capacity, liveness_fails_when_slow=False)
for name, log in [("aggressive", aggressive), ("tolerant", tolerant)]:
served = sum(s for *_, s in log) / (440 * len(log))
print(f"{name}: {served:.0%} of requests served, min ready = {min(r for _, r, _ in log)}")
# True
# t= 0s ready=4 load/pod=110 restart [0]
# t= 2s ready=3 load/pod=147 restart [1, 2, 3]
# t=20s ready=1 load/pod=440 restart [0]
# t=22s ready=3 load/pod=147 restart [1, 2, 3]
# t=40s ready=1 load/pod=440 restart [0]
# t=42s ready=3 load/pod=147 restart [1, 2, 3]
# aggressive: 13% of requests served, min ready = 0
# tolerant: 98% of requests served, min ready = 4
One Pod slightly over its capacity is restarted; its traffic pushes the other three over theirs, and they are restarted too. Every Pod that comes back meets the full load alone and fails again, so the fleet never recovers. With liveness checking only whether the process is alive, the same overload costs 2% of requests.
The complexity
The costs are detection time against false alarms:
- Faster detection means more misfires. Halving
periodSecondsorfailureThresholdhalves the time to act, and makes a brief stall look like a deadlock. - A restart is expensive. It discards the process's state and adds boot time, during which the Pod serves nothing. A removal from endpoints costs nothing but capacity.
- A boot budget is cheap. A generous startup probe does not slow deadlock detection later.
Where it goes wrong
- Liveness on the same endpoint as real traffic. Under load, the health check queues behind requests, times out after the default 1 second, and the kubelet restarts a healthy process.
- Expecting readiness to fix anything. It removes a stuck Pod from traffic forever, and nothing restarts it. Pair it with a liveness probe for true hangs.
- Stretching liveness to cover the boot. A threshold big enough for a slow start also delays deadlock detection for the container's whole life. Use a startup probe instead.
- A boot budget shorter than the slowest boot. Under a cold cache or a slow image pull, every start is killed at the same second, as in the second run above.
When it shows up in interviews
It comes as a scenario: "pods keep restarting under load; what do you check?" The expected answer names the liveness probe, its timeoutSeconds and threshold, and the cascade, then moves overload handling to readiness and to scaling. A timing question is common too: "how long until a stuck container restarts?"
How to say it in an interview
"Only liveness and startup failures restart anything, and they restart the container in place, not the Pod. Readiness only removes the Pod from the Service's endpoints. Each acts after failureThreshold checks, one per periodSeconds, so with defaults a stuck container is restarted within 30 seconds. I give slow apps a startup probe as a boot budget, keep liveness narrow and tolerant so overload never triggers it, and let readiness and autoscaling handle slowness, because restarting overloaded Pods moves their load onto the rest and cascades."
Practise the order of events in probes and self-healing. Restarts caused by memory rather than probes are in Requests vs limits: what OOMKilled means.
Frequently asked questions
Does a failed readiness probe restart the pod?
No. A failing readiness probe only removes the Pod's address from the endpoints of its Services, so it gets no traffic. The container keeps running, the probe keeps checking, and the Pod rejoins once it passes. Only liveness and startup probe failures get the container killed and restarted.
How long does it take a liveness probe to restart a container?
Up to periodSeconds × failureThreshold after the container breaks, plus initialDelaySeconds right after a start. With the defaults, a period of 10 seconds and a threshold of 3, that is about 30 seconds. Lower values react faster and also restart containers that are only briefly slow.
Which probe should I use for a slow-starting app?
A startup probe. Until it succeeds, liveness and readiness do not run, so the app gets failureThreshold × periodSeconds to boot without being restarted, such as 30 × 10 for five minutes. Stretching the liveness threshold instead would slow deadlock detection for the rest of the container's life.
Sources
- Liveness, Readiness, and Startup Probes — Kubernetes Documentation
- Configure Liveness, Readiness and Startup Probes — Kubernetes Documentation
- Pod Lifecycle — Kubernetes Documentation