Skip to content
ohsudo
Kubernetes

Kubernetes CrashLoopBackOff: 7 causes and how to find yours

CrashLoopBackOff means the container keeps exiting. Read the exit code and the previous logs, then fix the crash, OOM kill, probe or command.

Intermediate5 min readApplies to Kubernetes 1.28+ · kubectl · any distribution (EKS · GKE · AKS · k3s · kubeadm)

by OhSudo ·

Your pod won’t stay up:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
api-7d9c8b6f5d-x2k4q 0/1 CrashLoopBackOff 6 (82s ago) 9m

CrashLoopBackOff is not the error. It’s Kubernetes waiting. The container started, exited, was restarted, and exited again. After each crash, the kubelet waits longer before the next try — 10 seconds, then 20, 40… up to 5 minutes. That waiting is the “back-off”. The real error is whatever made the container exit, and Kubernetes keeps it for you in two places: the exit code and the logs of the previous run.

TL;DR

Terminal window
kubectl describe pod api-7d9c8b6f5d-x2k4q # Last State: Reason + Exit Code, and Events
kubectl logs api-7d9c8b6f5d-x2k4q --previous # output of the run that crashed
Last State Most likely cause Section
Error, exit code 1 (or another small number) The app fails at startup 1, 2
OOMKilled, exit code 137 Out of memory 3
exit code 137 or 143, probe failures in Events Liveness probe kills it 4
Completed, exit code 0 The program just ends 5
StartError, exec format error, not found Wrong command or image 6
permission denied in the logs Security context 7

Read the two clues first

Terminal window
kubectl describe pod api-7d9c8b6f5d-x2k4q

In the container section, look for Last State:

State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error
Exit Code: 1
Started: Sun, 01 Nov 2026 09:40:12 +0000
Finished: Sun, 01 Nov 2026 09:40:14 +0000

Started and Finished matter too: a container that dies after 2 seconds fails at startup, one that dies after exactly 30 or 60 seconds is often killed by a probe. At the bottom, Events shows what Kubernetes did — probe failures, OOM kills, image problems.

Then read the logs. Plain kubectl logs shows the current container, which has just restarted and may have printed nothing yet. --previous shows the run that crashed:

Terminal window
kubectl logs api-7d9c8b6f5d-x2k4q --previous
kubectl logs api-7d9c8b6f5d-x2k4q -c app --previous # pods with several containers

1. The app fails at startup

Exit code 1 (or another small number) means the program itself decided to stop. The last lines of --previous usually say why:

Error: Missing required environment variable DATABASE_URL

Typical causes: a missing or misspelled environment variable, a config file mounted at the wrong path, a wrong key in a Secret. Check what the container really gets:

Terminal window
kubectl get deployment api -o yaml | grep -A20 'env:'
kubectl get configmap api-config -o yaml

If the crashes started with a new version, roll back first and investigate after:

Terminal window
kubectl rollout undo deployment/api

2. A dependency is not reachable (yet)

Error: connect ECONNREFUSED 10.96.112.4:5432

The app tries the database (or a queue, or an API) once at startup, fails, and exits. In Kubernetes, pods start in any order, so this happens after every cluster restart or when the database pod is being rescheduled. Fix it in this order:

  • Check that the target exists and answers: the Service name, the port, and kubectl get endpointslices -l kubernetes.io/service-name=postgres (no endpoints = no ready pod behind the Service).
  • Make the app retry the connection instead of exiting on the first failure — the most robust fix.
  • Or wait for it with an init container that runs before the app:
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z postgres 5432; do echo waiting for db; sleep 2; done']

3. OOMKilled: the container ran out of memory

Last State: Terminated
Reason: OOMKilled
Exit Code: 137

The container went over its memory limit (resources.limits.memory) and the kernel killed it. The logs just stop, with no error. See the limit and the real usage:

