Kubernetes Troubleshooting: CrashLoopBackOff, ImagePullBackOff, Pending, and Terminating

Learn how to troubleshoot CrashLoopBackOff, ImagePullBackOff, Pending, and Terminating Kubernetes Pods using kubectl, Events, logs, metrics, and debugging containers.

This post is also available in: Spanish

When an application fails in Kubernetes, seeing a Pod in CrashLoopBackOff, ImagePullBackOff, or Pending does not necessarily tell us what the actual problem is.

It tells us where to start investigating.

One of the most common mistakes during troubleshooting is trying to immediately fix the status shown by kubectl.

For example:

CrashLoopBackOff

is not the root cause.

It means that a container is repeatedly failing and Kubernetes is applying a progressive delay before trying to start it again.

The actual cause could be related to the application, configuration, resources, health checks, or an external dependency.

In this article, we are going to build a systematic process to investigate four of the most common Pod states:

  • CrashLoopBackOff

  • ImagePullBackOff

  • Pods stuck in Pending

  • Pods stuck in Terminating


Before you start: do not restart the Pod immediately

When an incident occurs, it can be tempting to run:

kubectl delete pod <pod>

and wait for Kubernetes to create a new one.

In some cases, this can temporarily restore the service.

But it can also remove useful evidence that could help us understand the root cause.

Before changing anything, review:

Pod State
|
+--- Events
|
+--- Logs
|
+--- Previous container state
|
+--- Resources
|
+--- Configuration

A good investigation starts by preserving evidence.


A basic troubleshooting flow

Before investigating a specific error, we can get a general view of the cluster.

kubectl get pods -A -o wide

This allows us to quickly identify:

  • namespace;

  • status;

  • restart count;

  • IP address;

  • node where the Pod is running;

  • Pod age.

Then we can review recent Events:

kubectl get events -A \
--sort-by='.lastTimestamp'

Events are particularly useful when Kubernetes is having problems with:

Scheduling
Image pulls
Volumes
Health checks
Nodes
Evictions

If we already know which Pod is affected:

kubectl describe pod <pod> -n <namespace>

kubectl describe combines much of the information we need for an initial investigation:

Containers
State
Last State
Restart Count
Conditions
Volumes
Node
Events

Then we can inspect the logs:

kubectl logs <pod> -n <namespace>

If the container has already restarted, there is another very important command:

kubectl logs <pod> \
-n <namespace> \
--previous

This retrieves logs from the previous instance of the container.


1. CrashLoopBackOff

Suppose we find:

NAME READY STATUS RESTARTS
api-xyz 0/1 CrashLoopBackOff 7

CrashLoopBackOff means that the container is entering a cycle similar to this:

Container starts
|
v
Container fails
|
v
Kubernetes restarts it
|
v
Container fails again
|
v
Backoff
|
+----------------+
|
v
Retry again

Kubernetes progressively increases the time between restart attempts to avoid continuously restarting a container that keeps failing.

The important question is:

Why is the process inside the container terminating?


Step 1: inspect the Pod

kubectl describe pod api-xyz -n production

Pay particular attention to:

State:
Last State:
Reason:
Exit Code:
Restart Count:
Events:

We might find something like:

Last State: Terminated
Reason: Error
Exit Code: 1

This tells us that the process exited with an error.

But we could also find:

Reason: OOMKilled
Exit Code: 137

In that case, the investigation changes completely and should focus on memory usage.


Step 2: inspect previous logs

Start with:

kubectl logs api-xyz -n production

But if the container restarts quickly:

kubectl logs api-xyz \
-n production \
--previous

This command often contains the most useful clue.

For example:

ERROR: DATABASE_URL environment variable is missing

Now we have a much more specific hypothesis.


Step 3: inspect configuration

We can inspect the complete Pod specification:

kubectl get pod api-xyz \
-n production \
-o yaml

And if it belongs to a Deployment:

kubectl get deployment api \
-n production \
-o yaml

Check:

environment variables
Secrets
ConfigMaps
command
args
resources
volume mounts
probes
image

An incorrect configuration may allow the container to be created successfully but cause the application to fail immediately after startup.


Step 4: inspect health checks

