Skip to main content
DevOpsintermediate

CrashLoopBackOff in Kubernetes: How to Diagnose and Fix

Fix CrashLoopBackOff in Kubernetes. Read the previous container logs, decode exit codes 1, 137 and 143, and rule out OOMKilled and failing probes.

10 min readUpdated August 2026

A pod in CrashLoopBackOff has a rising restart count:

$ kubectl get pods
NAME                        READY   STATUS             RESTARTS      AGE
api-6b8f7d9c5d-h4trm        0/1     CrashLoopBackOff   5 (48s ago)   4m

CrashLoopBackOff is a symptom, never a cause. Your container started, exited, and Kubernetes restarted it — and after each failure it waits longer before trying again, doubling the delay up to a five-minute cap. The status simply reports that it is currently in one of those waiting periods.

Getting the actual cause takes one command, and the flag is the important part:

kubectl logs api-6b8f7d9c5d-h4trm --previous

--previous returns the logs of the run that crashed. Without it you get the current attempt, which is usually either still starting or already gone — which is why "there are no logs" is such a common and misleading first impression.

Step 1: Get the Exit Code

The exit code narrows the cause faster than anything else:

kubectl describe pod api-6b8f7d9c5d-h4trm

Look for Last State:

    Last State:     Terminated
      Reason:       Error
      Exit Code:    1
      Started:      Tue, 12 Aug 2026 14:02:11 +0000
      Finished:     Tue, 12 Aug 2026 14:02:12 +0000
Exit codeMeaningWhere to look
0Exited successfully — the process finished its workThe container has nothing long-running to do
1Generic application errorkubectl logs --previous
126Command found but not executableEntrypoint permissions in the image
127Command not foundWrong command/args, or missing binary in the image
137SIGKILL (128 + 9) — usually the OOM killerMemory limits
139SIGSEGV (128 + 11) — segmentation faultApplication or native dependency bug
143SIGTERM (128 + 15) — asked to stop and didProbes, evictions, or a normal shutdown

Also check Reason on the same block. OOMKilled is definitive; Error is generic.

Cause 1: The Application Fails at Startup (exit 1)

The overwhelming majority of cases. The process started, tried to initialise, and gave up:

kubectl logs api-6b8f7d9c5d-h4trm --previous

Typical output:

Error: connect ECONNREFUSED 10.96.0.42:5432
    at TCPConnectWrap.afterConnect [as oncomplete]

Common versions of this:

  • A missing environment variable, often because a ConfigMap or Secret key is misspelled. A key that does not exist is injected as empty rather than erroring, so the application fails later with a confusing message.
  • A dependency that is not up yet. Databases and caches routinely start after the application that needs them.
  • A config file that is not where the app expects it, usually a volume mounted at the wrong path.

Check what the container actually received:

kubectl set env deployment/api --list
kubectl get configmap api-config -o yaml

For a dependency that is merely slow, the right fix is retry-with-backoff in the application, or an init container that waits:

initContainers:
  - name: wait-for-db
    image: busybox:1.36
    command: ['sh', '-c', 'until nc -z db 5432; do echo waiting for db; sleep 2; done']

An init container is better than raising the restart tolerance, because the main container never starts in a known-bad state.

Cause 2: Out of Memory (exit 137, OOMKilled)

    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137

The container exceeded its memory limit and the kernel killed it. This is not a soft warning — the process gets SIGKILL and cannot clean up.

Check what is configured:

kubectl get pod api-6b8f7d9c5d-h4trm -o jsonpath='{.spec.containers[*].resources}'

Then either raise the limit or reduce consumption:

resources:
  requests:
    memory: "256Mi"
  limits:
    memory: "512Mi"

Two subtleties worth knowing. Raising the limit only helps if the application has a genuine steady-state need — with a leak it delays the kill rather than preventing it, and the interval between restarts is the diagnostic. And JVM or Node applications need to be told about the limit; a JVM that does not see the cgroup limit sizes its heap against the node's total memory and is killed long before it thinks it is under pressure.

Advertisement

Cause 3: A Failing Liveness Probe (exit 137 or 143)

This one is easy to miss because the application is often perfectly healthy. If the liveness probe fails, the kubelet kills the container regardless.

The tell is in the events:

  Warning  Unhealthy  30s (x3 over 50s)  kubelet  Liveness probe failed: Get "http://10.244.1.5:8080/healthz": context deadline exceeded
  Normal   Killing    30s                kubelet  Container api failed liveness probe, will be restarted

The usual mistake is a liveness probe that starts checking before a slow-booting application is ready. Use a startup probe for boot time and keep the liveness probe for genuine hangs:

startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  failureThreshold: 30
  periodSeconds: 10      # allows up to 5 minutes to start

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 10
  failureThreshold: 3

While a startup probe is running, the liveness probe is disabled — which is exactly the behaviour you want.

Cause 4: Wrong Command or Entrypoint (exit 127 or 126)

