CrashLoopBackOff
Fix CrashLoopBackOff: run kubectl logs --previous and kubectl describe pod, then match the exit code to the cause. Covers OOMKilled, bad config and probes.
NAME READY STATUS RESTARTS AGE
Incident Overview
KubernetesKubernetes
Fix CrashLoopBackOff: run kubectl logs --previous and kubectl describe pod, then match the exit code to the cause. Covers OOMKilled, bad config and probes.
medium fix·7 min read·6 causes
By Vishvesh Patel·DevOps Engineer·Updated 16 September 2026
NAME READY STATUS RESTARTS AGE payments-api-7d4f-x8k2 0/1 CrashLoopBackOff 6 8m
Do this first3 steps
Run these in order. Each one tells you what its output means before you change anything.
• Read the logs of the run that crashed
--previous is the whole trick: plain kubectl logs shows the container starting now, which during a backoff has produced nothing. The application's own error is usually right here.
• Get the termination reason and exit code
OOMKilled is cause 1. Exit 1 with logs is cause 2 or 3. Exit 0 means the process finished normally (cause 5). Exit 127 or 128 means the entrypoint does not exist (cause 6).
• If the logs are empty, read the events
Check Events and Last State. A restart attributed to a liveness probe is cause 4; 'no such file or directory' or 'exec format error' is cause 6.
kubectl logs payments-api-7d4f-x8k2 --previous
Then: the 6 causes in full
If the steps above did not settle it, these are the causes in the order they are usually responsible. Each section explains why it happens and how to confirm it.
CrashLoopBackOff is not a cause. It is Kubernetes telling you that a container started, exited, and has been restarted several times with an increasing delay between attempts: 10s, 20s, 40s, doubling up to a five-minute cap.
The actual cause is somewhere else, and there are six realistic candidates.
Get the real error first
Do these two things before anything else. Between them they identify the cause in the large majority of cases.
--previous is the whole trick. Plain kubectl logs shows the container that has just started, which during a backoff has produced nothing yet. That is why people report "CrashLoopBackOff with empty logs". They are reading the wrong instance.
The bottom of that output is what matters:
And the single most useful one-liner:
# 1. The logs of the run that CRASHED, not the one starting now kubectl logs payments-api-7d4f-x8k2 --previous
Match the exit code to the cause
Cause 1: OOMKilled
The most common by a distance. The container exceeded its memory limit and the kernel killed it instantly, so the application never got to log anything.
Check what it actually uses, then raise the limit, or find the leak:
A JVM, Node or .NET application is a special case. These runtimes size their heap against what they believe is available memory, and without being told about the container limit they will happily size against the node's total. A container limited to 512Mi with a JVM sizing a 16GB heap is OOMKilled reliably.
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
# OOMKilledCause 2: Missing configuration
The application starts, cannot find a required environment variable or config file, and exits. The logs usually say so plainly, which is why --previous matters.
Check what the container actually received:
A related failure looks different: if a referenced ConfigMap or Secret does not exist, the Pod never starts at all and shows CreateContainerConfigError rather than CrashLoopBackOff.
kubectl logs <pod> --previous # Error: DATABASE_URL is not defined
Cause 3: A dependency is unreachable at startup
The application tries to connect to a database, cache or API during boot, fails, and exits rather than retrying.
Test reachability from inside the cluster:
The usual causes are a wrong Service name, a missing namespace segment in the DNS name, or a NetworkPolicy blocking the traffic.
The durable fix is for the application to retry with backoff rather than exit. A dependency being briefly unavailable is normal; crashing because of it converts a transient blip into a crash loop.
kubectl logs <pod> --previous # Error: connect ECONNREFUSED 10.96.41.203:5432
Cause 4: A liveness probe killing a slow starter
The application takes 60 seconds to boot, the liveness probe starts checking after 10, fails three times, and the kubelet kills it. It restarts and the cycle repeats. A crash loop caused entirely by the health check.
The giveaway is Killing in the events with clean application logs. The app was fine, it was just not ready yet.
The fix is a startup probe, not a longer initialDelaySeconds:
A startup probe suspends liveness until it passes, so you get a generous boot window and fast detection afterwards. Inflating initialDelaySeconds gives you the first at the cost of the second.
Events:
Warning Unhealthy Liveness probe failed: Get "http://10.244.1.17:8080/healthz":
dial tcp: connect: connection refused
Normal Killing Container api failed liveness probe, will be restartedCause 5: The process completes and exits
Exit code 0 with Reason: Completed. The container did its job and stopped, but restartPolicy: Always, which Deployments use, restarts a container whatever its exit code.
This means you have deployed batch work as a long-running service. Use a Job or CronJob instead, which set restartPolicy: OnFailure or Never and understand completion.
It also happens when a server daemonises itself into the background, making PID 1 exit. Keep the process in the foreground.
Cause 6: Wrong command or entrypoint
Exit code 127 means command not found; 126 means found but not executable.
Usually a typo in command, a script missing the executable bit, or a shebang mismatch. A frequent variant: a script built on Windows with CRLF line endings fails with no such file or directory pointing at the interpreter, which is baffling until you know.
Check what the image actually defines:
kubectl logs <pod> --previous # exec /app/start.sh: no such file or directory
Debugging when the container dies too fast
Sometimes it exits before you can inspect anything. Two approaches.
Override the command so it stays up, then poke around inside:
Attach an ephemeral container to the failing Pod, sharing its namespaces:
And to stop the backoff delay slowing you down while iterating, delete the Pod so a fresh one starts immediately rather than waiting out the timer.
kubectl run debug --image=<your-image> --restart=Never -it --command -- sh
A checklist
Frequently Asked Questions
What does CrashLoopBackOff actually mean?
It means a container has started, exited, and been restarted repeatedly, and the kubelet is now waiting before trying again: 10 seconds, then 20, then 40, doubling up to a five-minute cap. The "BackOff" part is the delay, not the failure. It is a symptom rather than a diagnosis: the real cause is whatever made the container exit, which you find in the logs of the previous run and the Pod's events.
Why does kubectl logs show nothing for a CrashLoopBackOff pod?
Because it reads the container that is running now, and during a backoff that instance has either not started or has only just started and produced no output. The output you need belongs to the terminated instance, which kubectl logs <pod> --previous retrieves. This is the single most common stumbling block with crash loops, and it accounts for most "empty logs" reports.
How do I know if CrashLoopBackOff is caused by out of memory?
Check the termination reason: kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'. If it returns OOMKilled, the container exceeded its memory limit and the kernel killed it, which is why the application logged nothing before dying. Compare actual usage with kubectl top pod before simply raising the limit, since unbounded growth is a leak that a bigger limit only postpones.
Why does my container crash loop even though the application is fine?
Most often a liveness probe killing it during startup. If the application needs 60 seconds to boot and the probe begins checking at 10, it fails, the kubelet kills the container, and the cycle repeats. The signature is Killing in the Pod events with clean application logs. Add a startup probe with a generous failureThreshold, which suspends the liveness probe until the application is up, rather than increasing initialDelaySeconds and losing fast failure detection later.
What does exit code 137 mean in Kubernetes?
It is 128 plus 9, meaning the process received SIGKILL. The two realistic sources are the kernel's OOM killer when a memory limit is exceeded, and the kubelet terminating a container that failed its liveness probe. lastState.terminated.reason distinguishes them: OOMKilled for the first, Error with a Killing event for the second. Exit 143 is 128 plus 15, SIGTERM, which usually indicates an ordinary shutdown rather than a fault.
How do I stop a pod crash looping while I debug it?
Scale the Deployment to zero with kubectl scale deployment/<name> --replicas=0 to stop the churn, then investigate using a separate debug Pod: kubectl run debug --image=<your-image> --restart=Never -it --command -- sh starts the same image with a shell instead of the failing command, so you can inspect the filesystem, environment and configuration directly. For a Pod that is still cycling, kubectl debug attaches an ephemeral container without disturbing it.
Reference and practice
Learn the underlying concept
Other Kubernetes errors
Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.
Get notified about new tutorials and features.
• Roadmaps
• Interview Prep
• Certifications
• Company
© 2026 DevOpsLesson. All rights reserved.
Need to practice this scenario?
Simulate this error condition and practice recovery commands safely in our in-browser terminal.