How to Fix Kubernetes PVC Pending and Multi-Attach Errors (2026)

Nov 10, 2026 · 15 min · Ajay Kumar

This guide is for anyone staring at kubectl get pvc showing Pending, or a pod stuck in ContainerCreating with a FailedAttachVolume event about Multi-Attach. By the end you will be able to read the event, map it to one of eight causes, and fix it with the command for that cause. I checked every message string against the Kubernetes 1.37.2 source and the commands against current docs for the AWS EBS CSI driver v1.67.2, with EKS as the worked example because it generates the most of these questions; the Kubernetes side is identical on GKE, AKS or bare metal. You need kubectl access to the namespace, plus aws CLI access for the EBS-specific steps. If the objects themselves are unfamiliar, read the companion Kubernetes storage explainer first.

Step 1: Read the events before changing anything

Almost every storage failure explains itself in events, on the PVC, the pod, or both. Start here and do not edit YAML until you know which message you have.

# The claim: phase, class, and the Events section at the bottom
kubectl describe pvc data-pg-0 -n prod

# The pod that uses it: scheduling and attach/mount events
kubectl describe pod pg-0 -n prod

# Everything recent in the namespace, newest last
kubectl get events -n prod --sort-by=.lastTimestamp | tail -30

# What classes exist, and which one is the default
kubectl get storageclass

Illustrative get storageclass output for an EKS cluster with a default class (your names and ages will differ). If no row says (default), jump to Step 2.

NAME                   PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2                    kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  90d
gp3-retain (default)   ebs.csi.aws.com         Retain          WaitForFirstConsumer   true                   30d

The diagram maps the five messages you will see most often to their cause. The rest of the guide goes through each one, then covers stuck deletions and resizes.

PVC Pending or pod stuck attaching: match the event, then fix kubectl describe pvc / pod read the Events section first no storage class is set Cause: no default StorageClass Fix: annotate exactly one class as default Waiting for a volume to be created Cause: CSI driver missing or no IAM Fix: install add-on, attach the role waiting for first consumer Normal: no pod has been scheduled yet Fix: debug the pod's scheduling instead didn't match PV's node affinity Cause: disk lives in another zone Fix: add a node in the volume's zone Waiting for detach (Multi-Attach) Cause: RWO volume wanted on 2 nodes Fix: Recreate, or out-of-service taint Still stuck? PVC Terminating: find the pod still using it (kubernetes.io/pvc-protection finalizer). Resize ignored or forbidden: the StorageClass needs allowVolumeExpansion: true, and you can only grow.
Event strings are from the Kubernetes v1.37.2 PersistentVolume controller, scheduler and attach/detach controller source; the Multi-Attach wording changed to Waiting for detach in 1.37 (PR 138837).

Step 2: Fix "no persistent volumes available for this claim and no storage class is set"

This event means the PVC has no storageClassName, no class in the cluster is marked default, and no pre-created PV matches. The PersistentVolume controller emits exactly that string and then waits. It is the single most common Pending cause on new EKS clusters: from EKS 1.30, new clusters stopped marking gp2 as the default, a change Replicated hit in their installer tests, and EKS Auto Mode does not create any StorageClass for you.

Create a class and mark it default. Use WaitForFirstConsumer so the disk lands in the pod's zone (Step 5 explains why).

# gp3-default.yaml, for the self-managed EBS CSI driver or EKS add-on
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com            # EKS Auto Mode: ebs.csi.eks.amazonaws.com
parameters:
  type: gp3
  encrypted: "true"
reclaimPolicy: Delete                    # use Retain for data you cannot regenerate
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
kubectl apply -f gp3-default.yaml

# If another class is already default, unset it so only one remains
kubectl patch storageclass gp2 \
  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

You do not need to recreate the stuck PVC. Retroactive default StorageClass assignment, stable since 1.28, assigns the new default to existing claims that have no class set. The exception is a claim with storageClassName: "", which explicitly asks for no class and will only bind to a pre-created PV without a class. If a Helm chart renders the empty string, set its storage class value instead.

If you set two classes as default, Kubernetes uses the most recently created one, per the StorageClass docs. That works, but it is how you end up with half your volumes on the wrong tier.

Step 3: Fix "Waiting for a volume to be created either by the external provisioner"

The full event is Waiting for a volume to be created either by the external provisioner 'ebs.csi.aws.com' or manually by the system administrator, followed by a hint to check that the provisioner is running. The PersistentVolume controller has handed the claim to a CSI driver and nothing has answered. A few seconds of this is normal. Minutes means the driver is missing, crashing or unauthorised.

# Is the driver registered at all?
kubectl get csidrivers
# Expect a row named ebs.csi.aws.com (or efs.csi.aws.com, pd.csi.storage.gke.io, ...)

