# PersistentVolumeClaim Stuck Pending — How to Fix It

URL: https://www.devopsness.com/blog/fix-pvc-pending
Author: Kiril Urbonas
Published: 2026-07-13
Updated: 2026-07-13
Categories: DevOps
Tags: Kubernetes, Storage, DevOps, Debugging, Operations

Summary: A PVC stuck in Pending means nothing on the cluster can hand it a volume. Here's how we read the events and get it bound fast.

---
# PersistentVolumeClaim Stuck Pending — How to Fix It

A PVC sits in `Pending` for exactly one reason: nothing on the cluster has handed it a volume yet. Either no existing PersistentVolume matches the claim, or the dynamic provisioner that was supposed to create one hasn't. The pod that needs the storage will sit in `Pending` right alongside it, and you'll get paged because a deploy never came up.

The good news: the PVC tells you why, if you ask it. The bad news is the message is buried in the events, and half the teams I work with never run the one command that surfaces it.

## What Pending actually means

When you create a PVC, the control plane tries to satisfy it. With static provisioning it looks for a pre-created PV whose capacity, access mode, and storage class match. With dynamic provisioning it calls the CSI driver named by the StorageClass and asks it to cut a fresh volume. `Pending` means neither path finished. It is not a transient "wait a bit" state most of the time. It's a mismatch or a missing piece, and it will stay Pending forever until you change something.

## Start with describe, always

Skip `kubectl get`. It only tells you it's Pending, which you already know. Go straight to the events:

```
kubectl describe pvc data-postgres-0 -n prod
```

The bottom of that output is where the answer lives:

```
Name:          data-postgres-0
Namespace:     prod
StorageClass:  fast-ssd
Status:        Pending
Volume:
Access Modes:
Events:
  Type     Reason              Age                From                         Message
  ----     ------              ----               ----                         -------
  Warning  ProvisioningFailed  12s (x8 over 2m)   persistentvolume-controller  storageclass.storage.k8s.io "fast-ssd" not found
```

There it is. The claim asked for a StorageClass called `fast-ssd` and no such class exists on this cluster. Every root cause below shows up as a distinct event line, so read them before you touch anything.

Then list what you actually have:

```
kubectl get storageclass
```

```
NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      DEFAULT
gp3 (default)        ebs.csi.aws.com         Delete          WaitForFirstConsumer  true
standard             ebs.csi.aws.com         Delete          Immediate             false
```

## The causes, and how each one reads

**No matching StorageClass, or no default.** This is the `"fast-ssd" not found` case above. Either the class name in the PVC is wrong, or you left `storageClassName` blank expecting a default and no class is marked default. Check the `DEFAULT` column. If nothing says `(default)`, a class-less PVC has nowhere to go. Fix it by pointing the PVC at a real class, or annotate one class as default:

```
kubectl annotate storageclass gp3 \
  storageclass.kubernetes.io/is-default-class=true
```

**The CSI driver isn't installed or is failing.** The class exists but provisioning still fails with something like `failed to provision volume with StorageClass "gp3": rpc error ... driver name ebs.csi.aws.com not found`. That means the controller can't reach the CSI driver. Check the driver pods:

```
kubectl get pods -n kube-system -l app=ebs-csi-controller
kubectl get csidrivers
```

If the controller is crash-looping, the usual culprit is IAM: the driver's service account can't create volumes in the cloud. Read the controller logs, fix the role, and the queued PVCs provision on their own.

**Requested size or access mode isn't available.** With static PVs, `no persistent volumes available for this claim and no storage class is set` means you're relying on hand-made PVs and none is big enough or free. The other classic is access mode. A block store like EBS or a plain GCP PD is `ReadWriteOnce` only. Ask for `ReadWriteMany` on it and no PV will ever match:

```
Warning  ProvisioningFailed  ...  invalid AccessModes [ReadWriteMany]: only ReadWriteOnce supported
```

RWX needs a file backend: EFS, Filestore, or an NFS-backed class. Don't try to bend a block volume into shared access. Pick the right backend.

**WaitForFirstConsumer, no consumer.** Look back at that StorageClass table. `gp3` uses `WaitForFirstConsumer`. That binding mode is deliberate: the volume isn't provisioned until a pod that mounts the PVC gets scheduled, so the volume lands in the same zone as the pod. If you create a bare PVC and stare at it, it will stay Pending with:

```
Normal  WaitForFirstConsumer  ...  waiting for first consumer to be created before binding
```

This is not a bug. It's Pending-by-design. Create the pod that uses it. If the pod itself won't schedule (no capacity, taints, unsatisfiable node selector), the PVC waits on the pod, so go debug the pod's scheduling first.

**Zone mismatch.** With multi-zone clusters and `Immediate` binding, a volume can get provisioned in zone `us-east-1a` while the only node that can run the pod is in `1b`. EBS volumes don't cross zones, so the pod stays Pending with `volume node affinity conflict`. This is the exact problem `WaitForFirstConsumer` was built to prevent, which is why I default every block-storage class to it.

**Quota.** A `ResourceQuota` on the namespace capping `requests.storage` or `persistentvolumeclaims` will block new claims with a quota-exceeded event. Run `kubectl describe resourcequota -n prod` and either raise the cap or clean up abandoned PVCs.

## A worked example

Here's a PVC and a class that bind cleanly on AWS:

```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-postgres-0
  namespace: prod
spec:
  storageClassName: gp3
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 50Gi
```

The `data-postgres-0` failure at the top was just `storageClassName: fast-ssd` where the cluster only had `gp3`. Changing that one line and re-applying cleared it. The PVC bound the moment the StatefulSet pod scheduled.

If you want a broader map of cluster failure modes, our [Kubernetes troubleshooting](/blog/kubernetes-error-troubleshooting-guide) guide covers the neighboring ones.

## The call we'd make

Run `describe` first, every time, and read the events before you change a single YAML line. Nine times out of ten the message names the exact problem, and the tenth is a CSI driver or IAM issue that the driver's own logs will hand you. Default your block-storage classes to `WaitForFirstConsumer` so zone mismatches stop happening, mark exactly one StorageClass as default, and stop reaching for `ReadWriteMany` on volumes that were never built to share.

