🚀 Executive Summary

TL;DR: The `kubernetes.io/ingress.class` annotation is being deprecated by Kubernetes, with ingress-nginx retiring its support by March 2026, necessitating migration to the `spec.ingressClassName` field. This article provides a practical playbook with options ranging from manual updates to GitOps with policy enforcement or controller-level overrides to ensure a smooth transition and avoid production outages.

🎯 Key Takeaways

  • The `kubernetes.io/ingress.class` annotation is deprecated in favor of the structured `IngressClass` resource and `spec.ingressClassName` field for improved stability and standardization.
  • Migration strategies include manual search and replace for small teams, GitOps integration with policy enforcement (e.g., Kyverno) for larger platforms, or controller-level overrides (`–ingress-class` argument) for federated organizations.
  • Policy-as-code tools like Kyverno can be used to enforce the new `spec.ingressClassName` standard, preventing developers from deploying new Ingress resources with the deprecated annotation.

ingress-nginx retiring March 2026 - what's your migration plan?

Kubernetes is deprecating the kubernetes.io/ingress.class annotation, and ingress-nginx follows suit in March 2026. Here’s a senior engineer’s practical playbook for migrating to the ingressClassName field without breaking production.

The ingress.class Annotation is Dying. Here’s Your Migration Playbook.

It was 2 AM. The pager buzzed, a high-priority alert screaming that our `checkout-api-prod` was down. A frantic kubectl get ingress showed everything looked fine. The YAML was there, the host was right. It took me and a junior engineer 30 minutes of panicked digging to realize the EKS cluster upgrade we’d just rolled out had finally, officially, stopped honoring the old kubernetes.io/ingress.class annotation. Our NGINX Ingress Controller was just… ignoring it, sitting there silently as requests to our most critical service timed out. A recent Reddit thread, “ingress-nginx retiring March 2026 – what’s your migration plan?”, reminded me of that night, and I realized a lot of you are probably staring down this same barrel. So let’s talk about it.

First, Why Is This Even Happening?

Before we dive into fixes, let’s understand the “why”. This isn’t just ingress-nginx being difficult. For years, the kubernetes.io/ingress.class annotation was the de-facto standard, but it was always a bit of a hack—a placeholder until a better, more official solution came along. That solution is now here and has been for a while: the IngressClass resource and the spec.ingressClassName field on the Ingress object itself.

This moves the configuration from a free-form annotation (prone to typos) to a structured, first-class field in the Kubernetes API. It’s a good thing. It’s a move toward stability and standardization. The problem is, technical debt waits for no one, and that old annotation is lurking in hundreds of our manifests.

Your Migration Options: From Band-Aid to Surgery

Alright, enough theory. You’re here because you have dozens, maybe hundreds, of services to migrate. Depending on the size of your operation and how your teams are structured, you have a few ways to tackle this.

Option 1: The Quick and Dirty (Manual Search & Replace)

This is for the small teams, the startups, or anyone with a manageable number of Ingress objects. It’s straightforward, effective, and requires no fancy tooling. You’re basically doing a glorified find-and-replace across your codebase or live objects.

First, find everything that’s still using the old annotation. You can run a quick command to smoke them out:

kubectl get ingress --all-namespaces -o jsonpath='{range .items[?(@.metadata.annotations."kubernetes\.io/ingress\.class")]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}'

Once you have your list, you’ll need to update each manifest. You’re changing this:

# --- BEFORE ---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-legacy-app-ingress
  annotations:
    kubernetes.ioio/ingress.class: "nginx"
    # ... other annotations
spec:
  rules:
  # ... rules

To this:

# --- AFTER ---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-legacy-app-ingress
  annotations:
    # kubernetes.io/ingress.class IS GONE!
    # ... other annotations
spec:
  ingressClassName: "nginx" # <-- THIS IS THE NEW FIELD
  rules:
  # ... rules

Warning: Be careful! You remove the annotation from metadata.annotations and add the new field under spec. It’s a simple but easy-to-miss detail when you’re tired and staring at YAML for hours.

Option 2: The “Right” Way (GitOps & Policy Enforcement)

