Kubernetes CrashLoopBackOff: the 7 real causes and how to fix them
CrashLoopBackOff is not an error in itself. It is Kubernetes telling you that a container keeps exiting, and that the kubelet is now waiting longer and longer before restarting it: 10 seconds, then 20, 40, and so on, capped at 5 minutes. The backoff resets once the container has run cleanly for 10 minutes.
So the real question is always: why does the container exit? This guide gives you the seven usual causes, a five-minute diagnosis, and an alert that catches a crash loop before your users report it.
Start with the exit code
kubectl describe pod <pod> -n <namespace>
Look at Last State for the crashing container:
| Exit code | Meaning | Usual cause |
|---|---|---|
1 (or another small number) |
The application exited with an error | Bad config, missing env var or secret, failed startup check |
137 |
Killed with SIGKILL |
OOM kill (Reason: OOMKilled), or a failing liveness probe |
139 |
Segmentation fault | Native crash, incompatible library |
143 |
Terminated with SIGTERM |
Stopped from outside, often by a probe or a rollout |
Then read the logs of the previous run, not the current one, which has often just started:
kubectl logs <pod> -n <namespace> -c <container> --previous
The seven usual causes
1. Missing or wrong configuration. A required environment variable, ConfigMap key or Secret is absent or misspelled. The logs of the previous run usually say so in the first lines.
2. A dependency is not reachable at startup. The database, the message broker or an API is down, and the application exits instead of retrying.
3. A liveness probe that is too aggressive. The application needs 40 seconds to start, the probe gives it 10, and the kubelet kills it before it is ready. Look for Liveness probe failed in the events, and use a startupProbe for slow starters.
4. Out of memory on every run. The container is OOM killed (exit code 137, reason OOMKilled), restarts, and is killed again. See Kubernetes OOMKilled (exit code 137).
5. Wrong command or image. A typo in command or args, or an image built for another CPU architecture (exec format error in the logs).
6. Permissions on OpenShift. OpenShift runs containers with an arbitrary user ID by default. An image that expects to run as root, or to write to a directory owned by root, fails at startup with Permission denied. Make the writable paths group-writable for the root group (GID 0), or write to a mounted volume.
7. The process simply finishes. A container whose main process exits successfully (exit code 0) is still restarted under restartPolicy: Always. If it is a one-off task, it belongs in a Job.
Get alerted, with Prometheus
With kube-state-metrics, this rule fires when a container is waiting in CrashLoopBackOff:
- alert: ContainerCrashLooping
expr: |
max by (namespace, pod, container) (
kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff"}
) == 1
for: 5m
labels:
severity: warning
And this one catches repeated restarts even between two backoff states:
- alert: ContainerRestartingOften
expr: |
max by (namespace, pod, container) (
increase(kube_pod_container_status_restarts_total[30m])
) >= 3
labels:
severity: warning
What these rules do not tell you
Both rules fire after the loop has started, and they do not say which of the seven causes you are facing. They also miss a quieter pattern: a workload that fails on a regular rhythm, every few hours, never often enough to trip a "3 restarts in 30 minutes" rule.
Ruptura is an open-source operator for Kubernetes and OpenShift that reports a crash loop as a named failure with its evidence and a next step, and that predicts the next failure of a workload that fails at regular intervals. Results go straight to Alertmanager, including the OpenShift cluster Alertmanager. It is early and feedback is welcome on GitHub.