## Introduction

Kubernetes Ingress Classes control how traffic reaches your services, and losing or misconfiguring them can take down applications in production. This article gives you a practical, version-aware approach to backing up, restoring, and validating Ingress Classes, with commands you can run today.

You'll learn how to take a snapshot of your current Ingress Classes, restore them from a backup, roll back to a known-good state, and verify that everything works as expected. We'll cover the exact commands, expected outputs, common failure modes, and how to recover when something goes wrong.

This guide is written for developers, DevOps engineers, and technical teams who want to manage Ingress Classes safely. The goal is operational safety: observe before changing, limit the blast radius, protect secrets, and verify every result.

## Version and Environment Inventory

Before touching anything, understand your environment. The version of Kubernetes and your Ingress controller determines which commands and features are available. For example, the ingressClassName field in the Ingress spec is stable since Kubernetes 1.22, and the networking.k8s.io/v1 API is the current standard.

### Check Kubernetes and Controller Versions

Start by identifying your cluster version and the Ingress controller you're using:

kubectl version --short
kubectl get pods -n ingress-nginx -o wide 
 Expected output for cluster version:

Client Version: v1.29.0
Server Version: v1.29.2 
 For the controller, you'll see pods like ingress-nginx-controller-xxxx with the image tag showing the version.

### Inventory Current Ingress Classes

List all Ingress Classes in the cluster:

kubectl get ingressclass 
 Sample output:

NAME CONTROLLER PARAMETERS AGE
nginx k8s.io/ingress-nginx <none> 30d 
 To see details, including annotations and parameters, use:

kubectl describe ingressclass nginx 

### Capture a Backup

 Create a backup of all Ingress Classes in YAML format:

kubectl get ingressclass -o yaml > ingressclass-backup.yaml 
 Verify the file is not empty and contains the expected data:

wc -l ingressclass-backup.yaml
head -20 ingressclass-backup.yaml 
 If you use GitOps, commit this file to your repository. For a more robust backup, also export related resources like the Ingress controller deployment, services, and ConfigMaps, because Ingress Classes often depend on them.

### Record Timestamps and State

Before making any changes, capture the current time and the exact state of the Ingress Classes:

date -u +"%Y-%m-%dT%H:%M:%SZ"
kubectl get ingressclass -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.creationTimestamp}{"\n"}{end}' 
 This gives you a clear snapshot to compare against after any changes.

