← Documentation home
Kubernetes troubleshooting

CrashLoopBackOff is a symptom. The exit code tells you the cause.

A pod in CrashLoopBackOff is not reporting a problem — it is reporting that the kubelet has given up restarting for the moment. The useful information is in what the container did before it exited. Read that first and the fix is usually obvious.

What CrashLoopBackOff actually means

The kubelet started your container, the container exited, and the kubelet restarted it. That happened repeatedly, so the kubelet is now waiting before trying again. CrashLoopBackOff is the name of that waiting period.

This matters because the state itself carries no diagnostic content. A missing ConfigMap, a bad entrypoint, an unreachable database and a memory limit breach all produce exactly the same status. Every minute spent looking at the phrase itself is wasted; the evidence is one level down.

The first two commands

Everything you need is in the previous container instance, not the current one.

# the logs of the instance that crashed - note --previous
kubectl logs <pod> -n <namespace> --previous

# exit code, reason, restart count, events
kubectl describe pod <pod> -n <namespace>

The --previous flag is the one most people miss. The current container may be sitting in backoff and have produced no output at all, so a plain kubectl logs returns nothing and looks like the application is silent. The crashed instance is where the stack trace lives.

For a pod with several containers, name the one you want:

kubectl logs <pod> -n <namespace> -c <container> --previous

Read the exit code before anything else

The exit code narrows six possible causes down to one or two in a few seconds.

kubectl get pod <pod> -n <namespace> \
  -o jsonpath="{range .status.containerStatuses[*]}{.name}{': '}{.lastState.terminated.exitCode}{' '}{.lastState.terminated.reason}{'\n'}{end}"
0Exited cleanlyThe process finished and returned success. Usually a one-off task deployed as a Deployment. It belongs in a Job, or needs a process that stays in the foreground.
1Application errorAn unhandled exception or a failed startup check. The previous logs contain the reason. This is the most common code.
127Command not foundThe entrypoint or command does not exist in the image. A typo, a missing binary, or a path that only exists on your laptop.
137SIGKILL, almost always OOMThe container exceeded its memory limit and the kernel killed it. Confirm with reason OOMKilled.
139Segmentation faultA native crash. Usually an architecture mismatch or a broken shared library in the image.
143SIGTERMSomething asked the container to stop: a failing liveness probe, an eviction, or a rolling update.

If the code is 137, the memory limit is your problem and the rest of this page is the wrong guide — see OOMKilled and exit code 137.

Six causes and their fixes

1. The application crashes on startup. Exit code 1, and the previous logs show an exception. Read the trace before changing any Kubernetes configuration — a database URL that is wrong is not a cluster problem.

2. A missing ConfigMap or Secret. The container never runs, so there are no logs at all. The pod status is usually CreateContainerConfigError rather than CrashLoopBackOff, and the events name the missing object.

kubectl get events -n <namespace> --field-selector involvedObject.name=<pod>

# does the referenced object actually exist?
kubectl get configmap,secret -n <namespace>

3. A failing liveness probe. The application starts, the probe fails, the kubelet kills it, and the loop repeats. The signature is exit code 143 plus Liveness probe failed in the events. The usual cause is a probe that is stricter than the application's real startup time.

spec:
  template:
    spec:
      containers:
        - name: app
          startupProbe:            # gives slow starters room
            httpGet: { path: /healthz, port: 8080 }
            failureThreshold: 30
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /healthz, port: 8080 }
            initialDelaySeconds: 10
            periodSeconds: 10

A startupProbe is the right fix for an application that needs 90 seconds to warm up. Raising initialDelaySeconds on the liveness probe works too, but it also delays detection of a genuine hang for the whole life of the pod.

4. A wrong command or entrypoint. Exit code 127, or an error naming a file that is not in the image. Check what the manifest overrides:

kubectl get pod <pod> -n <namespace> \
  -o jsonpath="{range .spec.containers[*]}{.name}{': '}{.command}{' '}{.args}{'\n'}{end}"

5. A dependency that is not reachable yet. The application exits because a database or queue refuses the connection. The fix belongs in the application: retry with backoff rather than exiting. If you cannot change it, an init container that waits for the dependency is the usual workaround.

