Kubernetes
Helm
Troubleshooting
Ingress
Nginx

ingress-nginx-controller service is still not removed after uninstalling chart

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When helm uninstall finishes but the ingress-nginx-controller Service still exists, the problem is usually not Helm itself. In most cases the Service was recreated outside the release, is stuck behind a finalizer, or is waiting on cloud load balancer cleanup that has not completed yet.

What Helm Actually Deletes

Helm removes the resources that belong to the release manifest it has recorded. If the Service was changed after install, recreated manually, or created by another controller, Helm may no longer be the owner that can clean it up.

Start by confirming whether the release is actually gone.

bash
helm list -n ingress-nginx
helm status ingress-nginx -n ingress-nginx

If the release no longer exists, inspect the Service directly.

bash
kubectl get svc ingress-nginx-controller -n ingress-nginx -o yaml

Look for these details:

  • 'metadata.ownerReferences'
  • 'metadata.finalizers'
  • 'metadata.labels'
  • 'metadata.annotations'

If there is no owner reference back to the Helm-managed workload, the object may have drifted away from the original release.

LoadBalancer Cleanup Is a Common Reason

The most common leftover object is a Service of type LoadBalancer. Deleting that Service asks the cloud controller to tear down the external load balancer, public IP, forwarding rules, and similar infrastructure. Until that cleanup is done, Kubernetes may keep a finalizer on the Service.

You can see this with:

bash
kubectl describe svc ingress-nginx-controller -n ingress-nginx

If the output shows a finalizer and the object has a deletionTimestamp, deletion has started but has not completed. That usually means one of these conditions applies:

  • the cloud controller manager is not running
  • cloud credentials are broken
  • the load balancer resource was modified manually in the cloud console
  • the cluster has already lost the controller that knows how to finish cleanup

In that state Helm is done, but Kubernetes is still waiting for infrastructure cleanup.

Verify Whether the Service Was Recreated

Sometimes the chart was uninstalled correctly and then another process recreated the Service. This happens with GitOps systems, leftover manifests, or another Helm release using the same name.

Check for matching resources and automation:

bash
kubectl get all -n ingress-nginx
kubectl get svc ingress-nginx-controller -n ingress-nginx -o jsonpath='{.metadata.labels}'
kubectl get svc ingress-nginx-controller -n ingress-nginx -o jsonpath='{.metadata.annotations}'

If you see labels from Argo CD, Flux, or another release, the object is being managed elsewhere. In that case deleting it manually will only make it come back.

Safe Removal Workflow

A practical cleanup flow looks like this:

bash
helm uninstall ingress-nginx -n ingress-nginx
kubectl get svc -n ingress-nginx
kubectl describe svc ingress-nginx-controller -n ingress-nginx

If the Service still exists and is not already terminating, try a normal delete:

bash
kubectl delete svc ingress-nginx-controller -n ingress-nginx

If it is stuck because a finalizer can no longer complete, remove the finalizer only after you confirm the external load balancer is already gone or you are willing to clean cloud resources manually.

bash
1kubectl patch svc ingress-nginx-controller \
2  -n ingress-nginx \
3  --type=merge \
4  -p '{"metadata":{"finalizers":[]}}'

Use that last step carefully. It tells Kubernetes to forget the remaining controller cleanup work.

Check for Namespace and Hook Leftovers

Helm uninstalling a release does not delete the namespace unless you delete it yourself. Hook-created jobs, admission webhooks, or custom configuration created outside the main manifest can also remain.

These commands help with a broader audit:

bash
kubectl get all -n ingress-nginx
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations | grep ingress
kubectl get clusterrole,clusterrolebinding | grep ingress

That gives you a quick view of whether the leftover Service is part of a wider uninstall problem.

Common Pitfalls

  • Assuming helm uninstall deletes anything with a similar name, even if another tool recreated it.
  • Forgetting that LoadBalancer Services often depend on cloud-side cleanup before deletion completes.
  • Removing finalizers before checking whether cloud resources still exist.
  • Looking only at Helm output instead of inspecting the live Service object.
  • Ignoring GitOps or admission controllers that may recreate the Service after deletion.

Summary

  • Helm removes release-owned resources, not arbitrary objects with matching names.
  • A leftover ingress-nginx-controller Service is often stuck on cloud load balancer cleanup.
  • Inspect ownerReferences, labels, annotations, and finalizers before forcing deletion.
  • If another controller recreated the Service, fix that source first.
  • Remove finalizers only when you understand the external cleanup consequences.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.