A namespace or object stuck in Terminating for hours, never disappearing, isn’t a Kubernetes bug: it’s a finalizer waiting for a controller to run a cleanup, a controller that, often, no longer exists or no longer responds. Understanding this mechanism avoids working around it blindly, an operation with a real cost if it skips a necessary cleanup step.

What a finalizer blocks, and why

A finalizer is a string set in an object’s metadata, preventing its final deletion until explicitly removed. kubectl delete on an object with a finalizer doesn’t delete it immediately: it marks the object as Terminating and waits for the controller that set the finalizer to run its cleanup, then remove it itself.

metadata:
  finalizers:
    - kubernetes.io/pv-protection
  deletionTimestamp: "2026-07-28T10:00:00Z"

The kubernetes.io/pv-protection finalizer, for instance, prevents a PersistentVolume from being finally deleted while a pod still uses it, a safeguard avoiding the deletion of a storage volume still attached to an active workload.

The classic scenario: the controller is gone

A finalizer set by an operator or a custom webhook assumes that same component stays operational to remove it at the right moment. If the operator was uninstalled, crashed permanently, or never correctly handled this case, the finalizer stays on the object indefinitely: nobody will ever remove it, and the object stays in Terminating forever, a state that looks like an API server hang while the actual cause lives elsewhere, in a missing controller.

# Identify which finalizer is actually blocking deletion
kubectl get namespace stuck-ns -o jsonpath='{.spec.finalizers}'

Diagnose before forcing anything

The first step is never removing the finalizer blindly, but understanding why it was never removed: does the associated controller still exist in the cluster, is it running, do its logs show an error at the moment of the cleanup attempt? A finalizer blocking because its controller hits a transient error (an unreachable external dependency) gets solved by fixing that cause, not by forcing deletion.

# Check whether the associated controller is still
# present and active before forcing anything
kubectl get pods -n kube-system | grep <operator-name>

Forcing removal, knowingly

When the controller is definitively gone (uninstalled without prior cleanup) and no fix is possible, manually removing the finalizer unblocks the object, but deliberately skips the cleanup that finalizer was meant to guarantee.

# Forces removal of the blocking finalizer, skipping
# the cleanup it was meant to guarantee
kubectl patch namespace stuck-ns --type=merge \
  -p '{"spec":{"finalizers":[]}}' --subresource=finalize

This command is never a first reflex: it’s justified only after confirming no controller can still perform the expected cleanup, and explicitly accepting that this cleanup (sometimes an external cloud resource, a DNS record, a certificate) will never happen.

Takeaway

A Kubernetes object stuck in Terminating is waiting for a controller to remove a finalizer after running a cleanup, not a hang in the API server itself. The most common scenario is a gone or permanently failing controller, one that will never remove the finalizer it set. Diagnosing the real cause (does the controller exist, is it working) always precedes forcing finalizer removal, an operation that deliberately skips a cleanup, to reserve for cases where that cleanup isn’t possible anyway, one of the troubleshooting situations that comes up the moment a Kubernetes migration uninstalls an operator without having anticipated this case.