Terminal window
kubectl get pod api-7d9c8b6f5d-x2k4q -o jsonpath='{.spec.containers[0].resources}'
kubectl top pod api-7d9c8b6f5d-x2k4q # needs metrics-server

Raise the limit if the app really needs more, or make the app use less — the same runtime settings as for Docker apply (Java -XX:MaxRAMPercentage, Node --max-old-space-size, fewer workers). See Docker “exited with code 137” for those details.

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

4. The liveness probe kills a healthy app

The app is fine, but Kubernetes kills it because the liveness probe fails. Events show it clearly:

Warning Unhealthy Liveness probe failed: Get "http://10.244.1.17:8080/healthz": dial tcp 10.244.1.17:8080: connect: connection refused
Normal Killing Container app failed liveness probe, will be restarted

The exit code is then usually 143 (the app stopped on SIGTERM) or 137 (it didn’t stop in time). Common reasons:

  • Slow startup: the probe starts checking before the app is ready to answer. Give startup its own probe; the liveness probe only begins once it succeeds:
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 30 × 10s = up to 5 minutes to start
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
  • Wrong path or port in the probe: check them against the app with kubectl port-forward pod/api-7d9c8b6f5d-x2k4q 8080:8080 and curl -i localhost:8080/healthz.
  • A health check that is too strict: if /healthz also checks the database, a database outage restarts every pod in a loop. Liveness should only answer “is this process stuck?”.

5. The program finishes (exit code 0)

Last State: Terminated
Reason: Completed
Exit Code: 0

Nothing crashed: the program did its work and ended. But a Deployment expects a process that keeps running, so Kubernetes starts it again, and again. Typical causes: the container runs a one-off script (a migration, a backup), or the server starts in the background and the main script ends.

  • For one-off work, use a Job (or a CronJob for scheduled work) instead of a Deployment.
  • For a server, make sure it runs in the foreground as the container’s main process — for example nginx -g 'daemon off;', not a script that starts nginx and exits.

6. Wrong command or wrong image

The container can’t even start its program. The reason is in describe, often with Reason: StartError:

exec: "/app/start.sh": stat /app/start.sh: no such file or directory
exec: "gunicorn": executable file not found in $PATH

Check the command and args in your manifest against what’s really in the image — they override the image’s ENTRYPOINT and CMD. If the logs show:

exec /usr/local/bin/app: exec format error

the image was built for another CPU architecture — for example on an Apple Silicon Mac (arm64) and run on amd64 nodes. Build for the nodes’ platform, or a multi-platform image:

Terminal window
docker buildx build --platform linux/amd64,linux/arm64 -t registry.example.com/api:1.4.2 --push .

7. Permission denied inside the container

mkdir: cannot create directory '/var/cache/app': Permission denied

The pod’s securityContext doesn’t match what the image expects: runAsNonRoot / runAsUser with an image that writes to root-owned folders, or readOnlyRootFilesystem: true with an app that writes to its own directory. Don’t remove the security settings — give the app a writable place instead:

volumeMounts:
- name: cache
mountPath: /var/cache/app
volumes:
- name: cache
emptyDir: {}

For volumes owned by root, securityContext.fsGroup makes them writable for the pod’s group.

When the logs are empty

If the container dies too fast to log anything, start a copy of the pod with a shell instead of the app, and run the command by hand:

Terminal window
kubectl debug api-7d9c8b6f5d-x2k4q -it --copy-to=api-debug --container=app -- sh

Inside, run the original command and watch it fail. Delete the copy when you’re done: kubectl delete pod api-debug.

Shell, we have a problem!

Similar, but different: ImagePullBackOff / ErrImagePull — the image can’t be downloaded (wrong name or tag, private registry without credentials), so nothing runs at all. CreateContainerConfigError — a ConfigMap or Secret the pod refers to doesn’t exist. Pending — no node has room for the pod’s resource requests. None of these crash, so --previous logs don’t exist; kubectl describe pod explains them.

Found a mistake? Let us know at contact@ohsudo.com.

Articles

Related articles