# Are the controller and node pods running?
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver

# What does the provisioner sidecar say?
kubectl logs -n kube-system deploy/ebs-csi-controller -c csi-provisioner --tail=50

If there is no ebs.csi.aws.com row, the driver is not installed. AWS recommends the managed add-on. Create the IAM role first, as the EKS EBS CSI page describes, then install the add-on with either IRSA or Pod Identity:

# Find a compatible add-on version for your cluster
aws eks describe-addon-versions --addon-name aws-ebs-csi-driver \
  --kubernetes-version 1.37 \
  --query 'addons[0].addonVersions[0].addonVersion' --output text

# Option A: IRSA (role trusts the cluster OIDC provider)
aws eks create-addon --cluster-name my-cluster \
  --addon-name aws-ebs-csi-driver \
  --service-account-role-arn arn:aws:iam::111122223333:role/AmazonEKS_EBS_CSI_DriverRole

# Option B: EKS Pod Identity (AWS's suggested option)
aws eks create-addon --cluster-name my-cluster \
  --addon-name aws-ebs-csi-driver \
  --pod-identity-associations serviceAccount=ebs-csi-controller-sa,roleArn=arn:aws:iam::111122223333:role/AmazonEKS_EBS_CSI_DriverRole

Attach AmazonEBSCSIDriverPolicyV2 (or a scoped copy) to that role. When permissions are missing, AWS documents that kubectl describe pvc shows failed to provision volume with StorageClass together with could not create volume in EC2: UnauthorizedOperation. That is a ProvisioningFailed warning, which tells you the driver is running and only IAM is wrong.

For a self-managed install outside EKS add-ons, pin the chart. Chart 2.67.2 ships driver v1.67.2; chart 2.68.0 appeared on 9 November 2026 and I would let it settle for a week.

helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
helm repo update
helm upgrade --install aws-ebs-csi-driver aws-ebs-csi-driver/aws-ebs-csi-driver \
  --namespace kube-system --version 2.67.2

Two more variants of this cause. On EKS Auto Mode, a class with provisioner: ebs.csi.aws.com waits forever, because Auto Mode only serves ebs.csi.eks.amazonaws.com. And EBS volumes cannot be mounted on Fargate pods at all, per the same AWS page, so a stateful pod scheduled to a Fargate profile will never get its disk.

Step 4: "waiting for first consumer to be created before binding" is not an error

With volumeBindingMode: WaitForFirstConsumer, the claim stays Pending until a pod that uses it is scheduled. The PersistentVolume controller emits waiting for first consumer to be created before binding while no pod references it, and waiting for pod <name> to be scheduled once a pod exists but has not been placed. Both are type Normal.

So the real question is why the pod is not scheduled. Look at the pod, not the claim:

kubectl get pod pg-0 -n prod -o wide
kubectl describe pod pg-0 -n prod | sed -n '/Events:/,$p'

If the pod events say Insufficient cpu, untolerated taints, or no nodes matching a selector, it is a scheduling problem, and the Pods stuck in Pending guide covers those. Storage only becomes the problem once the volume exists, which is Step 5. If the pod event says pod has unbound immediate PersistentVolumeClaims, the class uses Immediate binding and provisioning has failed; go back to Step 3.

Step 5: Fix "didn't match PersistentVolume's node affinity" (zone conflicts)

An EBS volume, a GCE PD or an Azure managed disk exists in exactly one zone, and its PV carries a nodeAffinity for that zone. If no schedulable node in that zone has room, the scheduler reports node(s) didn't match PersistentVolume's node affinity. Clusters before 1.33 printed the older wording node(s) had volume node affinity conflict; same cause.

This happens when a volume was created with Immediate binding in a zone you have no nodes in, or when a StatefulSet pod's disk is in us-east-1a and the node group or Karpenter NodePool can now only launch in us-east-1b.

# Which zone is the volume pinned to?
PV=$(kubectl get pvc data-pg-0 -n prod -o jsonpath='{.spec.volumeName}')
kubectl get pv "$PV" -o jsonpath='{.spec.nodeAffinity.required.nodeSelectorTerms[*].matchExpressions[*]}{"\n"}'
# {"key":"topology.kubernetes.io/zone","operator":"In","values":["us-east-1a"]}

# Which zones do your nodes live in?
kubectl get nodes -L topology.kubernetes.io/zone

Fix it by giving the pod a node in the volume's zone: scale the zonal node group, or make sure the Karpenter NodePool's topology.kubernetes.io/zone requirement includes that zone (the Karpenter setup guide shows the field). Do not delete the PVC to "move" it; with a Delete reclaim policy that deletes the data. Moving data across zones means a snapshot and a restore into a new claim.