<style>
.eno-quiz-widget{margin:2rem 0;padding:1.5rem;border-radius:12px;background:var(--eno-surface-lowest,#f7f7f8);border:1px solid var(--eno-border-soft,#e2e2e6);font-family:var(--eno-font-body,Inter,sans-serif)}
.eno-quiz-widget .eno-quiz-kicker{font-family:var(--eno-font-label,"JetBrains Mono",monospace);font-size:.75rem;letter-spacing:.05em;text-transform:uppercase;color:var(--eno-text-muted,#5f5f68);margin:0 0 .5rem}
.eno-quiz-widget .eno-quiz-question{font-family:var(--eno-font-heading,"Hanken Grotesk",sans-serif);font-size:1.0625rem;font-weight:600;margin:0 0 1rem;color:var(--eno-text-strong,#1a1a1f)}
.eno-quiz-widget .eno-quiz-options{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.5rem}
.eno-quiz-widget .eno-quiz-option{display:block;width:100%;min-height:44px;text-align:left;padding:.625rem .875rem;border-radius:8px;border:1.5px solid var(--eno-border-soft,#e2e2e6);background:#fff;font-size:.9375rem;cursor:pointer;transition:border-color var(--eno-motion-base,180ms ease-out),background var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-option:hover{border-color:var(--eno-primary,#0059bb)}
.eno-quiz-widget .eno-quiz-option:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-option[aria-pressed="true"]{border-color:var(--eno-primary,#0059bb);background:rgb(0 89 187 / 0.06)}
.eno-quiz-widget .eno-quiz-option[data-correct="true"].eno-quiz-revealed{border-color:var(--eno-success,#17803a);background:rgb(23 128 58 / 0.08)}
.eno-quiz-widget .eno-quiz-option[data-correct="false"].eno-quiz-revealed.eno-quiz-was-selected{border-color:var(--eno-error,#ba1a1a);background:var(--eno-error-soft,#ffdad6)}
.eno-quiz-widget .eno-quiz-option-icon{display:inline-block;width:1.1em;margin-right:.4em;font-weight:700}
.eno-quiz-widget .eno-quiz-submit{margin-top:1rem;min-height:44px;padding:.5rem 1.25rem;border-radius:8px;border:none;background:var(--eno-primary,#0059bb);color:#fff;font-weight:600;font-size:.9375rem;cursor:pointer;transition:opacity var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-submit:disabled{opacity:.5;cursor:not-allowed}
.eno-quiz-widget .eno-quiz-submit:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-explanation{margin-top:1rem;padding:.875rem 1rem;border-radius:8px;font-size:.9375rem;line-height:1.5;display:none}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-visible{display:block}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-correct{background:rgb(23 128 58 / 0.08);color:var(--eno-success,#17803a)}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-incorrect{background:var(--eno-error-soft,#ffdad6);color:var(--eno-error,#ba1a1a)}
@media (prefers-reduced-motion: reduce){.eno-quiz-widget *{transition:none!important}}
</style><div class="eno-quiz-widget" role="group" aria-label="Quick check question">
Quick check 1 of 2

Which API version of IngressClass is no longer served as of Kubernetes v1.22?

- networking.k8s.io/v1
- networking.k8s.io/v1beta1
- extensions/v1beta1
- networking.k8s.io/v1alpha1

<button type="button" class="eno-quiz-submit" disabled>Submit</button>
<div class="eno-quiz-explanation">The passage states that the networking.k8s.io/v1beta1 API version of IngressClass is no longer served as of v1.22.</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
  document.querySelectorAll('.eno-quiz-widget:not([data-eno-quiz-bound])').forEach(function (widget) {
    widget.setAttribute('data-eno-quiz-bound', '1');
    var options = Array.prototype.slice.call(widget.querySelectorAll('.eno-quiz-option'));
    var submitBtn = widget.querySelector('.eno-quiz-submit');
    var explanation = widget.querySelector('.eno-quiz-explanation');
    var selected = null;
    options.forEach(function (opt) {
      opt.addEventListener('click', function () {
        if (widget.hasAttribute('data-eno-quiz-answered')) return;
        options.forEach(function (o) { o.setAttribute('aria-pressed', 'false'); });
        opt.setAttribute('aria-pressed', 'true');
        selected = opt;
        submitBtn.disabled = false;
      });
    });
    submitBtn.addEventListener('click', function () {
      if (!selected || widget.hasAttribute('data-eno-quiz-answered')) return;
      widget.setAttribute('data-eno-quiz-answered', '1');
      submitBtn.disabled = true;
      var correct = selected.getAttribute('data-correct') === 'true';
      options.forEach(function (o) {
        o.classList.add('eno-quiz-revealed');
        if (o === selected) o.classList.add('eno-quiz-was-selected');
        var icon = o.querySelector('.eno-quiz-option-icon');
        if (o.getAttribute('data-correct') === 'true') icon.textContent = '\u2713';
        else if (o === selected) icon.textContent = '\u2717';
      });
      explanation.classList.add('eno-quiz-visible', correct ? 'eno-quiz-correct' : 'eno-quiz-incorrect');
    });
  });
});
</script>

## Safe Configuration Path

Now that you have a backup, you can safely make changes. The key is to make one change at a time, test it, and always have a rollback plan.

### Create a New Ingress Class

Suppose you want to add a new Ingress Class for a different controller. Create a YAML file:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
 name: traefik
spec:
 controller: traefik.io/ingress-controller 
 Apply it:

kubectl apply -f traefik-ingressclass.yaml 
 Verify it was created:

kubectl get ingressclass traefik 

### Update an Existing Ingress Class

 To change an existing Ingress Class's parameters, edit the object. For example, to add a parameter referencing a ConfigMap for the NGINX controller:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
 name: nginx
spec:
 controller: k8s.io/ingress-nginx
 parameters:
 apiGroup: k8s.example.com
 kind: IngressParameters
 name: nginx-params 
 Apply the change:

kubectl apply -f updated-ingressclass.yaml 
 Always verify the change took effect:

kubectl describe ingressclass nginx | grep -A2 Parameters 

### Testing with a Minimal Ingress

 Create a simple Ingress that uses the Ingress Class and point it to a test service:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: test-ingress
spec:
 ingressClassName: nginx
 rules:
 - host: test.example.com
 http:
 paths:
 - path: /
 pathType: Prefix
 backend:
 service:
 name: echo
 port:
 number: 80 
 Apply and verify the Ingress controller picks it up:

kubectl apply -f test-ingress.yaml
kubectl get ingress test-ingress
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller --tail=50 | grep test.example.com 
 Expected log lines include "Adding or updating ingress" for the host.

## Verification and Diagnostics

Verification is not just about checking that resources exist; it's about confirming the system works as intended.

### Check Ingress Controller Health

Use these commands to ensure the Ingress controller is running correctly:

kubectl get pods -n ingress-nginx -o wide
kubectl describe pod -n ingress-nginx <pod-name>
kubectl logs -n ingress-nginx <pod-name> --tail=20 
 Look for "Running" status, recent logs, and no crash loops.

### Validate Ingress Resources

List all Ingress resources and check their class:

kubectl get ingress -A
kubectl get ingress -A -o custom-columns=NAME:.metadata.name,CLASS:.spec.ingressClassName 
 This helps you confirm that every Ingress points to the correct class.

### Test Traffic Flow

For a local test, use port-forwarding:

kubectl port-forward service/echo 8080:80 
 Then, in another terminal, make a request:

curl -H "Host: test.example.com" http://localhost:8080/ 
 You should see a response from the echo service, confirming the Ingress Class and controller work.

### Use Diagnostic Commands

When something fails, use kubectl describe and kubectl logs to diagnose. For example, if the Ingress doesn't get an address, check events:

kubectl describe ingress test-ingress 
 Look for events like "Error on ingress ..." or "Scheduled for sync".

<style>
.eno-quiz-widget{margin:2rem 0;padding:1.5rem;border-radius:12px;background:var(--eno-surface-lowest,#f7f7f8);border:1px solid var(--eno-border-soft,#e2e2e6);font-family:var(--eno-font-body,Inter,sans-serif)}
.eno-quiz-widget .eno-quiz-kicker{font-family:var(--eno-font-label,"JetBrains Mono",monospace);font-size:.75rem;letter-spacing:.05em;text-transform:uppercase;color:var(--eno-text-muted,#5f5f68);margin:0 0 .5rem}
.eno-quiz-widget .eno-quiz-question{font-family:var(--eno-font-heading,"Hanken Grotesk",sans-serif);font-size:1.0625rem;font-weight:600;margin:0 0 1rem;color:var(--eno-text-strong,#1a1a1f)}
.eno-quiz-widget .eno-quiz-options{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.5rem}
.eno-quiz-widget .eno-quiz-option{display:block;width:100%;min-height:44px;text-align:left;padding:.625rem .875rem;border-radius:8px;border:1.5px solid var(--eno-border-soft,#e2e2e6);background:#fff;font-size:.9375rem;cursor:pointer;transition:border-color var(--eno-motion-base,180ms ease-out),background var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-option:hover{border-color:var(--eno-primary,#0059bb)}
.eno-quiz-widget .eno-quiz-option:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-option[aria-pressed="true"]{border-color:var(--eno-primary,#0059bb);background:rgb(0 89 187 / 0.06)}
.eno-quiz-widget .eno-quiz-option[data-correct="true"].eno-quiz-revealed{border-color:var(--eno-success,#17803a);background:rgb(23 128 58 / 0.08)}
.eno-quiz-widget .eno-quiz-option[data-correct="false"].eno-quiz-revealed.eno-quiz-was-selected{border-color:var(--eno-error,#ba1a1a);background:var(--eno-error-soft,#ffdad6)}
.eno-quiz-widget .eno-quiz-option-icon{display:inline-block;width:1.1em;margin-right:.4em;font-weight:700}
.eno-quiz-widget .eno-quiz-submit{margin-top:1rem;min-height:44px;padding:.5rem 1.25rem;border-radius:8px;border:none;background:var(--eno-primary,#0059bb);color:#fff;font-weight:600;font-size:.9375rem;cursor:pointer;transition:opacity var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-submit:disabled{opacity:.5;cursor:not-allowed}
.eno-quiz-widget .eno-quiz-submit:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-explanation{margin-top:1rem;padding:.875rem 1rem;border-radius:8px;font-size:.9375rem;line-height:1.5;display:none}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-visible{display:block}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-correct{background:rgb(23 128 58 / 0.08);color:var(--eno-success,#17803a)}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-incorrect{background:var(--eno-error-soft,#ffdad6);color:var(--eno-error,#ba1a1a)}
@media (prefers-reduced-motion: reduce){.eno-quiz-widget *{transition:none!important}}
</style><div class="eno-quiz-widget" role="group" aria-label="Quick check question">
Quick check 2 of 2

What field in the Ingress spec replaces the deprecated kubernetes.io/ingress.class annotation?

- spec.ingressClassName
- metadata.name
- spec.defaultBackend
- spec.rules

<button type="button" class="eno-quiz-submit" disabled>Submit</button>
<div class="eno-quiz-explanation">The passage mentions that the annotation is deprecated in favor of spec.ingressClassName.</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
  document.querySelectorAll('.eno-quiz-widget:not([data-eno-quiz-bound])').forEach(function (widget) {
    widget.setAttribute('data-eno-quiz-bound', '1');
    var options = Array.prototype.slice.call(widget.querySelectorAll('.eno-quiz-option'));
    var submitBtn = widget.querySelector('.eno-quiz-submit');
    var explanation = widget.querySelector('.eno-quiz-explanation');
    var selected = null;
    options.forEach(function (opt) {
      opt.addEventListener('click', function () {
        if (widget.hasAttribute('data-eno-quiz-answered')) return;
        options.forEach(function (o) { o.setAttribute('aria-pressed', 'false'); });
        opt.setAttribute('aria-pressed', 'true');
        selected = opt;
        submitBtn.disabled = false;
      });
    });
    submitBtn.addEventListener('click', function () {
      if (!selected || widget.hasAttribute('data-eno-quiz-answered')) return;
      widget.setAttribute('data-eno-quiz-answered', '1');
      submitBtn.disabled = true;
      var correct = selected.getAttribute('data-correct') === 'true';
      options.forEach(function (o) {
        o.classList.add('eno-quiz-revealed');
        if (o === selected) o.classList.add('eno-quiz-was-selected');
        var icon = o.querySelector('.eno-quiz-option-icon');
        if (o.getAttribute('data-correct') === 'true') icon.textContent = '\u2713';
        else if (o === selected) icon.textContent = '\u2717';
      });
      explanation.classList.add('eno-quiz-visible', correct ? 'eno-quiz-correct' : 'eno-quiz-incorrect');
    });
  });
});
</script>

## Failure Modes and Recovery

Even with careful planning, things fail. Here are common failure modes and how to recover.

### Ingress Class Not Found

If your Ingress references a non-existent class, the controller might ignore it or error. Check the Ingress events:

kubectl describe ingress <name> 
 If you see "class nginx not found", restore the Ingress Class from your backup.

### Manual Recovery Steps

- Restore the Ingress Class from backup:

kubectl apply -f ingressclass-backup.yaml 

- Verify it exists:

 kubectl get ingressclass 

- Re-apply the Ingress if needed:

 kubectl replace -f test-ingress.yaml 

### Rolling Back a Change

 If you modified an Ingress Class and it caused issues, roll back to a previous version. Since Ingress Classes are not versioned like ConfigMaps, you must re-apply the backup file. If you use the --server-side flag, you can also use kubectl apply --server-side --force-conflicts to overwrite.

### Controller Crash

If the Ingress controller crashes, check its logs:

kubectl logs -n ingress-nginx <pod-name> --previous 
 If it's a crash loop, roll back to a previous controller version. If you have a GitOps setup, revert the commit that changed the controller.

## Operations Checklist

Follow this checklist for every Ingress Class operation.

### Before the Change

- [ ] Verify the cluster version: kubectl version --short

- [ ] List current Ingress Classes: kubectl get ingressclass

- [ ] Take a backup: kubectl get ingressclass -o yaml > backup.yaml

- [ ] Save timestamps: date -u +"%Y-%m-%dT%H:%M:%SZ"

- [ ] Identify the blast radius: which services are affected?

### During the Change

- [ ] Apply the change: kubectl apply -f <file>.yaml

- [ ] Verify the resource is updated: kubectl describe ingressclass <name>

- [ ] Test with a dummy Ingress: apply a test Ingress and check traffic

### After the Change

- [ ] Confirm the Ingress controller is healthy: kubectl get pods -n ingress-nginx

- [ ] Validate that all Ingresses use the correct class: kubectl get ingress -A -o custom-columns=...

- [ ] Update your backup file with the new state: kubectl get ingressclass -o yaml > backup.yaml

### If Something Fails

- [ ] Don't panic; assess the error.

- [ ] Restore from backup: kubectl apply -f backup.yaml

- [ ] Check controller logs: kubectl logs -n ingress-nginx --previous

- [ ] If needed, roll back the controller deployment to a previous version.

## Conclusion

 Backing up and restoring Kubernetes Ingress Classes is a routine operation that can prevent downtime and data loss. By following a version-aware, observable, and reversible process, you can handle changes with confidence.

The key takeaways are: always inventory your environment, take backups before changes, make small scoped changes, and verify everything with concrete commands. Use the examples in this article as your starting point, and adapt them to your specific controller and version.

As a next step, run a low-risk backup and restore drill in a development cluster. Document the process, and share it with your team. This way, when a production incident occurs, everyone knows exactly what to do.