If there are no application logs at all, the process may never have run. Check the events rather than the logs:

  Warning  Failed  10s  kubelet  Error: failed to create containerd task: failed to create shim task: OCI runtime create failed: exec: "/app/start.sh": stat /app/start.sh: no such file or directory

Confirm what the image expects by inspecting it, rather than guessing:

docker inspect myrepo/api:v1.2.3 --format '{{.Config.Entrypoint}} {{.Config.Cmd}}'

exec format error in place of the above means an architecture mismatch — an amd64 image on arm64 nodes, or the reverse. Build multi-arch images or pin the node architecture.

Cause 5: The Container Exits Successfully (exit 0)

Exit code 0 with restarts climbing means the container did its job and stopped — and the default restartPolicy: Always restarted it anyway. Kubernetes Deployments expect a long-running process.

If the workload is genuinely a one-shot task, it should be a Job, not a Deployment:

apiVersion: batch/v1
kind: Job
metadata:
  name: migrate
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: migrate
          image: myrepo/api:v1.2.3
          command: ["./migrate"]

How to Pause the Crash Loop and Debug

Deleting the pod does not help — the controller recreates it, and you lose the previous logs. Scale down instead:

kubectl scale deployment/api --replicas=0

Then run a debug copy that stays up regardless of whether the app works:

kubectl run debug --rm -it --image=myrepo/api:v1.2.3 --restart=Never \
  --command -- sleep 3600

In another terminal, exec in and reproduce the startup by hand:

kubectl exec -it debug -- sh

On clusters that support it, kubectl debug attaches an ephemeral container to the real pod, keeping its actual environment and volumes:

kubectl debug -it api-6b8f7d9c5d-h4trm --image=busybox --target=api

Verify the Fix

kubectl rollout restart deployment/api
kubectl rollout status deployment/api
kubectl get pods -w

A fixed pod reaches Running with 1/1 ready and a restart count that stops climbing. Watch it for a few minutes before declaring victory — the backoff only resets after the container has stayed up for ten minutes, so a pod that survives thirty seconds has not necessarily recovered.

kubectl get pod -l app=api -o wide

Prevention

  • Set memory requests and limits deliberately, and make runtimes cgroup-aware (-XX:MaxRAMPercentage for the JVM; recent Node versions read the limit automatically).
  • Use startup probes for slow boots rather than inflating initialDelaySeconds on the liveness probe.
  • Fail loudly and early on missing configuration. An application that validates its environment at startup and prints exactly what is missing turns a fifteen-minute investigation into a ten-second one.
  • Retry external dependencies with backoff instead of exiting on the first connection refusal.
  • Alert on restart counts, not just on Ready. A pod that crashes and recovers every few minutes can stay technically available while dropping requests the whole time.

Frequently Asked Questions

Find answers to common questions

Your container started, exited, and Kubernetes restarted it — repeatedly. CrashLoopBackOff is the waiting period between restarts, which doubles after each failure up to five minutes. It is a symptom: the real cause is why the container exited, which is in the logs of the previous run.

Run 'kubectl logs --previous'. Without --previous you get the current attempt, which is usually still starting or already gone. The --previous flag returns the output of the run that actually crashed, and that is where the stack trace or error message lives.

137 is 128 + 9, meaning the process was killed by SIGKILL. Almost always that is the OOM killer because the container exceeded its memory limit — confirm by checking whether the last state reason is OOMKilled. Raise the memory limit or reduce what the application allocates.

A generic application error — the process started and then chose to exit unsuccessfully. Missing environment variables, an unreachable database, a malformed config file, and unhandled startup exceptions all produce it. The logs from the previous run will say which.

The process never got far enough to write anything. Usually the command or entrypoint does not exist in the image, which shows as a StartError or 'exec format error' in the pod events rather than in the logs. Run 'kubectl describe pod' and read the events instead.

Yes, and it is a commonly missed cause. If the liveness probe fails, the kubelet kills and restarts the container even when the application is healthy but slow to start. The tell is a 'Liveness probe failed' event with exit code 137. Add a startup probe or increase initialDelaySeconds.

Scale the deployment to zero with 'kubectl scale deployment/ --replicas=0', or temporarily override the container command with something long-running such as sleep 3600 so it stays up and you can exec in. Do not delete the pod — the controller just recreates it and you lose the previous logs.

Forever, with backoff capped at five minutes between attempts. The restart counter keeps climbing and the pod never moves to a failed state on its own, so nothing times out for you. The backoff resets once the container stays up for ten minutes.

Error is the status right after a container exits non-zero. CrashLoopBackOff appears once Kubernetes has restarted it enough times to begin backing off. Both mean the container is exiting; CrashLoopBackOff just means it has been happening long enough for the delay to kick in.

Not usually. The image pulled and the container started, which rules out most image problems. The failure is at runtime — configuration, dependencies, permissions, resource limits, or a probe. A broken image normally shows up as ImagePullBackOff or InvalidImageName instead.