6. An OOM kill. Exit code 137 with reason OOMKilled. Covered in full on the exit code 137 guide.

Why the restarts get slower

The kubelet backs off exponentially: roughly 10 seconds, then 20, 40, 80, and so on, capped at five minutes. A pod that appears frozen for minutes is usually waiting out that timer rather than hanging.

Two practical consequences. First, after a fix, the pod may not recover immediately — it has to reach the end of the current backoff. Deleting the pod forces a fresh start and skips the wait. Second, a high restart count with long gaps means the pod has been failing for hours, not minutes, and the timestamps in describe will tell you since when.

Debugging a container that dies too fast to exec into

The frustrating case: the container exits in under a second, so kubectl exec never connects. Two ways in.

Override the entrypoint temporarily so the container stays up, then reproduce the start by hand:

kubectl run debug-pod --rm -it --restart=Never \
  --image=<the same image and tag> \
  --command -- sleep 3600

# then, in another shell
kubectl exec -it debug-pod -- sh
# run the real entrypoint yourself and watch it fail

Or attach an ephemeral container to the running pod, which needs no manifest change:

kubectl debug -it <pod> -n <namespace> \
  --image=busybox --target=<container>

Use the exact image and tag from the failing pod. Debugging latest when the deployment pins a digest reproduces a different container and wastes the exercise.

When the failing container is an init container

A pod stuck at Init:CrashLoopBackOff is failing before your application ever starts. Init containers run in order and the pod does not proceed until each one succeeds, so a migration or wait-for-dependency step that fails will loop indefinitely.

kubectl logs <pod> -n <namespace> -c <init-container-name> --previous

# list the init containers and their statuses
kubectl get pod <pod> -n <namespace> \
  -o jsonpath="{range .status.initContainerStatuses[*]}{.name}{': '}{.state}{'\n'}{end}"

Reading the application container's logs here returns nothing, because it has not started. Name the init container explicitly.

What not to do

Do not restart the deployment and hope. A rollout restart re-runs the same failing configuration. If the cause is a missing Secret, you get the same loop with a fresh timestamp and no new information.

Do not remove the liveness probe to stop the killing. That converts a visible restart loop into a silently wedged pod that still receives traffic. Fix the probe timing instead.

Do not switch the image to latest to see if it helps. It changes two variables at once and makes the result impossible to interpret.

Do not raise resources reflexively. Unless the exit code is 137, memory is not the constraint and you are adding cost without changing anything.

Verify the fix

kubectl rollout status deployment/<name> -n <namespace>

# restart count should stop climbing
kubectl get pods -n <namespace> -w

# and the pod should report Ready, not just Running
kubectl get pod <pod> -n <namespace> \
  -o jsonpath="{.status.conditions[?(@.type=='Ready')].status}"

Running is not the same as Ready. A pod can run while failing its readiness probe, which keeps it out of the Service and looks like an unrelated networking problem.

Frequently asked

What does CrashLoopBackOff mean? The container keeps starting and exiting, and the kubelet is waiting before the next attempt. It is a backoff state, not a cause.

Why are the logs empty? You are reading the container that has not started yet. Add --previous.

Why does it take five minutes to restart? That is the backoff cap. Delete the pod to skip the wait after a fix.

Is CrashLoopBackOff always the application's fault? No. A missing ConfigMap, a strict liveness probe or a memory limit are cluster-side causes that produce an identical status.

What is the difference from ImagePullBackOff? ImagePullBackOff happens before the container runs — Kubernetes cannot fetch the image. CrashLoopBackOff means the image ran and the process exited.

Doing this automatically

The sequence above — previous logs, exit code, events, probe configuration, the container's real command — is mechanical, which is why it is worth automating rather than repeating at 2am.

KrevoPilot runs it and returns the cause with the evidence attached: the exit code, the restart count, the command the container actually ran, the probe that failed, and the manifest field to change. When the evidence does not support a conclusion it says so instead of offering a plausible guess. The in-cluster agent is read-only and cannot change your workloads.

Apply for a trial OOMKilled guide Troubleshooting guide