CrashLoopBackOff means your container keeps dying on startup and Kubernetes keeps restarting it slower each time. Here's the diagnosis flow we actually use.
A pod goes CrashLoopBackOff and half the team starts guessing. It's almost never a mystery once you read the right output, and the order you read it in matters more than any single command.
Here's what the status actually means. Your container starts, runs for a bit (sometimes milliseconds), and exits. Kubernetes restarts it. It exits again. After a few rounds the kubelet stops restarting immediately and starts waiting: 10s, 20s, 40s, doubling up to a 5-minute cap. That growing delay is the "backoff." The "crashloop" is the part where your process refuses to stay up. So CrashLoopBackOff is not an error in itself, it's Kubernetes telling you it's tired of restarting something that won't stay alive.
The container is crashing. Your job is to find out why it exited, not why Kubernetes backed off.
Start here, every time. First look at the pod:
kubectl get pods
NAME READY STATUS RESTARTS AGE
api-7d9f8c6b4-2xqzk 0/1 CrashLoopBackOff 6 4m12s
Six restarts in four minutes. Now describe it:
kubectl describe pod api-7d9f8c6b4-2xqzk
Scroll to the container state and the events at the bottom. This is the single most useful block:
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error
Exit Code: 1
Started: Wed, 08 Jul 2026 10:22:14 +0000
Finished: Wed, 08 Jul 2026 10:22:14 +0000
Restart Count: 6
Started and Finished at the same second means the process died on boot. The exit code is your first real clue (more on codes below).
Now the opinionated part: read the previous container's logs before anything else.
kubectl logs api-7d9f8c6b4-2xqzk --previous
The --previous flag is the whole game. Without it, kubectl logs shows the current container, which may be freshly restarted and empty, or already dead again by the time you run it. --previous shows the instance that actually crashed. Nine times out of ten the stack trace or panic is sitting right there, and you can skip the guessing entirely.
If logs are empty, check events across the namespace:
kubectl get events --sort-by=.lastTimestamp
That surfaces the things logs won't: failed mounts, image pull problems, OOM kills, probe failures.
Application error or unhandled exception (exit 1). The most common case. Your app throws on startup: a nil pointer, an unhandled promise rejection, a bad migration. --previous logs show the trace. Fix the code or roll back the image. Exit code 1 is a generic application failure, so treat the logs as truth, not the code.
Bad or missing config / env var. The app starts, can't find DATABASE_URL or a required flag, and bails. Logs usually say something like panic: required env DATABASE_URL not set. Check what the pod actually received:
kubectl exec api-7d9f8c6b4-2xqzk -- env | grep DATABASE
If the pod won't stay up long enough to exec, inspect the ConfigMap and Secret refs in the deployment spec directly. A typo'd configMapKeyRef name silently gives you nothing.
Failed dependency (DB not reachable). The app boots, tries to connect to Postgres or Redis, times out, and exits instead of retrying. Logs show connection refused or a DNS failure. Two fixes: make the app retry with backoff instead of dying, and confirm the dependency is actually up and its Service resolves:
kubectl exec api-7d9f8c6b4-2xqzk -- nslookup postgres.default.svc.cluster.local
A hard crash on a transient dependency blip is a bug in your app, not in Kubernetes. Startup dependencies should be patient.
Failing liveness probe killing a healthy app. This one fools people. The app is fine but slow to warm up, the liveness probe hits /healthz before the app is ready, fails, and the kubelet kills the container. Restart, repeat, CrashLoopBackOff. You'll see it in describe:
Warning Unhealthy kubelet Liveness probe failed: HTTP probe failed with statuscode: 500
Normal Killing kubelet Container failed liveness probe, will be restarted
The fix is a startupProbe, or a longer initialDelaySeconds / failureThreshold on the liveness probe. Liveness should catch a wedged app, not punish a slow boot.
OOMKilled (exit 137). The container hit its memory limit and the kernel killed it. Describe shows Reason: OOMKilled and Exit Code: 137 (128 + signal 9). Either the limit is too low or the app is leaking. Raise resources.limits.memory if the workload legitimately needs it, or fix the leak. Don't just keep bumping the limit forever.
Missing file or permission denied. Often after switching to a non-root user or a read-only filesystem. Logs show permission denied or no such file or directory. Check your securityContext, volume mounts, and whether the image expects to write somewhere it no longer can.
Wrong command or entrypoint (exit 127 / 126). Exit 127 is "command not found," 126 is "found but not executable." Usually a bad command: / args: override in the manifest, or a typo in the binary path. The container never really runs your app at all.
Run these in order and you'll rarely get stuck:
kubectl get pods — confirm the status and restart count.kubectl describe pod <pod> — read Last State, Reason, and Exit Code.kubectl logs <pod> --previous — read the crash from the dead container first.kubectl get events --sort-by=.lastTimestamp — catch mounts, probes, OOM, image pulls.If you want a broader reference for the surrounding failure modes, our Kubernetes troubleshooting guide covers the pod lifecycle states around this one.
Read --previous logs first, always. Everything else is confirmation. The exit code narrows it (137 sends you to memory and probes, 127 to your entrypoint, 1 to the application), but the crashed container's own log line is what tells you the truth. Resist the urge to bump memory limits or loosen probes before you've actually read why the thing died, because the fastest fix is the one aimed at the real cause.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
Our best engineer quit citing on-call. We rebuilt the whole thing: saner rotations, runbooks that actually help at 3am, and escalation that doesn't punish asking for help.
We ripped every client secret out of our CI pipelines by pointing Azure federated credentials at GitHub's OIDC issuer. Here's the exact setup and the claims that trip people up.
Explore more articles in this category
Every CI/CD platform claims to be fast and easy. The real differences are in pricing, self-hosting, and where each one falls apart at scale. This is the map.
Woodpecker forked Drone when the license changed. Here's how the two compare for small teams and homelabs that just want simple container-native CI.
Buildkite runs the control plane and lets you own the compute; Actions keeps everything close to your repo. Here's how they actually differ once you scale.
Evergreen posts worth revisiting.