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.
by OhSudo ·
Your pod won’t stay up:
$ kubectl get podsNAME READY STATUS RESTARTS AGEapi-7d9c8b6f5d-x2k4q 0/1 CrashLoopBackOff 6 (82s ago) 9mCrashLoopBackOff 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
kubectl describe pod api-7d9c8b6f5d-x2k4q # Last State: Reason + Exit Code, and Eventskubectl 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
kubectl describe pod api-7d9c8b6f5d-x2k4qIn 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 +0000Started 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:
kubectl logs api-7d9c8b6f5d-x2k4q --previouskubectl logs api-7d9c8b6f5d-x2k4q -c app --previous # pods with several containers1. 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_URLTypical 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:
kubectl get deployment api -o yaml | grep -A20 'env:'kubectl get configmap api-config -o yamlIf the crashes started with a new version, roll back first and investigate after:
kubectl rollout undo deployment/api2. A dependency is not reachable (yet)
Error: connect ECONNREFUSED 10.96.112.4:5432The 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: 137The 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:
kubectl get pod api-7d9c8b6f5d-x2k4q -o jsonpath='{.spec.containers[0].resources}'kubectl top pod api-7d9c8b6f5d-x2k4q # needs metrics-serverRaise 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: 512Mi4. 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 refusedNormal Killing Container app failed liveness probe, will be restartedThe 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: 10livenessProbe: 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:8080andcurl -i localhost:8080/healthz. - A health check that is too strict: if
/healthzalso 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: 0Nothing 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 directoryexec: "gunicorn": executable file not found in $PATHCheck 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 errorthe 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:
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 deniedThe 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/appvolumes: - 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:
kubectl debug api-7d9c8b6f5d-x2k4q -it --copy-to=api-debug --container=app -- shInside, 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.