We may also find Events such as:

Liveness probe failed

or:

Startup probe failed

In this case, review:

livenessProbe:
startupProbe:
readinessProbe:

An application that needs 40 seconds to start can end up in a restart loop if a health check starts too early.


Common causes of CrashLoopBackOff

CrashLoopBackOff
|
+--- Application crash
|
+--- Missing environment variable
|
+--- Invalid Secret / ConfigMap
|
+--- Dependency unavailable
|
+--- OOMKilled
|
+--- Liveness probe
|
+--- Startup probe
|
+--- Invalid command / arguments

This is why CrashLoopBackOff should be treated as a symptom, not the root cause.


2. ImagePullBackOff

Now suppose we find:

NAME READY STATUS RESTARTS
api-abc 0/1 ImagePullBackOff 0

In this case, the container has not started running our application yet.

The failure happens earlier:

Pod
|
v
kubelet
|
v
Container runtime
|
v
Registry
|
+--- Image
+--- Authentication
+--- DNS
+--- Network
+--- TLS

Again, start with:

kubectl describe pod api-abc \
-n production

The Events may show errors such as:

manifest unknown
pull access denied
unauthorized
x509: certificate signed by unknown authority

or:

no such host

Each error points us in a different direction.


Confirm the image

kubectl get pod api-abc \
-n production \
-o jsonpath='{.spec.containers[*].image}'

For example:

registry.example.com/platform/api:2.4.1

Confirm that the following are correct:

  • registry;

  • repository;

  • image name;

  • tag.

A simple typo can be enough to cause ImagePullBackOff.


Private registries

If we use a private registry, inspect available Secrets:

kubectl get secrets -n production

And check which imagePullSecrets the Pod is using:

kubectl get pod api-abc \
-n production \
-o jsonpath='{.spec.imagePullSecrets}'

There is also an important point to remember:

Just because your laptop can run docker pull does not mean that the Kubernetes node can do the same.

The kubelet and container runtime on the node need access to the registry.

That means the issue could also involve:

DNS
Routing
Firewall
Proxy
TLS
Registry availability

3. Pods stuck in Pending

Another common scenario:

NAME READY STATUS RESTARTS
api-123 0/1 Pending 0

With Pending, the first question is:

Was the scheduler able to assign the Pod to a node?

The fastest way to find out is:

kubectl describe pod api-123 \
-n production

We might find:

Warning FailedScheduling

followed by something like:

0/3 nodes are available:
2 Insufficient cpu,
1 node(s) had untolerated taint

This information is much more useful than only seeing STATUS=Pending.


Insufficient resources

Check the Pod resource requests:

kubectl get pod api-123 \
-n production \
-o yaml

For example:

resources:
requests:
cpu: "2"
memory: "4Gi"

Then inspect the nodes:

kubectl describe nodes

and check current metrics:

kubectl top nodes

But there is an important difference to understand.

The scheduler primarily makes placement decisions using resource requests, not simply the real-time consumption shown by kubectl top.

We could have:

Actual Node CPU: 30%
Requested CPU: 95%

and new Pods may still be unable to schedule because there is not enough unallocated requested capacity.


Other FailedScheduling causes

CPU and memory are not the only possible causes.

We should also investigate:

nodeSelector
nodeAffinity
podAffinity
podAntiAffinity
taints
tolerations
topologySpreadConstraints
PVC
ResourceQuota

For example:

kubectl get nodes --show-labels

and:

kubectl describe node <node>

can help identify problems related to labels and taints.


4. Pods stuck in Terminating

Now suppose we find:

NAME READY STATUS AGE
api-456 1/1 Terminating 2d

Pods do not disappear instantly when we delete them.

Kubernetes allows them to perform a graceful shutdown before removing them.

Start with:

kubectl describe pod api-456 \
-n production

and:

kubectl get pod api-456 \
-n production \
-o yaml

Look specifically for:

deletionTimestamp:
finalizers:
terminationGracePeriodSeconds:

Common causes

A Pod can remain in Terminating because of:

Application not handling SIGTERM
|
preStop hook blocked
|
Volume cannot unmount
|
CSI problem
|
Finalizer
|
Node unavailable

We can identify the node with:

kubectl get pod api-456 \
-n production \
-o wide

Then check it:

kubectl get node <node>

If the Pod uses volumes:

kubectl describe pod api-456 \
-n production

and:

kubectl get pvc -n production

can help us identify storage-related issues.


What about force deletion?

We can run:

kubectl delete pod api-456 \
-n production \
--grace-period=0 \
--force

But this should be a deliberate action, not the first troubleshooting step.

Force deleting a Pod removes its Kubernetes API representation without necessarily guaranteeing that every underlying process or resource has already been terminated cleanly.

This requires even more caution with stateful workloads.

The first objective should be understanding why the Pod is not terminating normally.


When kubectl exec is not enough

Many modern container images are intentionally minimal or even distroless.

You may try:

kubectl exec -it api-xyz -- sh

and discover that the container does not include:

sh
curl
dig
tcpdump
ps

That does not mean we should permanently install debugging tools inside our production images.

Instead, we can use an ephemeral debugging container:

kubectl debug api-xyz \
-n production \
-it \
--image=ubuntu

kubectl debug allows us to add troubleshooting tools without modifying the original application image.

It can also be used to investigate nodes:

kubectl debug node/worker-01 \
-it \
--image=ubuntu

This creates a debugging environment that can help us inspect the node.


Metrics, Logs, and Events answer different questions

One of the most important parts of Kubernetes troubleshooting is understanding that a single source of information is usually not enough.

Problem
|
+-----------+-----------+
| | |
Events Metrics Logs
| | |
v v v
What did K8s? How much? What happened?

Events

Useful for:

FailedScheduling
FailedMount
FailedAttachVolume
FailedPull
BackOff
Unhealthy
Evicted

Command:

kubectl get events -A \
--sort-by='.lastTimestamp'

Metrics

Useful for:

CPU
Memory
Node utilization
Container utilization

For example:

kubectl top pods -A
kubectl top nodes

In environments with Prometheus, we can also inspect historical behavior and correlate incidents with changes in traffic or resource utilization.

Logs

Useful for understanding what happened inside the application:

kubectl logs <pod>

or:

kubectl logs <pod> --previous

In production, these logs should usually also be centralized using platforms such as Loki, OpenSearch, Elasticsearch, Splunk, Datadog, or similar solutions.


A troubleshooting process we can reuse

For these and many other Kubernetes problems, we can use the same general flow:

Symptom
|
v
Define blast radius
|
v
Check Pod state
|
v
Events
|
v
Logs / Metrics
|
v
Configuration
|
v
Dependencies / Infrastructure
|
v
Root cause
|
v
Fix
|
v
Validate
|
v
Prevent

Before changing anything, try to answer:

What is failing?
When did it start?
What changed?
Does it affect one Pod or several?
Does it affect one specific node?
What do the Events say?
What do the logs show?
What do the metrics show?

This helps reduce trial-and-error troubleshooting.


Command reference

# Pods
kubectl get pods -A -o wide
# Details and Events
kubectl describe pod <pod> -n <namespace>
# Logs
kubectl logs <pod> -n <namespace>
kubectl logs <pod> -n <namespace> --previous
# Events
kubectl get events -A --sort-by='.lastTimestamp'
# Resources
kubectl top pods -A
kubectl top nodes
# Configuration
kubectl get pod <pod> -n <namespace> -o yaml
kubectl get deployment <deployment> -n <namespace> -o yaml
# Nodes
kubectl get nodes -o wide
kubectl describe node <node>
# Debug container
kubectl debug <pod> -n <namespace> -it --image=ubuntu
# Node debugging
kubectl debug node/<node> -it --image=ubuntu

Conclusion

States such as CrashLoopBackOff, ImagePullBackOff, Pending, and Terminating are starting points for troubleshooting, not complete diagnoses.

The key is combining different signals:

Kubernetes State
+
Events
+
Logs
+
Metrics
|
v
Root Cause

In the next article, we will continue with another common group of Kubernetes problems:

CPU, memory, OOMKilled, throttling, and Pod evictions.

We will look at why a Pod can experience performance problems even when the Kubernetes node appears to have available resources.

Comments