AKS CrashLoopBackOff Explained and Fixed
CrashLoopBackOff is one of the most common states you will see when deploying workloads to Azure Kubernetes Service. It means a container is failing repeatedly on startup. The root cause can be anything from a missing environment variable to an application bug to an out-of-memory kill. This guide provides a systematic diagnostic process using kubectl and AKS-specific tools to identify and fix the underlying cause.
Step 1: Identify the crashing pods
# List all pods in a namespace and show their status
kubectl get pods -n myapp
# Example output showing CrashLoopBackOff
# NAME READY STATUS RESTARTS AGE
# api-deployment-7d4b9c-xkpq2 0/1 CrashLoopBackOff 5 8m
# api-deployment-7d4b9c-jlmn3 1/1 Running 0 8m
# Get a broader view across all namespaces
kubectl get pods --all-namespaces --field-selector=status.phase!=Running
# Check restart counts over time
kubectl get pods -n myapp -o wide --show-labelsNote the RESTARTS count. A pod that has restarted 5+ times has been crashing repeatedly. Also note the AGE — a pod that has been crashing for a long time has likely been seen by the team already, while a fresh crash may be related to a recent deployment.
Step 2: Read the previous crash logs
Because the container crashes and is restarted, the logs from the crash are in the previous container instance, not the current one. Use —previous to read them.
# Read logs from the previous (crashed) container instance
kubectl logs -n myapp api-deployment-7d4b9c-xkpq2 --previous
# If there are multiple containers in the pod, specify the container name
kubectl logs -n myapp api-deployment-7d4b9c-xkpq2 --previous -c api-container
# Follow current logs (useful if the container is briefly running before crashing)
kubectl logs -n myapp api-deployment-7d4b9c-xkpq2 -f
# Get the last 100 lines of logs
kubectl logs -n myapp api-deployment-7d4b9c-xkpq2 --previous --tail=100Common things to look for in crash logs: an unhandled exception or panic, a “port already in use” message, a “cannot connect to database” error, a “required environment variable not set” error, or simply an application printing its help text and exiting (which indicates the wrong command or arguments).
Step 3: Examine the pod description
kubectl describe pod provides the exit code, exit reason, resource usage, and the pod events — all of which are critical for diagnosis.
kubectl describe pod -n myapp api-deployment-7d4b9c-xkpq2Key sections to read in the output:
Containers:
api-container:
...
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error # <-- or OOMKilled, or Completed
Exit Code: 1 # <-- non-zero = error; 137 = OOMKilled; 1 = app error
Started: Thu, 19 Mar 2026 10:14:00 +0000
Finished: Thu, 19 Mar 2026 10:14:02 +0000 # <-- 2s runtime = likely startup error
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning BackOff 2m (x8 over 8m) kubelet Back-off restarting failed container
Warning Failed 8m kubelet Error: failed to create containerd task: ...Exit code 137 means OOMKilled (the process received SIGKILL from the kernel due to memory limit exceeded). Exit code 1 or any non-zero code other than 137 means the application itself exited with an error. A runtime of only 2 seconds means the container is crashing almost immediately — most likely a configuration problem rather than a runtime logic bug.
Step 4: Common root causes and fixes
Missing environment variable or secret: The application crashes immediately because a required config value is absent. The error in logs typically looks like “Required config ‘DATABASE_URL’ is missing” or a NullReferenceException at startup.
# Check which env vars the pod has
kubectl exec -n myapp api-deployment-7d4b9c-xkpq2 -- env | sort
# (If the container won't stay up, use the sleep override technique)
# Verify the secret or ConfigMap exists and has the expected keys
kubectl get secret my-app-secret -n myapp -o jsonpath='{.data}' | base64 -d
# Check if a secret is correctly mounted in the pod spec
kubectl get deployment -n myapp api-deployment -o jsonpath='{.spec.template.spec.containers[0].env}' | python3 -m json.toolOOMKilled — container exceeding memory limit:
# Increase the memory limit in the deployment
kubectl patch deployment api-deployment -n myapp --type='json' -p='[
{
"op": "replace",
"path": "/spec/template/spec/containers/0/resources/limits/memory",
"value": "512Mi"
}
]'
# Or edit the deployment directly
kubectl edit deployment api-deployment -n myappApplication failing to bind to the expected port: If another process already owns the port (or if the PORT environment variable does not match the containerPort in the spec), the application will exit immediately.
# Verify the containerPort in the deployment matches what the app listens on
kubectl get deployment api-deployment -n myapp -o jsonpath='{.spec.template.spec.containers[0].ports}'Liveness probe killing the container: If the liveness probe fires too early (before the application has fully started), Kubernetes will restart the pod before it is ready. Increase initialDelaySeconds or switch to a startupProbe.
# Correct liveness probe configuration with adequate startup delay
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30 # give the app time to start
periodSeconds: 10
failureThreshold: 3
startupProbe: # use startupProbe for slow-starting apps
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # allow up to 5 minutes for startup (30 * 10s)
periodSeconds: 10Image pull errors vs CrashLoopBackOff
Sometimes the pod status shows ImagePullBackOff or ErrImagePull rather than CrashLoopBackOff. These are distinct — the container is not crashing; Kubernetes cannot even pull the image.
# Check image pull errors
kubectl describe pod -n myapp api-deployment-7d4b9c-xkpq2 | grep -A5 "Events"
# Common fix: verify the imagePullSecret is present and valid in the namespace
kubectl get secret acr-secret -n myapp
# Create the ACR pull secret if missing
az acr credential show --name myRegistry
kubectl create secret docker-registry acr-secret \
--docker-server=myregistry.azurecr.io \
--docker-username=myRegistry \
--docker-password=PASSWORD \
--namespace myapp
# Better approach: attach ACR to AKS so no pull secret is needed
az aks update \
--resource-group myRG \
--name myAKSCluster \
--attach-acr myRegistryAdvanced: ephemeral debug containers
For crashes that are hard to reproduce or where you need to inspect the filesystem, use kubectl debug to attach an ephemeral container to the running (or failing) pod without modifying the pod spec:
# Attach an ephemeral debug container to inspect a crashing pod
kubectl debug -it -n myapp api-deployment-7d4b9c-xkpq2 \
--image=busybox \
--target=api-container \
--share-processes
# This opens a shell in a busybox container that shares the process namespace
# with the crashing container, allowing you to inspect files, network, and processesCommon mistakes
- Reading current logs instead of previous logs. When a container is in CrashLoopBackOff,
kubectl logs pod-nameshows the logs of the current (about-to-crash or just-started) container, which may be empty or minimal. The crash logs are in the previous container. Always use—previousto read the crash logs. - Setting memory limits too low for JVM or .NET applications. Java and .NET runtimes allocate heap memory based on available system memory if not constrained. In a container with a 256Mi memory limit, the JVM may try to allocate a 128Mi heap plus overhead, exceeding the limit. Set JVM heap explicitly with -Xmx and .NET heap with DOTNET_GCHeapHardLimit environment variable.
- Not considering timezone or locale issues at startup. Some applications fail on startup in Azure because they reference a timezone that is not present in the minimal container image. alpine-based images often lack timezone data. Add tzdata package to the container image or use a distro-full base image if your application references timezones at startup.
Summary
- CrashLoopBackOff means the container starts and immediately exits — always read kubectl logs —previous to see the crash output.
- Use kubectl describe pod to get the exit code (137 = OOMKilled, 1 = app error) and the events log for additional context.
- Check for missing environment variables, secrets not mounted, liveness probes firing too early, and OOMKills as the four most common causes.
- For containers that crash too fast to inspect, override the command to sleep and then kubectl exec in to investigate interactively.
- Use kubectl debug for ephemeral container inspection without modifying the pod spec.
Frequently asked questions
What does CrashLoopBackOff mean exactly?
CrashLoopBackOff means a container in the pod started, ran briefly, then exited with a non-zero exit code. Kubernetes tried to restart it, it crashed again, and Kubernetes is now backing off (waiting progressively longer intervals before each restart attempt — 10s, 20s, 40s, up to 5 minutes). The "BackOff" part means you can still read the logs from the previous crash attempt with kubectl logs --previous.
How is CrashLoopBackOff different from OOMKilled?
OOMKilled (Out Of Memory Killed) is a specific exit reason where the container exceeded its memory limit and the kernel killed it. It shows up in kubectl describe pod as a reason of OOMKilled in the Last State section. CrashLoopBackOff is the pod phase that describes the restart loop — OOMKilled is one possible reason a container is in that loop. Check kubectl describe pod to see the exit reason behind the crash.
Can I disable CrashLoopBackOff to debug a crashing container?
You cannot disable CrashLoopBackOff, but you can work around it for debugging. Override the container command in the pod spec to run a long sleep: command: ["/bin/sh", "-c", "sleep 3600"]. The container will not crash, giving you time to kubectl exec into it and investigate. Alternatively, use kubectl debug to create an ephemeral debug container attached to the failing pod.