For new volumes, prevent it with WaitForFirstConsumer, which the EBS CSI parameters doc recommends whenever possible. If you must pin zones, the driver accepts topology.kubernetes.io/zone or topology.ebs.csi.aws.com/zone in allowedTopologies.

Step 6: Fix access mode and static PV mismatches

When the claim targets pre-created PVs (static provisioning), it binds only to a PV with the same class, enough capacity, and every requested access mode. A claim asking for ReadWriteMany will never bind to an RWO-only PV, and the claim sits Pending with a FailedBinding event.

The dynamic version of this mistake is asking a block storage driver for RWX. On EBS, the driver's multi-attach doc only supports ReadWriteMany for io1/io2 volumes in volumeMode: Block, so a filesystem RWX claim on gp3 fails provisioning. If several pods on different nodes need the same files, use a file backend (EFS, Filestore, Azure Files, CephFS). If one pod needs exclusive access, use ReadWriteOncePod, stable since 1.29.

# Compare what the claim asks for with what the PVs offer
kubectl get pvc data-pg-0 -n prod -o jsonpath='{.spec.accessModes} {.spec.resources.requests.storage} {.spec.storageClassName}{"\n"}'
kubectl get pv -o custom-columns=NAME:.metadata.name,MODES:.spec.accessModes,SIZE:.spec.capacity.storage,CLASS:.spec.storageClassName,STATUS:.status.phase

Most of a PVC spec is immutable. The API server rejects edits with spec is immutable after creation except resources.requests and volumeAttributesClassName for bound claims, so changing access mode or class means a new claim.

Step 7: Fix "Waiting for detach" and "Multi-Attach error for volume"

This is the attach/detach controller refusing to attach an RWO or RWOP volume to a second node while it is still attached to the first. Until Kubernetes 1.36 the FailedAttachVolume warning read Multi-Attach error for volume "pvc-..." Volume is already exclusively attached to one node and can't be attached to another, or ... Volume is already used by pod(s) <names>. From 1.37 the prefix is Waiting for detach for volume after PR 138837; the "already used by" suffixes are unchanged, and the exclusive-attach one now reads Volume is already exclusively attached to one node, waiting on detach before it can be attached to another node. Update any alert that matches the old text.

There are three common causes.

A Deployment with an RWO claim. Two replicas, or a rolling update that starts the new pod before the old one stops, both want the disk on different nodes. The "already used by pod(s)" variant names the other pod. For a single-writer app, use strategy: type: Recreate so the old pod stops first, or convert it to a StatefulSet with one claim per replica.

# Deployment that owns one RWO volume: stop before start
spec:
  replicas: 1
  strategy:
    type: Recreate

A node that died with the volume attached. The controller waits for the volume to be unmounted. If the pod's deletion has not succeeded for six minutes and the node is unhealthy, it force-detaches; the EBS driver's FAQ calls this the six-minute attach delay. If you have confirmed the node is really powered off, you can skip the wait with the non-graceful shutdown taint, stable since 1.28:

# Only after confirming the instance is stopped or terminated, not rebooting
kubectl taint nodes ip-10-0-1-23.ec2.internal \
  node.kubernetes.io/out-of-service=nodeshutdown:NoExecute

# Remove it yourself once pods have moved and the node is recovered or deleted
kubectl taint nodes ip-10-0-1-23.ec2.internal node.kubernetes.io/out-of-service-

A stale VolumeAttachment. The CSI attacher records each attachment as a cluster-scoped VolumeAttachment. Compare it with what the cloud thinks:

kubectl get volumeattachment \
  -o custom-columns=NAME:.metadata.name,PV:.spec.source.persistentVolumeName,NODE:.spec.nodeName,ATTACHED:.status.attached

VOL=$(kubectl get pv "$PV" -o jsonpath='{.spec.csi.volumeHandle}')
aws ec2 describe-volumes --volume-ids "$VOL" \
  --query 'Volumes[0].{State:State,AZ:AvailabilityZone,Attached:Attachments[*].[InstanceId,State]}'

If the attachment points at a node that no longer exists and EC2 shows the volume available, deleting that VolumeAttachment lets the controller reconcile. If EC2 still shows it attached to a live instance, find out why before forcing anything; two writers on one ext4 filesystem is how you get the corruption this check exists to stop. For planned node churn, graceful shutdown settings and Karpenter v1's wait-for-detach behaviour, described in the same FAQ, avoid most of these.

Step 8: Fix a PVC stuck Terminating or a resize that will not happen

Stuck Terminating. A deleted claim that a pod still uses keeps the kubernetes.io/pvc-protection finalizer until no pod references it, as the storage object in use protection docs describe. That includes Completed Job pods and pods on dead nodes. Find them rather than stripping the finalizer:

kubectl get pods -n prod -o json | jq -r --arg c data-pg-0 \
  '.items[] | select(any(.spec.volumes[]?; .persistentVolumeClaim.claimName == $c)) | "\(.metadata.name) \(.status.phase)"'

On 1.37, where the PersistentVolumeClaimUnusedSinceTime gate is beta, the claim also carries an Unused condition:

kubectl get pvc data-pg-0 -n prod -o jsonpath='{.status.conditions[?(@.type=="Unused")]}{"\n"}'

Resize rejected. only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize means the class lacks allowVolumeExpansion: true. You can patch that field on the class, then retry. You can only grow a claim, never shrink it.

kubectl patch storageclass gp3 -p '{"allowVolumeExpansion":true}'
kubectl patch pvc data-pg-0 -n prod -p '{"spec":{"resources":{"requests":{"storage":"80Gi"}}}}'
kubectl get pvc data-pg-0 -n prod -o jsonpath='{.status.conditions[*].type} {.status.allocatedResourceStatuses}{"\n"}'

A FileSystemResizePending condition means the disk grew and the filesystem will follow on the node. A ControllerResizeError condition, or ControllerResizeInfeasible in allocatedResourceStatuses, means the backend refused. Since 1.34 you can recover by requesting a smaller size than the failed one, as long as it is still above .status.capacity. On EBS, AWS now allows up to four modifications per volume in a rolling 24 hours, down from a six-hour cooldown, so repeated resizes in one afternoon can still queue.

Troubleshooting

Symptom Likely cause Fix
no persistent volumes available for this claim and no storage class is set No default StorageClass (EKS 1.30+, Auto Mode) Create a class, annotate is-default-class: "true"
Waiting for a volume to be created either by the external provisioner for minutes CSI driver not installed or crashing kubectl get csidrivers; install aws-ebs-csi-driver
could not create volume in EC2: UnauthorizedOperation Driver role lacks permissions Attach AmazonEBSCSIDriverPolicyV2 via IRSA or Pod Identity
PVC Pending forever on EKS Auto Mode Class uses ebs.csi.aws.com Use provisioner: ebs.csi.eks.amazonaws.com
waiting for first consumer to be created before binding Normal for WaitForFirstConsumer Debug the pod's scheduling
didn't match PersistentVolume's node affinity Disk in a zone with no usable node Add capacity in that zone; use WaitForFirstConsumer
FailedBinding with static PVs Class, size or access mode mismatch Match storageClassName, size and modes, or provision dynamically
Waiting for detach / Multi-Attach error naming another pod RWO claim on a multi-replica or rolling Deployment strategy: Recreate or a StatefulSet
Waiting for detach after a node died Volume not cleanly unmounted Wait 6 min, or add the out-of-service taint once the node is off
PVC Terminating for hours A pod still references it Delete the pod; leave the finalizer alone
only dynamically provisioned pvc can be resized allowVolumeExpansion not set Patch the class, then the PVC size

FAQ

Why is my PVC stuck in Pending on a new EKS cluster?

Usually because nothing is marked default. EKS stopped making gp2 the default on clusters created at 1.30 or later, and Auto Mode creates no class at all. Create one, annotate it as default, and check that the EBS CSI driver or Auto Mode is installed.

Is it safe to remove the pvc-protection finalizer?

Rarely. The finalizer exists so the disk is not deleted while a pod still has it mounted. Find and remove the pod that references the claim instead; the finalizer clears itself once no pod uses it.

What does "Multi-Attach error for volume" mean?

A ReadWriteOnce or ReadWriteOncePod volume is still attached to one node while a pod on another node wants it. On Kubernetes 1.37 the same event is worded "Waiting for detach for volume". Either a second pod is holding the disk, or a dead node has not released it yet.

How long does Kubernetes wait before force-detaching a volume?

Six minutes. If a pod's deletion has not succeeded in that time and the node is unhealthy, the attach/detach controller force-detaches. The --disable-force-detach-on-timeout controller-manager flag turns that off, in which case you recover with the out-of-service taint.

When a stateful pod will not start, the first thing I check is whether its disk and its node are in the same zone; it is the cause that looks the most like something else. The second is how many classes claim to be default. In production I would alert on PVCs Pending for more than five minutes and on FailedAttachVolume events by reason, not by message text, so a rename like the one in 1.37 cannot silently break the alert. The references worth bookmarking are the Kubernetes persistent volumes and StorageClass pages, AWS's EBS CSI setup, and the driver's FAQ.

Related: How to Fix Kubernetes Pods Stuck in Pending: Causes and Commands (2026), How to Set Up Karpenter on EKS: NodePools, Spot and Consolidation (2026) and How to Back Up and Restore Kubernetes with Velero 1.18 (2026).

Spot an error or something out of date? Email me and I will fix it and log it on the corrections page.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related