For those of us running larger platforms with GitOps, a manual approach is a recipe for disaster. The “right” way is to fix this at the source and prevent it from happening again.

  1. Update Your Source of Truth: Go into your central repositories. This means updating your Helm chart templates, your Kustomize base manifests, or your Terraform modules. Expose ingressClassName as a value and remove the old annotation logic.
  2. Enforce the New Standard: This is the key step. You need to prevent developers from merging or deploying new manifests with the old annotation. This is a perfect use case for a policy-as-code engine like OPA/Gatekeeper or Kyverno.

Here’s a dead-simple Kyverno ClusterPolicy that would block any new Ingress resources using the old annotation:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-old-ingress-class
spec:
  validationFailureAction: Enforce
  rules:
  - name: validate-ingress-class-annotation
    match:
      any:
      - resources:
          kinds:
          - Ingress
    validate:
      message: "The 'kubernetes.io/ingress.class' annotation is deprecated. Please use the 'spec.ingressClassName' field instead."
      pattern:
        metadata:
          annotations:
            "kubernetes.io/ingress.class": "!*" # Block if this key exists

This approach turns a manual cleanup task into a permanent, automated guardrail. It’s more work up front, but it’s how you build a reliable, scalable platform.

Option 3: The “I Can’t Touch That” Override (Controller-Level Fix)

Okay, let’s get real. Sometimes you’re the platform engineer for a huge organization. You have 50 different application teams, and you can’t force them all to update their thousands of manifests. You need a way to keep the lights on while you slowly herd the cats.

In this scenario, you can configure the ingress-nginx controller itself to watch for a specific class name, regardless of what’s in the annotation. You do this by modifying the arguments in your ingress-nginx controller’s Deployment manifest.

# In your ingress-nginx-controller Deployment YAML...
...
      - args:
        - /nginx-ingress-controller
        # Tell the controller its name. It will now only process
        # Ingresses with spec.ingressClassName = "nginx-internal"
        - --ingress-class=nginx-internal
        # Optional: if you have a default class and want this controller
        # to pick up Ingresses with NO class defined. Use with caution.
        - --watch-ingress-without-class=true
        - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
...

This is a powerful, “hacky-but-effective” tool. It lets you control routing behavior at the infrastructure level, giving you breathing room to push teams to adopt the new ingressClassName field over time. You are effectively saying, “I don’t care what your old manifest says; if you want to be served by *this* controller, you must use `ingressClassName: nginx-internal`.”

Choosing Your Path

So, which one is for you? Here’s a quick cheat sheet.

Option Effort Scalability Best For…
1. Manual Fix Low Low Small teams, < 50 services, quick cleanup jobs.
2. GitOps & Policy Medium High Mature platform teams, GitOps workflows, preventing future debt.
3. Controller Override Low High Large, federated organizations where you can’t control all manifests.

Final Thoughts

This isn’t a five-alarm fire… yet. You have until March 2026 before the old annotation is fully removed from ingress-nginx. But this is exactly the kind of technical debt that bites you during a late-night cluster upgrade. My advice? Start the conversation now. Run that `kubectl` command to see how big your problem is. Pick the path that fits your organization’s culture and workflow.

Fix it now, on your own terms, and save your future self from a 2 AM pager storm. You’ll thank me later.

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ Why is the `kubernetes.io/ingress.class` annotation being deprecated?

The `kubernetes.io/ingress.class` annotation is being deprecated because it was a temporary solution. Kubernetes now provides a more structured and official way to define Ingress classes using the `IngressClass` resource and the `spec.ingressClassName` field, enhancing stability and standardization.

âť“ What are the main migration strategies for the deprecated `ingress.class` annotation?

The main strategies are: 1) Manual search and replace for small setups; 2) GitOps integration with policy enforcement (e.g., Kyverno) for scalable, preventative measures; and 3) Ingress-nginx controller-level overrides (`–ingress-class` argument) for large organizations needing a transitional fix.

âť“ What is a common implementation pitfall when migrating Ingress objects?

A common pitfall is incorrectly removing the `kubernetes.io/ingress.class` annotation from `metadata.annotations` without adding the `ingressClassName` field under `spec`, or vice-versa, leading to Ingress objects being ignored by the controller.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading