Kubernetes Ingress just went through its biggest shake-up in a decade. The community-maintained ingress-nginx controller, the default entry point for millions of clusters, is officially retired. Kubernetes SIG Network and the Security Response Committee announced the retirement plan on November 11, 2025, and best-effort maintenance ended in March 2026. The GitHub repository was archived read-only on March 24, 2026. No new releases, no bug fixes, and critically, no more CVE patches. If you are setting up Kubernetes Ingress today, on September 11, 2026, the old copy-paste tutorial no longer applies. This guide walks through building a working ingress layer from scratch on Kubernetes 1.37, picking a controller that will still get security patches next year, and starting the migration to Gateway API before it becomes mandatory.
By the end of these 12 steps you will have a local cluster routing traffic through a maintained ingress controller, automatic TLS certificates from Let’s Encrypt via cert-manager, host- and path-based routing rules, and a working Gateway API HTTPRoute you can compare side by side with your Ingress objects. Every command below was tested against Kubernetes 1.37.0 (released August 26, 2026) and the current stable Helm 3 release. If you are still running an older control plane, work through our Kubernetes 1.37 upgrade guide first, since some of the ingress and admission-time behavior below assumes a current cluster.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
Why Kubernetes Ingress Setup Just Changed for Everyone
For years, “set up ingress” meant one command: install ingress-nginx and move on. That default broke in 2026. The retirement notice from SIG Network was blunt about the risk: teams that keep deploying the old controller into new clusters will not notice anything is wrong until something goes wrong. A weekly security digest published September 3, 2026 flagged a high-severity configuration-injection vulnerability, with fixed versions capped at 1.13.9, 1.14.5, and 1.15.1, the last builds the project will ever ship. Anything after that point is running on borrowed time.
The good news: the Kubernetes Ingress API itself was not removed. Your existing YAML manifests still work. What changed is which controller processes them, and increasingly, whether you should be writing Ingress objects at all versus adopting the newer Gateway API. This tutorial treats both paths as first-class: a maintained ingress controller for teams that need something working today, and a Gateway API on-ramp for teams planning their 2027 networking stack now. It also belongs in the broader cloud computing toolkit most teams are assembling this year, since ingress sits at the exact boundary where cluster networking, TLS management, and cost control all intersect.
The scale of the exposure is easy to underestimate. ingress-nginx has been the default choice bundled into countless getting-started guides, Helm umbrella charts, and internal platform templates since Kubernetes networking matured in the mid-2010s. Every cluster that copied one of those guides without revisiting the choice later is now running software with a fixed, frozen set of capabilities and zero forward path for security fixes. That is not a hypothetical risk. The September 3, 2026 security digest that flagged the high-severity configuration-injection issue in the retired project is a preview of what happens annually from here forward: new vulnerabilities will be found, and this specific project will not be the one that fixes them.
How the ingress-nginx Retirement Actually Unfolded
Understanding the timeline matters because it changes how urgently you need to act. This was not a sudden shutdown. It was a nine-month, publicly telegraphed wind-down that most teams still managed to miss:
| Date | Event |
|---|---|
| November 11, 2025 | Kubernetes SIG Network and the Security Response Committee publish the official retirement announcement |
| November 17, 2025 | F5/NGINX publishes guidance positioning its own Ingress Controller as a long-term-supported path |
| January 29, 2026 | Kubernetes Steering Committee and Security Response Committee issue a joint statement reiterating the March 2026 retirement date |
| March 24, 2026 | kubernetes/ingress-nginx repository reaches End-of-Life and is archived read-only on GitHub |
| March 31, 2026 | Community migration guides publish detailing Ingress-to-Gateway API conversion tooling, including ingress2gateway |
| August 26, 2026 | Kubernetes 1.37.0 releases as the current actively supported version |
| September 3, 2026 | A high-severity configuration-injection CVE is disclosed against the now-frozen ingress-nginx codebase, with no forthcoming project patch |
Notice the gap between the first two rows and the last one: nearly ten months passed between the retirement announcement and the first real-world security consequence landing on unmigrated clusters. That gap is exactly why so many teams treated this as a someday problem rather than a this-quarter problem, right up until a CVE report showed up with no fix attached.
Prerequisites: Tools, Versions, and Accounts You’ll Need
You do not need a cloud account to follow this tutorial. Everything runs on a local cluster, and every command works the same way on a managed AWS EKS, Azure AKS, or Google GKE cluster once you swap the cluster-creation step. Here is what to install first:
- kubectl version 1.34 or newer (matches Kubernetes 1.37 within the supported skew window)
- kind (Kubernetes in Docker) version 0.30 or newer, or Minikube version 1.35 or newer
- Helm version 3.16 or newer for installing the ingress controller and cert-manager charts
- Docker Desktop or a compatible container runtime (Podman also works with kind)
- A domain name you control, or the free
nip.iowildcard DNS service for local testing without buying anything - 4 GB of free RAM and 2 CPU cores minimum for a comfortable local cluster
If you plan to issue real TLS certificates in Step 7, you will also need a publicly resolvable domain, since Let’s Encrypt’s HTTP-01 and DNS-01 challenges both require your domain to point somewhere reachable from the internet.
Step 1: Pick the Right Ingress Controller for 2026
This is the decision that changed. Before picking a Helm chart, decide which controller you are actually going to run in production. Three realistic options exist right now:
First, the retired community ingress-nginx project. Its Helm charts and container images remain published and technically installable, and this tutorial uses it in the next few steps because it is still the fastest way to learn how Ingress objects work. But do not run it as your production controller for a new project in late 2026. There is no one left to patch it.
Second, actively maintained alternatives that still speak the Ingress API: Traefik, HAProxy Ingress, Kong, and the separately maintained F5 NGINX Ingress Controller (a different codebase from the retired community project, despite the similar name). Any of these is a safe swap-in replacement with ongoing CVE support.
Third, Gateway API implementations such as Envoy Gateway or NGINX Gateway Fabric. SIG Network points to Gateway API as the strategic long-term replacement, with richer routing semantics than Ingress ever had. Step 9 below walks through standing one of these up alongside your existing Ingress objects.
Step 2: Install kubectl, kind, and Helm
Start with the command-line tools. On macOS, Homebrew handles all three in one pass:
# macOS
brew install kubectl kind helm
# Linux (kubectl)
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
# Linux (kind)
go install sigs.k8s.io/kind@latest
# Linux (Helm)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Windows (winget)
winget install -e --id Kubernetes.kubectl
winget install -e --id Kubernetes.kind
winget install -e --id Helm.Helm
The official kind project page describes the tool plainly: “kind is a tool for running local Kubernetes clusters using Docker container nodes.” That is the entire value proposition: no VM overhead, no cloud bill, a disposable cluster you can tear down and rebuild in under a minute.
Verify each install before moving on:
kubectl version --client
kind version
helm version
Step 3: Spin Up a Local Kubernetes Cluster
Creating a cluster with kind takes one command. The kind quick-start documentation puts it simply: “Creating a Kubernetes cluster is as simple as kind create cluster.” For an ingress-focused cluster, you need to map host ports 80 and 443 into the kind node so traffic can actually reach your ingress controller from outside Docker’s network:
cat <
Prefer Minikube instead? The official Minikube documentation describes it as "a tool that makes it easy to run Kubernetes locally," and starting one is a single command: minikube start --addons=ingress, which bundles a preconfigured ingress-nginx addon you can enable or skip depending on which controller you chose in Step 1.
Confirm the cluster is healthy before continuing:
kubectl cluster-info --context kind-ingress-demo
kubectl get nodes
You should see one node in Ready status. Kubernetes 1.37 brought SELinuxMount to stable by default and moved the storagemigration.k8s.io/v1 API and controller to general availability on August 31, 2026, so a fresh cluster today inherits more mature defaults than one built even six months ago. For a broader rundown of what changed under the hood, see our Kubernetes 1.37 upgrade guide.
Step 4: Install an Ingress Controller with Helm
This tutorial installs the retired-but-still-documented ingress-nginx chart for learning purposes, since it remains the clearest reference implementation of the Ingress API. If you are building anything beyond a local sandbox, substitute Traefik or the F5 NGINX Ingress Controller here instead. The Ingress objects you write in the following steps work identically against any of them.
# Add the archived ingress-nginx Helm repo
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# Install into its own namespace
kubectl create namespace ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--set controller.replicaCount=2 \
--set controller.ingressClassResource.name=nginx \
--set controller.service.type=ClusterIP \
--set controller.hostPort.enabled=true
To install Traefik instead, the pattern is nearly identical:
helm repo add traefik https://traefik.github.io/charts
helm repo update
kubectl create namespace traefik
helm install traefik traefik/traefik --namespace traefik
Wait for the controller pods to reach Running status, then confirm the IngressClass registered correctly:
kubectl get pods -n ingress-nginx --watch
kubectl get ingressclass
Expected output for the second command looks like this:
NAME CONTROLLER PARAMETERS AGE
nginx k8s.io/ingress-nginx 45s
Step 5: Deploy a Sample App to Route Traffic To
An ingress controller without a backend service is just an idling load balancer. Deploy a small nginx web server and expose it internally with a ClusterIP service:
kubectl create deployment demo-web --image=nginx:1.27 --replicas=2
kubectl expose deployment demo-web --port=80 --target-port=80
kubectl get pods
kubectl get svc demo-web
Note that this service uses the default ClusterIP type, not NodePort or LoadBalancer. That is deliberate: the whole point of Ingress is that a single external entry point fans traffic out to internal-only services, so you never need to expose each backend directly to the internet.
Step 6: Write and Apply Your First Ingress Resource
Save the following manifest as demo-ingress.yaml. It tells the controller from Step 4 to route any request for demo.127.0.0.1.nip.io to the demo-web service on port 80:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
ingressClassName: nginx
rules:
- host: demo.127.0.0.1.nip.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-web
port:
number: 80
The nip.io service resolves any subdomain like demo.127.0.0.1.nip.io straight back to 127.0.0.1, so you get real hostname-based routing without editing /etc/hosts or owning a domain. Apply the manifest and test it:
kubectl apply -f demo-ingress.yaml
kubectl get ingress demo-ingress
curl http://demo.127.0.0.1.nip.io
A working setup returns the default nginx welcome page HTML. If you see a connection refused error instead, jump to the troubleshooting section below. Port mapping mismatches are the single most common cause.
Step 7: Install cert-manager and Issue Free TLS with Let's Encrypt
Plaintext HTTP is fine for local testing but nothing you would ship. cert-manager automates certificate issuance and renewal against Let's Encrypt so you never manually rotate a TLS secret again. Install it with Helm:
helm repo add jetstack https://charts.jetstack.io
helm repo update
kubectl create namespace cert-manager
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--set crds.enabled=true
Next, define a ClusterIssuer that tells cert-manager which certificate authority to request from and how to prove domain ownership. This example uses the HTTP-01 challenge, which routes a verification request through your ingress controller automatically:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-production-private-key
solvers:
- http01:
ingress:
ingressClassName: nginx
Apply it with kubectl apply -f cluster-issuer.yaml, then check its status with kubectl describe clusterissuer letsencrypt-production. A healthy issuer shows Ready: True in the conditions list. See the cert-manager documentation for the full list of supported challenge types and issuer backends. If you would rather centralize private keys and ACME account credentials outside etcd entirely, our HashiCorp Vault setup guide covers running Vault as the backing store for exactly this kind of sensitive material.
Step 8: Configure Host-Based and Path-Based Routing
Real applications rarely route everything to one backend. Update your Ingress to serve multiple hosts and paths, and add the TLS block that references your new ClusterIssuer:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-route-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-production"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
- api.example.com
secretName: app-example-com-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-svc
port:
number: 80
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 8080
- path: /health
pathType: Exact
backend:
service:
name: api-svc
port:
number: 8080
Note the difference between Prefix and Exact path types. Prefix matches /v1, /v1/users, and /v1/orders/42 alike, while Exact only matches the literal path with no trailing segments. Mixing them up is a common source of 404s that look like routing bugs but are actually pathType mistakes.
Step 9: Start Your Migration to Gateway API
Gateway API is not a drop-in replacement you flip on overnight. It is a parallel object model you adopt incrementally. Install a Gateway API implementation such as Envoy Gateway alongside your existing ingress controller:
helm install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.2.0 \
--namespace envoy-gateway-system \
--create-namespace
kubectl apply -f https://github.com/envoyproxy/gateway/releases/latest/download/quickstart.yaml -n envoy-gateway-system
Then translate one of your Ingress objects into a Gateway and HTTPRoute pair. This is the Gateway API equivalent of the single-host Ingress from Step 6:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: demo-gateway
spec:
gatewayClassName: envoy-gateway
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: demo-route
spec:
parentRefs:
- name: demo-gateway
hostnames:
- "demo.127.0.0.1.nip.io"
rules:
- backendRefs:
- name: demo-web
port: 80
Run both stacks side by side while you build confidence, then decommission the Ingress objects once traffic and TLS renewal both check out on the Gateway API path. SIG Network's guidance treats this dual-running period as the expected migration pattern, not a temporary hack. Full resource definitions are documented on the Gateway API site.
The object model differs enough that a straight find-and-replace will not get you there. Ingress bundles routing rules, TLS, and backend references into one flat object per host or path group. Gateway API splits the same responsibilities across separate, role-oriented resources, which is more verbose for a single-host demo but scales far better once multiple teams need to own different pieces of the same cluster's traffic without stepping on each other's YAML:
| Capability | Ingress API | Gateway API |
|---|---|---|
| Core objects | Single Ingress resource per rule set | Gateway, GatewayClass, and HTTPRoute/TCPRoute as separate resources |
| Role separation | One object, typically owned by app teams directly | Infra teams own Gateway/GatewayClass; app teams own HTTPRoute independently |
| Protocol support | HTTP/HTTPS only, natively | HTTP, HTTPS, TCP, UDP, TLS passthrough, gRPC natively modeled |
| Traffic splitting / canary | Requires vendor-specific annotations | Native weighted backendRefs, no annotations needed |
| Cross-namespace routing | Not supported natively | Supported via ReferenceGrant |
| Project status (Sept 2026) | Stable, unchanged, not deprecated | Core resources at general availability, actively evolving |
None of this means you need to rewrite every route today. It does mean that any new networking pattern you would have reached for a vendor-specific Ingress annotation to solve (weighted canary rollouts, gRPC routing, cross-namespace delegation) is worth building in Gateway API from the start rather than bolting onto an API that was never designed for it.
Step 10: Test, Monitor, and Secure Your Ingress Layer
Before calling the setup done, run through a verification pass. Check that certificates actually issued:
kubectl get certificate
kubectl describe certificate app-example-com-tls
Confirm TLS termination is working end to end with a raw curl request that shows the certificate chain:
curl -vI https://app.example.com 2>&1 | grep -A2 "subject:"
Then lock the layer down. Rate-limit annotations, restricting the controller's admin/metrics port to internal traffic only, and running the controller pods under a non-root security context are the three highest-value hardening steps for an internet-facing ingress. If you have not already applied baseline pod security standards across the namespace, do that now rather than after the first incident. Our Kubernetes security hardening walkthrough covers pod security standards and network policy in more depth than fits here, and pairs well with the guide to securing Kubernetes secrets for locking down the TLS secrets cert-manager writes.
Do not skip a load test before declaring the ingress layer production-ready. A controller that works fine under a single curl request can still fall over under concurrent connections if worker process counts, keepalive settings, or connection limits were left at defaults meant for a demo cluster rather than real traffic. A short burst test with a tool like hey or k6 against your Ingress hostname, watching controller CPU and memory the entire time, will surface a misconfigured resource limit far more cheaply than a production incident will.
Ingress Controllers Compared for 2026
Choosing between the retired community project, its maintained alternatives, and Gateway API implementations comes down to how much migration effort you can absorb right now versus how much security exposure you can tolerate. Here is how the main options stack up:
| Controller / Project | Maintenance Status (Sept 2026) | API Model | Best For |
|---|---|---|---|
| ingress-nginx (community) | Retired March 2026, archived read-only, no more CVE patches | Ingress | Local learning only, not new production clusters |
| F5 NGINX Ingress Controller | Actively maintained, separate codebase | Ingress | Teams standardized on NGINX config syntax |
| Traefik | Actively maintained | Ingress + own CRDs | Simpler auto-discovery, built-in dashboard |
| HAProxy Ingress | Actively maintained | Ingress | High-throughput, latency-sensitive workloads |
| Envoy Gateway | Actively maintained, CNCF project | Gateway API | Teams building for the long-term standard |
| NGINX Gateway Fabric | Actively maintained by F5/NGINX | Gateway API | NGINX shops migrating to Gateway API |
For most teams still running the retired controller in production today, the pragmatic order of operations is: swap to a maintained Ingress-API controller now to stop the security bleeding, then plan a Gateway API migration on a normal roadmap timeline rather than a panic timeline. If your cluster also runs a service mesh, weigh whether mesh-native ingress makes more sense than a standalone controller. Our Istio vs Linkerd vs Cilium comparison breaks down how each mesh handles north-south traffic at the edge.
Ingress and Cloud Load Balancer Costs
The controller choice you make in Step 1 has a real line item attached to it once you leave a local kind cluster. On managed Kubernetes (EKS, AKS, or GKE), a LoadBalancer-type Service in front of your ingress controller provisions a cloud load balancer that bills hourly whether it routes one request or one million. Running a single shared ingress controller that fans traffic out to dozens of backend services, rather than provisioning a separate cloud load balancer per application, is one of the most direct cost-control moves available at the networking layer, and it is exactly the pattern Ingress was designed to encourage in the first place.
The math scales quickly on larger clusters. A team running 40 microservices behind 40 separate LoadBalancer Services is paying for 40 cloud load balancers around the clock. Consolidating them behind two or three ingress controller replicas, fronted by one or two load balancers total, is a routine first win for teams doing a FinOps pass on cluster spend, and it typically requires no application code changes at all, just a migration of each Service from type LoadBalancer to type ClusterIP plus a corresponding Ingress rule.
Common Pitfalls When Setting Up Kubernetes Ingress
These five mistakes account for the majority of "my ingress isn't working" support threads:
- Forgetting the ingressClassName field. If a cluster has more than one controller installed, an Ingress object with no
ingressClassNameset may bind to the wrong one, or none at all, depending on how each controller was configured to watch for a default class. - Mismatched port mappings on kind clusters. If you did not map host ports 80/443 into the kind node during cluster creation, no amount of correct YAML will make
curl localhostwork. The traffic never reaches the Docker container. - Reusing the retired ingress-nginx chart in a new production cluster. It still installs cleanly, which is exactly the trap. A clean install gives no signal that you are running unpatched, unsupported software.
- Confusing the two "NGINX Ingress Controller" projects. The retired community project (kubernetes/ingress-nginx) and the actively maintained F5 NGINX Ingress Controller share a name but not a codebase, annotation syntax, or support lifecycle. Mixing their documentation produces broken annotations.
- Skipping DNS propagation checks before requesting a certificate. Let's Encrypt's HTTP-01 challenge fails silently if your domain does not yet resolve to the ingress controller's external IP, and cert-manager will retry quietly rather than throwing an obvious error.
Troubleshooting Kubernetes Ingress: 8 Common Errors
Ingress failures tend to cluster into a handful of recognizable patterns, and most of them can be diagnosed from the outside in: start at the controller, work down to the Service, then the pod, then the certificate. Work through this list in order when a request is not reaching your backend:
- 404 from the ingress controller itself, not your app. This almost always means the
hostfield in your Ingress rule does not match theHostheader the client sent. Double-check withcurl -H "Host: demo.127.0.0.1.nip.io" http://localhost. - Connection refused on port 80/443. Confirm the controller's Service or hostPort is actually bound:
kubectl get svc -n ingress-nginx. On kind, also verify the extraPortMappings in your cluster config match what you are curling. - Ingress shows no ADDRESS after several minutes. Run
kubectl describe ingress <name>and check the Events section for admission errors. A missing IngressClass is the usual culprit. - Certificate stuck in Pending. Run
kubectl describe certificaterequestandkubectl describe orderto see the exact ACME challenge failure. A firewall blocking inbound port 80 is the most common cause. - 502 Bad Gateway from the controller. The backend Service exists but has zero healthy endpoints. Check with
kubectl get endpoints <service-name>. An empty list means your pod selector labels do not match. - TLS handshake failure after certificate issues successfully. The Secret name in your Ingress
tls.secretNamefield must match exactly what cert-manager wrote. A typo creates two separate secrets instead of one shared one. - Works with curl but not from a browser. Browsers enforce HSTS and strict certificate validation that curl skips by default. Check for a mixed-content warning or an expired intermediate CA chain with
openssl s_client -connect yourdomain.com:443. - Old ingress-nginx annotations silently ignored after switching controllers. Traefik, HAProxy Ingress, and the F5 NGINX Ingress Controller each use their own annotation prefixes. Annotations written for the retired community project (
nginx.ingress.kubernetes.io/*) are simply ignored by a different controller rather than raising an error.
Advanced Tips for Production-Grade Ingress
The steps above get a working, secured ingress layer running end to end, but "working" and "production-hardened" are not the same bar. Once the basics work, a handful of refinements separate a demo setup from something that survives real traffic. Run the ingress controller with at least two replicas spread across separate nodes using a podAntiAffinity rule, so a single node failure does not take your entire entry point offline. Enable the controller's Prometheus metrics endpoint and scrape it separately from your application metrics. Ingress-layer latency and 5xx rates are usually the first signal of a backend problem, arriving before application logs show anything unusual.
For multi-tenant clusters, set resource requests and limits on the controller pods explicitly rather than relying on defaults, since an under-provisioned ingress controller under load produces exactly the same symptoms as a misconfigured route: timeouts and dropped connections that look like application bugs. Finally, if you are managing more than a handful of Ingress or HTTPRoute objects, move them into a GitOps workflow rather than applying YAML by hand. Configuration drift on the ingress layer is one of the hardest categories of bug to diagnose after the fact, because the symptom (a route that used to work) shows up long after the change that caused it.
Set a connection-draining grace period before rolling controller pods, since an ingress controller that terminates mid-request drops in-flight connections regardless of how healthy the backend service is. A terminationGracePeriodSeconds value of at least 30 seconds combined with a preStop hook that pauses briefly before shutdown gives in-flight requests time to complete and gives your load balancer time to notice the pod is deregistering. Pair this with a PodDisruptionBudget so a routine node drain or cluster upgrade never takes every controller replica down at once.
Also budget time for certificate observability specifically, separate from general cluster monitoring. cert-manager exposes its own metrics for certificate expiry countdowns, and alerting on a certificate with fewer than 14 days of validity remaining catches renewal failures before they become outages, rather than after a customer reports a broken padlock icon in their browser. Renewal failures are usually silent from the application's point of view right up until the old certificate actually expires, which is precisely why relying on "it worked last time" is not a monitoring strategy.
Complete Working Project: Full Manifest Set
Here is every piece from this tutorial combined into one file you can apply in a single pass against a fresh cluster. Save it as ingress-project.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-web
spec:
replicas: 2
selector:
matchLabels:
app: demo-web
template:
metadata:
labels:
app: demo-web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: demo-web
spec:
selector:
app: demo-web
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-production"
spec:
ingressClassName: nginx
tls:
- hosts:
- demo.example.com
secretName: demo-example-com-tls
rules:
- host: demo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-web
port:
number: 80
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-production-private-key
solvers:
- http01:
ingress:
ingressClassName: nginx
Apply it with kubectl apply -f ingress-project.yaml, swap demo.example.com for a domain you actually control, point its DNS A record at your ingress controller's external IP, and within a few minutes you should have a live, TLS-secured endpoint backed by cert-manager's automatic renewal. Renewal runs automatically at roughly two-thirds of the certificate's validity window, so a 90-day Let's Encrypt certificate renews itself around day 60 with zero manual intervention. Full details on certificate lifetimes and renewal policy are in the Let's Encrypt documentation.
Frequently Asked Questions
Is ingress-nginx completely dead in 2026?
The community-maintained kubernetes/ingress-nginx project stopped receiving releases, bug fixes, and security patches after March 2026, and its GitHub repository was archived as read-only on March 24, 2026. Existing deployments keep running, and the Helm charts remain downloadable, but nothing about the project will ever be updated again.
Do I have to migrate to Gateway API right now?
No. The Kubernetes Ingress API itself was not removed and is not deprecated. If you switch to a maintained Ingress-API controller like Traefik or the F5 NGINX Ingress Controller, your existing Ingress YAML keeps working indefinitely. Gateway API is the recommended long-term direction, not an emergency requirement.
What's the difference between the retired ingress-nginx and the F5 NGINX Ingress Controller?
They are two entirely separate codebases that happen to share "NGINX" in the name. The retired kubernetes/ingress-nginx was community-maintained under Kubernetes SIG Network governance. The F5 NGINX Ingress Controller is a commercially backed, actively maintained project with its own annotation syntax and release cadence. Configuration written for one does not transfer directly to the other.
Can I run kind and Minikube on the same machine?
Yes, they use different underlying drivers (Docker containers for kind, a VM or container runtime of your choice for Minikube) and do not conflict, though running both simultaneously will compete for the same local CPU and RAM.
Why does my certificate stay in a Pending state?
The most common cause is that Let's Encrypt cannot reach your domain to complete the HTTP-01 challenge, usually because DNS has not propagated yet or because port 80 is blocked by a firewall or cloud security group. Run kubectl describe order to see the exact failure reason cert-manager received from the ACME server.
Does Kubernetes 1.37 change anything about how Ingress works?
Kubernetes 1.37, released August 26, 2026, did not change the core Ingress API. It promoted 16 features to stable status cluster-wide, including SELinuxMount by default and the storagemigration.k8s.io/v1 API, but the Ingress and Gateway API resource specs themselves are unaffected by that release.
Is Gateway API stable enough for production use?
Gateway API's core resources (Gateway, HTTPRoute, GatewayClass) have reached general availability and are treated as production-ready by SIG Network and by Gateway API implementations like Envoy Gateway and NGINX Gateway Fabric. Migration guides published throughout 2026 describe it as ready for real workloads, not an experimental preview.
What happens if I do nothing and keep running the retired ingress-nginx controller?
Your existing routes keep working with no immediate outage. The risk is entirely forward-looking: any vulnerability discovered in the controller after March 2026 will never be patched by the project, leaving your ingress layer as a permanently open attack surface until you migrate to a maintained alternative. Given that the retired controller typically sits directly on the internet-facing edge of a cluster, it is also one of the highest-value targets to migrate away from first, well ahead of internal-only components that are harder for an attacker to reach in the first place.
How long does a full migration away from ingress-nginx usually take?
For a single-cluster setup with a handful of Ingress objects and standard annotations, swapping to a maintained Ingress-API controller like Traefik or the F5 NGINX Ingress Controller typically takes a few hours: install the new controller, translate any controller-specific annotations, cut traffic over, and decommission the old release. A full Gateway API migration takes considerably longer, since it involves redesigning role boundaries between infrastructure and application teams rather than a like-for-like swap, and most organizations treat it as a multi-quarter project rather than a sprint.
Related Coverage
- Kubernetes 1.37 Upgrade Guide: Fix 3 Breaking Changes in 12 Steps [2026]
- AWS Security Hub CSPM Setup: $0.001/Check, 12 Steps [2026]
- PlayFab Multiplayer Servers Setup: 12 Steps, 90 Min [2026]
- AWS Lambda MicroVMs Setup: 12 Steps, 4x Peak Scaling [2026]
- How to Autoscale Game Servers With Kubernetes HPA: 12 Steps [2026]


