CrashLoopBackOff is a Kubernetes pod status rather than an error of its own. It means your container started, exited, and the kubelet is waiting a while before starting it again.
If a pod is sitting in that state right now, the cause is almost always one of these:
- A memory limit set below what the workload actually uses, so the kernel kills the process as soon as it grows past the limit.
- A dependency missing from the image, so the process dies on startup with a message like
no module named. Init containers fail the same way, and a failed init container stops the main container from ever running. - A Secret, ConfigMap, or environment variable that no longer matches what the application reads, which is how a release that worked last month starts crashing with no code change at all.
- An image tag that moved. A
:latestor:1tag resolves to a different image than it did at the last deploy. - A liveness probe failing during startup, where the application is healthy and simply needs longer to boot than the probe allows.
- A container exiting with code 0, which reads as success everywhere except Kubernetes, where a service that finishes is a service that stopped.
This guide covers each of these, how to tell which one you have from the exit code and the crashed run's logs, and what to change once you know.
Understanding CrashLoopBackOff in Kubernetes
What CrashLoopBackOff actually means
Every node runs an agent called the kubelet, and its job is to start the containers assigned to that node and keep them running. When a container exits, the kubelet starts it again.
A container that fails on startup exits again straight away, and the restarts become a loop. Instead of retrying as fast as it can, the kubelet waits between attempts, and waits a little longer each time. CrashLoopBackOff is the name for those waiting periods.
CrashLoopBackOff appears in the STATUS column of kubectl get pods:
NAME READY STATUS RESTARTS AGE
checkout-api-59bbbb748d-76z5z 0/1 CrashLoopBackOff 6 (3m20s ago) 9m22sRESTARTS counts the restarts, and the time in parentheses is how long ago the last one happened. One restart is routine. A count that keeps climbing means the container has never stayed up.
CrashLoopBackOff compared with ImagePullBackOff: the difference is whether the container ever ran. ImagePullBackOff means the kubelet could not download the image, so nothing started, usually a wrong tag, a private registry with no credentials, or a network problem. CrashLoopBackOff means the image pulled fine and the container exited on its own.
The backoff interval mechanism
Every time the container crashes, the kubelet waits before starting it again, and each wait is double the one before. The sequence runs 10 seconds, then 20, 40, 80, and 160, before holding at 300 seconds, so five minutes is the longest gap you ever see between attempts.
Without that wait, a container that crashed and restarted instantly would allocate memory, open connections, and write logs in a tight loop, and one container stuck in that loop would affect everything else on the node.
If a container manages to stay up for 10 minutes, Kubernetes forgets all its previous crashes, and the next crash starts over at a 10-second wait. A container that crashes every 12 minutes is therefore forgiven every time. Its wait never grows, its status never becomes CrashLoopBackOff, and kubectl get pods shows it as Running. An alert watching for the CrashLoopBackOff status never fires, even though the container has restarted 40 times today, so watch the restart count instead.
Restart policy and its role in CrashLoopBackOff
restartPolicy is a field on the pod spec, and it decides whether the kubelet restarts an exited container at all:
Alwaysrestarts the container whenever it exits, whatever code it returned. That value is the default, and it is the only one a Deployment, StatefulSet, or DaemonSet accepts, which is why CrashLoopBackOff shows up almost entirely on long-running services.OnFailurerestarts only after a non-zero exit code, and treats a clean exit as a finished job.Neverleaves the container stopped, and the pod settles intoFailedorSucceeded.
If the crashing container is a one-off task, a database migration or a nightly backup, you can stop the loop by setting restartPolicy to Never. Kubernetes then leaves the container stopped after the first failure instead of retrying it. Jobs accept only Never or OnFailure, because Always would restart the container even after it finished successfully and the Job would never be marked done.
For a long-running service you have no such option. A Deployment accepts only Always, so you cannot turn the restarts off, and the crash itself has to be fixed.
Pod status transitions leading to CrashLoopBackOff
A crashing pod moves through Pending while it is scheduled and pulled, into Running when the container starts, and into the waiting state the moment it exits. kubectl describe pod prints where the pod is now and where it was when it last stopped:
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error
Exit Code: 1
Started: Thu, 20 Aug 2026 14:06:56 +0200
Finished: Thu, 20 Aug 2026 14:06:56 +0200
Ready: False
Restart Count: 6State only confirms the pod is waiting, which kubectl get pods already told you. Last State describes the run that just ended, and its Exit Code tells you more about the cause than anything else in the output.
Root causes, diagnosis, and debugging CrashLoopBackOff
Interpreting exit codes to identify crash causes
The exit code is the number the container's main process returned to the kernel when it stopped. Six of them account for nearly every crash loop:
Any code above 128 is the kernel reporting a signal, so subtracting 128 gives the signal number, which makes 137 signal 9 (SIGKILL) and 143 signal 15 (SIGTERM).
Two commands retrieve the code. kubectl describe pod <pod-name> prints it in the Last State block of its output, and kubectl get pod <pod-name> -o json returns the whole status object when you want to pipe it somewhere:
kubectl get pod checkout-api-59bbbb748d-76z5z -o json \
| jq '.status.containerStatuses[].lastState.terminated'
Analyzing pod logs and container logs
kubectl logs <pod-name> reads the running container's output, and a pod sitting in backoff has no running container, so the command usually returns nothing. The flag that returns the crashed container's output is --previous:
kubectl logs checkout-api-59bbbb748d-76z5z --previous--previous reads the log of the run that just crashed. Add --timestamps to line it up against the event times from describe, and -c <container-name> to pick one container out of several.
A failing init container changes the status string. kubectl get pods reports Init:CrashLoopBackOff, the main container never starts, and asking the pod for logs returns an error instead of output:
Defaulted container "checkout-migrate" out of: checkout-migrate, run-migrations (init)
Error from server (BadRequest): container "checkout-migrate" in pod "checkout-migrate-6c8f87c85c-dxkbs" is waiting to start: PodInitializingNaming the init container gets the real error, and describe reports its reason as plain CrashLoopBackOff, so only kubectl get pods shows the Init: prefix:
kubectl logs checkout-migrate-6c8f87c85c-dxkbs -c run-migrationsModuleNotFoundError: No module named 'psycopg2'A container that dies before its logging library starts writes nothing at all, so an empty --previous tells you the crash happened very early, usually in the entrypoint. Shipping logs to Fluentd, Loki, or Elasticsearch also keeps them readable after the pod object is deleted, which matters because --previous only reaches back one run.
When --previous comes back empty, events are the next place to look. They record what the kubelet did rather than what the application printed, so they cover the window before the container wrote anything:
kubectl get events -n <namespace> --sort-by=.lastTimestamp
LAST SEEN TYPE REASON OBJECT MESSAGE
2m26s Warning BackOff pod/checkout-migrate-6c8f87c85c-dxkbs Back-off restarting failed container run-migrationsEvents expire an hour after they are recorded by default, so a crash loop that started this morning can leave a describe with nothing useful in it. Add --field-selector involvedObject.name=<pod-name> to narrow a busy namespace down to one pod.
Application-level bugs causing crash loops
These crashes come from the code rather than the cluster, and they almost always exit 1 with a stack trace in --previous:
- An unhandled exception or panic during startup
- A database or upstream service unreachable at boot, with no retry loop around the connection
- An entrypoint pointing at the wrong binary, or passing arguments the binary rejects
- A package, module, or file the application imports that the image never included
Configuration errors in pod spec
These crashes come from the manifest rather than the code, so the same image runs fine elsewhere. A misspelled environment variable leaves a config value empty, and a ConfigMap or Secret missing from the namespace stops the pod before the container runs. Check both against what the application reads:
kubectl get configmap,secret -n <namespace>
kubectl describe pod <pod-name> | grep -A5 EnvironmentAn image tag that has moved produces the same crash with no change to your manifest. A :latest tag that now points at a new major version gives you a container that pulls successfully and then crashes on a config file it no longer understands, which is why pinning to a digest removes this whole category of crash.
Resource constraints and OOM-related crashes
Exit code 137 says the kernel sent SIGKILL, and the most common reason it does that is memory. When a container's memory use passes its limit, the kernel terminates the process immediately, with no chance for the application to write a message first. kubectl describe pod names the OOM kill directly:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Ready: False
Restart Count: 6OOMKilled on a container that stayed inside its limit means the node ran out of memory rather than the container. When a node runs out of memory, the kubelet evicts pods to reclaim it, starting with the pods that never set a memory request at all.
A CPU limit works differently from a memory limit. When a container reaches its CPU limit, Linux pauses it until its next time slice, so the container gets slow rather than killed. That slowdown alone produces no crash loop, though a container held up this way can time out its liveness probe and get restarted for that reason instead.
kubectl top pod returns podmetrics.metrics.k8s.io not found for the pod you are debugging, since a container waiting out its backoff has nothing running to measure. Point it at a replica that is still up instead:
kubectl top pod -l app=checkout-api --containersFor sizing the numbers rather than guessing at them, the Vertical Pod Autoscaler in recommendation mode reports what a workload has actually used over time, and our guide to Kubernetes resource optimization covers how to set requests and limits from that data.
Liveness, readiness, and startup probe failures
A liveness probe that fails restarts the container, so a probe configured too aggressively creates a crash loop out of an application that works. The classic case is an application that takes 45 seconds to load before it can answer anything, behind a liveness probe that starts checking after 10 and gives up after three failures.
This probe is the problem:
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3A startup probe gives that application the 45 seconds it needs. The startup probe runs first and suspends the liveness probe until it succeeds, so a slow-booting application gets as long as it takes without loosening the liveness check that protects it afterwards:
startupProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 5
failureThreshold: 30 # allows up to 150s to start
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 5
failureThreshold: 3A failing readiness probe never restarts anything, and only pulls the pod out of Service endpoints, so a pod that is Running but never READY is a readiness problem, and a pod that restarts at regular intervals is a liveness problem. The probe events appear at the bottom of kubectl describe pod, with the exact reason each check failed.
Volume mount issues and filesystem errors
Storage problems stop the container at startup, usually before any application logging exists:
- A
mountPaththat does not match where the application reads or writes - A PersistentVolumeClaim stuck in
Pendingbecause no volume matched it, which holds the pod inPendingrather than crashing it - A read-only volume under an application that writes to it
- A UID mismatch between the container's user and the files on the volume
- A ConfigMap or Secret mounted by a key that is absent from the object
Network issues and dependency failures
A dependency that is not ready yet makes the same manifest work on redeploy and fail on a cold start:
- DNS resolution failing inside the pod, which
kubectl execandnslookupconfirm in a few seconds - An upstream service refusing connections because it has not finished starting itself
- A NetworkPolicy denying the egress the application needs
- An init container that times out waiting on a dependency, keeping the main container from ever running
Permissions and security context errors
A hardened securityContext blocks something the image needs, and the container exits 126 or 1 within a second or two:
runAsNonRoot: trueagainst an image whose default user is root- A
runAsUserUID that does not own the files it needs to write readOnlyRootFilesystem: trueunder an application that writes temporary files- A ServiceAccount without the RBAC the application needs to call the API server
Troubleshooting workflow, fixes, and prevention of CrashLoopBackOff
Step-by-step diagnostic workflow
Run these in order. Most crash loops resolve by step 3:
1. Confirm the status and the restart count.
kubectl get pods -n <namespace>2. Read the events and the exit code. describe covers this pod, get events covers everything around it.
kubectl describe pod <pod-name> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp3. Read the log of the crashed run.
kubectl logs <pod-name> -n <namespace> --previous4. Inspect the spec that produced it, checking environment variables, volumes, and probe timings.
kubectl get pod <pod-name> -o yaml5. Compare usage against the limits, using a replica that is still running. A pod already in backoff returns no metrics.
kubectl top pod -l app=<label> --containers6. Confirm every referenced object exists and is bound.
kubectl get configmap,secret,pvc -n <namespace>7. Isolate the container from the cluster by running the same image with a shell as its command. A container that also fails here has an image or entrypoint problem rather than a Kubernetes one.
kubectl run debug --rm -it --image=<same-image> --command -- /bin/sh8. Inspect the live container during the seconds it is up, or attach a debug container to a pod that never stays up long enough.
kubectl debug -it <pod-name> --image=busybox --target=<container-name>
Targeted fixes for common CrashLoopBackOff scenarios
Match the fix to the cause:
Using monitoring tools and alerts for CrashLoopBackOff detection
When you write alerting rules for crash loops, kubectl get pods gives you two columns you could watch. STATUS shows the words CrashLoopBackOff, and RESTARTS shows the count. Alert on the count.
The STATUS column only reads CrashLoopBackOff while the kubelet is waiting between restarts. A container that stays up long enough for Kubernetes to forget its earlier crashes never displays it at all, however many times it has restarted. The restart count only ever goes up.
kube-state-metrics publishes that count as kube_pod_container_status_restarts_total, one series per container, and one expression turns it into an alert:
increase(kube_pod_container_status_restarts_total[15m]) > 3A container that crashes once and recovers never reaches that threshold of three, so a single failure that fixes itself never triggers the alert. A container in a real crash loop reaches its third restart about seventy seconds after the first crash, given waits of 10, 20, and 40 seconds, so the 15-minute window is wide enough to catch it while someone can still act.
Once the rule is live, Alertmanager forwards what it fires to PagerDuty, Slack, or email.
When it does fire, check whether a deploy went out just before it. Datadog, New Relic, and Dynatrace draw that line for you automatically. Without them, compare the pod's first restart time against your deploy log, which is faster than reading code.
If you want software to react instead of a person, the Kubernetes Events API streams the same BackOff events that kubectl describe prints, and auto-rollback tooling watches that stream.
Preventing CrashLoopBackOff in production deployments
Most of these cost one line of YAML:
- Give every slow-starting application a startup probe, and keep liveness thresholds tight only after it passes
- Set requests and limits on every container, and enforce that with a
LimitRangeso nothing lands in BestEffort by accident - Validate manifests before they merge with
kubeval,kube-score, or Datree - Handle SIGTERM in application code so shutdown finishes inside the 30-second grace period
- Pin images to digests, and deploy through Helm or GitOps so the running spec matches the repository
- Alert on restart count, not on the CrashLoopBackOff status
A LimitRange fills in resource defaults when a manifest omits them, and an admission policy rejects an image that arrives without a digest, so Kubernetes enforces parts of what's on this list on its own. The rest stays your job, because you'll have to write your own SIGTERM handler and configure your alerts.
None of it removes crash loops entirely. When the next one happens, read the exit code, then read the log of the run that crashed.
Frequently asked questions
What is a CrashLoopBackOff in Kubernetes?
A pod status meaning the container started, exited, and the kubelet is waiting before restarting it. The wait doubles from 10 seconds up to a 5-minute cap.
How do you fix a CrashLoopBackOff error?
Run kubectl describe pod for the exit code, then kubectl logs --previous for the crashed run. The code names the category, the log names the failure.
What happens when a pod crashes in Kubernetes?
The kubelet restarts the container according to the pod's restartPolicy, which is Always under any Deployment. Repeated crashes add a growing delay between attempts.
Can you fix CrashLoopBackOff by moving the pod to another node?
No. The scheduler placed the pod once and is finished, and the kubelet restarts the container in place. A container missing a Secret exits the same way on every node.
What's the difference between CrashLoopBackOff and ImagePullBackOff?
ImagePullBackOff means the image never downloaded, so nothing started, usually a bad tag or missing credentials. CrashLoopBackOff means the image pulled fine and the container then exited.
How do you gracefully shut down a pod to avoid crash loops?
Handle SIGTERM, finish in-flight work, and exit 0 inside the 30-second grace period. A process that ignores SIGTERM gets SIGKILL and exits 137, which reads as a crash.