Fix Kubernetes Namespace Stuck in Terminating State (Error from server: namespaces is being deleted)

intermediate☸️ Kubernetes2026-03-18| Kubernetes 1.18+, kubectl, any cloud provider (EKS, GKE, AKS) or self-hosted cluster

Error Message

Error from server: namespaces "my-namespace" is being deleted
#kubernetes#namespace#terminating#finalizer#kubectl

The Problem

You run kubectl delete namespace my-namespace and the namespace just... hangs. Status shows Terminating. You wait. Ten minutes later, it's still there. When you try to interact with it:

Error from server: namespaces "my-namespace" is being deleted

You can't recreate it, can't deploy into it, can't touch it at all. Ask any Kubernetes admin and they've hit this β€” usually at the worst possible moment, like mid-deployment or right before a demo.

Why This Happens

Kubernetes uses finalizers to control deletion order. When you delete a namespace, Kubernetes stamps a deletion timestamp and starts cleaning up everything inside. The namespace won't actually disappear until every finalizer is satisfied.

Here's what typically gets in the way:

  • A custom resource (CRD) still exists, but its controller is gone β€” so the finalizer never runs
  • An admission webhook is unreachable and silently blocking the deletion
  • A persistent volume is stuck in a cleanup loop
  • A third-party operator (cert-manager, Istio, Prometheus Operator) installed finalizers that were never removed

Start by inspecting the namespace's finalizers directly:

kubectl get namespace my-namespace -o yaml

Look for the finalizers field under spec. A stuck namespace usually looks like this:

spec:
  finalizers:
  - kubernetes
  - some-operator.io/cleanup

Also scan for stuck resources inside the namespace:

kubectl api-resources --verbs=list --namespaced -o name \
  | xargs -I{} kubectl get {} -n my-namespace --ignore-not-found 2>/dev/null

Quick Fix: Remove the Finalizer via kubectl patch

Strip out the finalizers and Kubernetes will complete the deletion immediately. Try this first:

kubectl patch namespace my-namespace \
  -p '{"spec":{"finalizers":[]}}' \
  --type=merge

If the namespace is already deep in the deleting state, that command may fail. Use the API proxy method instead β€” it bypasses the issue entirely.

Method 2: API Proxy (most reliable)

Start the proxy in one terminal:

kubectl proxy &

In a second terminal, export the namespace spec to a file:

kubectl get namespace my-namespace -o json > /tmp/ns.json

Open /tmp/ns.json, find the spec block, and replace its contents with:

"spec": {
  "finalizers": []
}

Push the change through the finalize endpoint:

curl -s -o /dev/null -w "%{http_code}" \
  -X PUT \
  -H 'Content-Type: application/json' \
  --data-binary @/tmp/ns.json \
  http://127.0.0.1:8001/api/v1/namespaces/my-namespace/finalize

HTTP 200 means it worked. The namespace should disappear within 5–10 seconds.

Kill the proxy when done:

kill %1

Step-by-Step: Full Cleanup Workflow

Step 1 β€” Check what's actually stuck

# Namespace status and conditions
kubectl describe namespace my-namespace

# Recent events sorted by time
kubectl get events -n my-namespace --sort-by='.lastTimestamp'

# Everything running inside the namespace
kubectl get all -n my-namespace

Step 2 β€” Try to clean up resources manually first

Force-deleting the namespace is a last resort. Clean up stuck resources individually first β€” it's safer and avoids leaving orphaned objects behind.

# Force delete a stuck pod (skips the 30s grace period)
kubectl delete pod stuck-pod-name -n my-namespace --grace-period=0 --force

# Remove a finalizer from a specific resource
kubectl patch pod stuck-pod-name -n my-namespace \
  -p '{"metadata":{"finalizers":null}}' \
  --type=merge

Got custom resources? Find them first:

kubectl get crds | grep my-namespace

Patch their finalizers out the same way:

kubectl patch myresource resource-name -n my-namespace \
  -p '{"metadata":{"finalizers":[]}}' \
  --type=merge

Step 3 β€” Force remove the namespace finalizer

Once stuck resources are cleared β€” or if you can't clear them β€” use the API proxy method above to wipe the namespace-level finalizer.

Step 4 β€” Verify the fix

# Should return NotFound if fully deleted (that's the good outcome)
kubectl get namespace my-namespace

# Expected output:
# Error from server (NotFound): namespaces "my-namespace" not found

# Double-check it's gone from the list
kubectl get namespaces | grep my-namespace

Need to recreate it right away?

kubectl create namespace my-namespace

Preventing This in the Future

Nine times out of ten, a stuck namespace traces back to an operator that installed finalizers and was removed without cleaning them up. A few habits that prevent this:

  • Uninstall operators before deleting namespaces. For Helm-managed operators, helm uninstall handles cleanup automatically. For manual installs, check the operator docs for the teardown procedure.
  • Audit admission webhooks. Webhooks matching your namespace can silently block deletion. Run kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations and look for any that target the namespace you're deleting.
  • Add a pre-delete script to CI/CD pipelines. Before any namespace deletion in automation, run a script that patches out known finalizers. Catches the problem before it becomes an incident at 2am.
  • Delete CRD instances before removing the operator. Uninstalling cert-manager or Istio while their CRD objects still exist in a namespace leaves those objects orphaned with finalizers that can never be satisfied.

When Nothing Works

Still stuck? Check whether a validating webhook is intercepting the finalize call:

kubectl get validatingwebhookconfigurations -o yaml | grep -A5 my-namespace

Delete the offending webhook temporarily, then retry the curl command from Method 2.

On managed clusters β€” EKS, GKE, AKS β€” some system webhooks get automatically reinstalled by the control plane. You can't safely remove those without breaking other things. If the webhook belongs to a cluster-managed component, open a support ticket with your cloud provider. It's the only clean path forward.

Related Error Notes