Kubernetes

CrashLoopBackOff

Your container starts, exits, and Kubernetes restarts it with growing delay. Here is how to find which of the six causes is responsible, in the order they actually occur.

The error

NAME                     READY   STATUS             RESTARTS   AGE
payments-api-7d4f-x8k2   0/1     CrashLoopBackOff   6          8m

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.

# 1. The logs of the run that CRASHED — not the one starting now
kubectl logs payments-api-7d4f-x8k2 --previous

--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.

# 2. Events and the termination reason
kubectl describe pod payments-api-7d4f-x8k2

The bottom of that output is what matters:

    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
Events:
  Warning  BackOff  kubelet  Back-off restarting failed container

And the single most useful one-liner:

kubectl get pod payments-api-7d4f-x8k2 \
  -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}{"\n"}{.status.containerStatuses[0].lastState.terminated.exitCode}'

Match the exit code to the cause

Exit codeReasonWhat it means
137OOMKilledExceeded the memory limit — cause 1
137ErrorSIGKILL from a failed liveness probecause 4
143ErrorSIGTERM — something asked it to stop
1ErrorGeneric application failure — cause 2 or 3
0CompletedFinished successfully — cause 5
127ErrorCommand not found — cause 6
126ErrorCommand found but not executable

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.

kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
# OOMKilled

Check what it actually uses, then raise the limit — or find the leak:

kubectl top pod <pod> --containers

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.

env:
  - name: JAVA_TOOL_OPTIONS
    value: "-XX:MaxRAMPercentage=75"

Cause 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.

kubectl logs <pod> --previous
# Error: DATABASE_URL is not defined

Check what the container actually received:

kubectl exec <pod> -- env | sort          # if it stays up long enough
kubectl get pod <pod> -o jsonpath='{.spec.containers[0].env}'

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.

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.

kubectl logs <pod> --previous
# Error: connect ECONNREFUSED 10.96.41.203:5432

Test reachability from inside the cluster:

kubectl run tmp --rm -it --image=nicolaka/netshoot -- \
  nc -zv payments-db.payments.svc.cluster.local 5432

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.

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.

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 restarted

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:

startupProbe:
  httpGet: { path: /healthz, port: 8080 }
  periodSeconds: 10
  failureThreshold: 30      # up to 300s to boot
livenessProbe:
  httpGet: { path: /healthz, port: 8080 }
  periodSeconds: 10
  failureThreshold: 3       # then detect a hang in ~30s

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.

Cause 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.

kubectl logs <pod> --previous
# exec /app/start.sh: no such file or directory

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 get pod <pod> -o jsonpath='{.spec.containers[0].command} {.spec.containers[0].args}'
docker inspect --format '{{.Config.Entrypoint}} {{.Config.Cmd}}' <image>

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:

kubectl run debug --image=<your-image> --restart=Never -it --command -- sh

Attach an ephemeral container to the failing Pod, sharing its namespaces:

kubectl debug -it <pod> --image=nicolaka/netshoot --target=<container>

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.

A checklist

  1. kubectl logs <pod> --previous — the crashed run's output.
  2. kubectl describe pod <pod> — events and Last State.
  3. Read the exit code against the table above.
  4. 137 + OOMKilled → raise the memory limit, or fix the runtime's heap sizing.
  5. Killing in events with clean logs → add a startup probe.
  6. Exit 0 → it should be a Job, not a Deployment.
  7. Exit 127 → wrong command, missing file, or CRLF line endings.

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.

Learn the underlying concept

Other Kubernetes errors