Kubernetes CrashLoopBackOff: Causes and How to Fix It (Step by Step)
CrashLoopBackOff means your container starts, crashes, and Kubernetes keeps restarting it, waiting a little longer each time. It is a symptom, not the cause. Run kubectl describe pod <name> to read the exit code, and kubectl logs <name> --previous to read the error of the container that crashed. The exit code tells you whether it is an app error, a wrong command, missing config, memory (OOMKilled) or a failing probe.
Quick answer
Run these four commands in this order. Most of the time the answer is in step 3:
kubectl get pods # 1. STATUS = CrashLoopBackOff, RESTARTS keeps growing
kubectl describe pod orders-api-7d9f-x2k # 2. Last State: Terminated, Reason, Exit Code
kubectl logs orders-api-7d9f-x2k --previous # 3. the real error from the crashed container
kubectl get deploy orders-api -o yaml # 4. check env, command, resources, probes
A live example first
Friends, let us see the error before the theory. Here is a small lab cluster (kind on a laptop, the same on Amazon EKS). We deploy the official postgres:16 image, but we forget the password variable:
kubectl create deployment db --image=postgres:16
kubectl get pods
NAME READY STATUS RESTARTS AGE
db-6c8d7b9f5d-q8l2m 0/1 CrashLoopBackOff 4 (32s ago) 2m
Now the old logs:
kubectl logs db-6c8d7b9f5d-q8l2m --previous
Error: Database is uninitialized and superuser password is not specified.
You must specify POSTGRES_PASSWORD to a non-empty value for the
superuser.
There is the cause in plain English. It is not a Kubernetes problem at all. The app refused to start without its config. The fix is one line:
kubectl create secret generic db-pass --from-literal=POSTGRES_PASSWORD='Lab@12345'
kubectl set env deployment/db --from=secret/db-pass
kubectl get pods -w
After about 20 seconds the pod shows 1/1 Running and the restarts stop. That is the whole method: read, don't guess.
Why does Kubernetes say CrashLoopBackOff?
CrashLoopBackOff is a symptom. The exit code and logs --previous tell you the real cause.
The kubelet on the node starts your container. When the main process exits, the kubelet restarts it (because restartPolicy: Always). If it keeps dying, the kubelet waits 10 seconds, then 20, then 40, up to 5 minutes between tries. That growing wait is the back-off. So the pod is not "broken by Kubernetes". Your process is exiting, and Kubernetes is politely trying again.
How do I fix CrashLoopBackOff step by step?
- Run
kubectl get podsand note the pod name and the RESTARTS count. - Run
kubectl describe pod <name>and scroll to Last State. - Write down Reason and Exit Code.
- Run
kubectl logs <name> --previousand read the last 20 lines. - If the pod has more than one container, add
-c <container>. - Match the exit code in the table below.
- Change the Deployment YAML, not the pod.
- Run
kubectl apply -f deploy.yaml. - Run
kubectl get pods -wand wait until RESTARTS stops growing.
What the exit code tells you
| Exit code / Reason | Meaning | Where to look |
|---|---|---|
1 Error |
The app threw an error and quit | logs --previous: stack trace, missing config |
127 |
Command or file not found | command: and args: in YAML, the image entrypoint |
126 |
File not executable | chmod +x in the Dockerfile |
137 OOMKilled |
Memory limit hit, killed with signal 9 | resources.limits.memory, see the OOMKilled guide |
137 Error (no OOM) |
Killed by a failing liveness probe | livenessProbe, Events: "Liveness probe failed" |
0 Completed |
The process finished normally | A web app must stay in the foreground; a batch job should be a Job |
Exit code 0: the "it worked, but it stopped" trap
Many students build an image whose CMD runs a script that starts the server in the background and then ends. The script exits with 0, so the container stops, and Kubernetes restarts it forever. The fix is to run the server in the foreground, for example nginx -g 'daemon off;' instead of service nginx start.
Common causes, stack by stack
The same symptom has a different look in every language. Here is what the --previous log usually shows.
Java / Spring Boot
APPLICATION FAILED TO START
Failed to configure a DataSource: 'url' attribute is not specified
Fix: set SPRING_DATASOURCE_URL from a ConfigMap or Secret. Spring Boot is also slow to start, so give it a startupProbe on /actuator/health.
Node.js
Error: Cannot find module '/app/server.js'
Fix: the WORKDIR or COPY path in the Dockerfile does not match the CMD. Run docker run --rm -it image sh and ls /app to see what is really inside.
Python (Flask / Django with Gunicorn)
ModuleNotFoundError: No module named 'psycopg2'
Fix: the package is missing from requirements.txt, or it was installed in a different build stage. Rebuild and push a new tag.
Go
exec /app/server: no such file or directory
Fix: the binary was built for glibc but the image is Alpine (musl). Build with CGO_ENABLED=0 for a static binary.
.NET
Unhandled exception. System.IO.FileNotFoundException: appsettings.Production.json
Fix: mount the config file with a ConfigMap, or set ASPNETCORE_ENVIRONMENT to the file you actually ship.
nginx
nginx: [emerg] host not found in upstream "api" in /etc/nginx/conf.d/default.conf
Fix: the Service name in proxy_pass does not exist in this namespace. Create the Service first, or use the full name api.default.svc.cluster.local.
What if the container dies too fast to debug?
Override the command for a few minutes, so the container stays alive and you can look inside:
command: ["sleep", "3600"]
Then:
kubectl exec -it deploy/orders-api -- sh
env | sort # are the variables there?
ls -l /app # are the files there?
/app/start.sh # run the real start command and read the error
Remove the override when you are done. Never leave a debug sleep in production.
Real incidents: why careful restarts matter
- Reddit, March 2023 (Pi Day outage): Reddit's public post-mortem explains that a Kubernetes version upgrade went wrong because of an old node label used by their Calico network setup. The site was down for about 5 hours. The lesson for us: read the real error first, and test upgrades on a copy of the cluster.
- Monzo, October 2017: Monzo's write-up describes how a combination of Kubernetes, etcd and their linkerd service mesh stopped requests between services, and the bank's app was down for about 1.5 hours. Pods looked "up" but could not talk to each other, which is why we always check logs and events, not only the STATUS column.
Summary table
| Step | Command | What you learn |
|---|---|---|
| 1 | kubectl get pods |
Which pod, how many restarts |
| 2 | kubectl describe pod |
Reason and exit code |
| 3 | kubectl logs --previous |
The real error message |
| 4 | Fix YAML / image | Env, command, memory, probe |
| 5 | kubectl get pods -w |
Proof that it stays Running |
Quick quiz: check what you learned
Which command shows the logs of the container that crashed?
- kubectl logs POD --previous
- kubectl get pods --watch
- kubectl top pod POD now
- kubectl get events -A
Show answer
A. The --previous flag reads the last terminated container, which holds the crash message.
Exit code 127 in Last State usually means what?
- The command was not found
- The memory limit was hit
- The image tag is missing
- The node has no free CPU
Show answer
A. 127 is the shell code for command not found, so check command and args in the YAML.
Why does deleting a crashing pod not help?
- The Deployment recreates it
- The node blocks deletions
- Kubernetes keeps old logs
- The image is cached locally
Show answer
A. A Deployment keeps the desired replica count, so the same broken spec comes back.
What helps a slow app that is killed by its liveness probe?
- Add a startupProbe to wait
- Remove the memory request
- Change the image pull policy
- Scale replicas down to zero
Show answer
A. A startupProbe pauses liveness checks until the app has finished starting.
Learn it properly
This guide is the short path. The free DevOps course covers Kubernetes step by step, with labs:
Frequently asked questions
What does CrashLoopBackOff mean?
The container starts, crashes, and Kubernetes keeps restarting it. Between restarts it waits longer each time (10s, 20s, 40s up to 5 minutes). That waiting is the BackOff.
Why are kubectl logs empty?
You are reading the new container, which has not printed anything yet. Use kubectl logs POD --previous to read the container that crashed.
What is exit code 137?
137 means the process was killed with signal 9. In Kubernetes it is usually OOMKilled (memory limit hit) or a failing liveness probe.
Can a liveness probe cause CrashLoopBackOff?
Yes. If the probe fails before the app is ready, the kubelet kills the container again and again. Add a startupProbe or a longer initialDelaySeconds.
How do I debug a container that dies too fast?
Temporarily override the command with sleep 3600, exec into the pod and run the start command yourself to see the error.
Does deleting the pod fix it?
No. The Deployment creates a new pod with the same broken spec. Fix the YAML, image or config instead.