CrashLoopBackOff is the most commonly seen and most commonly misdiagnosed Kubernetes state in production, because it describes an identical symptom for three completely different root-cause families. Chasing the wrong family is the most frequent time-sink: the method below tells the three apart in a few commands, before any assumption gets made.
What the state describes, and what it doesn’t
The kubelet displays CrashLoopBackOff when a container restarts repeatedly, spacing each attempt with a growing delay (exponential backoff: 10 seconds, 20, 40, up to a 5-minute cap). This mechanism protects the node from a hammering of restarts; it says strictly nothing about why the container crashed. Three families hide behind the identical state, and the first step is identifying which one, not fixing directly.
# The systematic starting point, before any assumption:
# the pod's event history
kubectl describe pod my-app-7d9f8c-x2k4p
Family 1: the process actually crashes
The container starts, then stops on its own: invalid configuration, a missing dependency at startup, an unhandled exception. The exit code and the previous container’s logs (the one that just died, not the new one restarting) are the source of truth:
# --previous fetches logs from the dead container,
# not the new one currently restarting
kubectl logs my-app-7d9f8c-x2k4p --previous
# The exit code steers the diagnosis:
# 0 = voluntary stop, 1 = generic application error,
# 137 = SIGKILL (see family 2)
kubectl get pod my-app-7d9f8c-x2k4p -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
An exit code of 1 with an explicit stack trace in --previous logs confirms this family: the fix is application-level or configuration-level, not a Kubernetes problem.
Family 2: the kernel kills the container (OOMKilled)
A container exceeding its memory limit (resources.limits.memory) gets killed by the kernel, not by an application error. The distinctive signal: exit code 137 (128 + SIGKILL) and reason OOMKilled explicitly shown by describe pod, with no useful stack trace in the logs, since the process was killed abruptly, not terminated cleanly.
# The OOMKilled reason is directly visible in describe,
# no need to guess from the exit code alone
kubectl describe pod my-app-7d9f8c-x2k4p | grep -A3 "Last State"
The fix here is never purely in application code: either the memory limit is too low for real-world usage (see the article on resource requests and limits), or there’s a genuine memory leak to fix on the application side. Bumping the limit without checking which of the two applies sometimes just hides a real bug.
Family 3: a probe kills a healthy process
A poorly tuned liveness probe (timeout too short, an endpoint responding slowly under load) can kill a perfectly healthy process that would never have crashed on its own. The distinctive signal: pod events explicitly show Liveness probe failed, never an OOMKilled reason or an application stack trace.
# Probe failures show up in events,
# distinct from application crashes or OOM kills
kubectl describe pod my-app-7d9f8c-x2k4p | grep -i "probe failed"
This family is the sneakiest: the container was working fine, the probe wrongly declared it dead. The fix targets the probe’s parameters (timeoutSeconds, failureThreshold) or the slow dependency it’s checking, never the application code itself.
The cascading restart trap
A poorly tuned liveness probe checking a shared dependency can put every replica of a service into CrashLoopBackOff simultaneously: each one watches the same slow endpoint, each restarts at the same moment, and the entire service disappears while the underlying dependency was never actually down. Exponential backoff slows the restart frequency without ever fixing the cause, which makes identifying the right family all the more urgent before the incident gets worse.
Takeaway
CrashLoopBackOff describes an identical symptom for three radically different causes: a genuine application crash (exit code and --previous logs), a memory overrun (exit code 137, reason OOMKilled), or a poorly tuned liveness probe killing a healthy process (Liveness probe failed in events). Identifying the right family before acting, via describe pod and logs --previous, avoids fixing the wrong thing, a habit that saves disproportionate time during a production incident, one of the reflexes that gets built early in a Kubernetes migration.