Kubernetes Ingress Setup: 12 Steps After NGINX EOL [2026]

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.

Google · Preferred Sources

Don't miss new tech stories on Google

Add Tech Insider once in the Google app and our stories appear in your news suggestions.

Add Now

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:

DateEvent
November 11, 2025Kubernetes SIG Network and the Security Response Committee publish the official retirement announcement
November 17, 2025F5/NGINX publishes guidance positioning its own Ingress Controller as a long-term-supported path
January 29, 2026Kubernetes Steering Committee and Security Response Committee issue a joint statement reiterating the March 2026 retirement date
March 24, 2026kubernetes/ingress-nginx repository reaches End-of-Life and is archived read-only on GitHub
March 31, 2026Community migration guides publish detailing Ingress-to-Gateway API conversion tooling, including ingress2gateway
August 26, 2026Kubernetes 1.37.0 releases as the current actively supported version
September 3, 2026A 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.io wildcard 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:

CapabilityIngress APIGateway API
Core objectsSingle Ingress resource per rule setGateway, GatewayClass, and HTTPRoute/TCPRoute as separate resources
Role separationOne object, typically owned by app teams directlyInfra teams own Gateway/GatewayClass; app teams own HTTPRoute independently
Protocol supportHTTP/HTTPS only, nativelyHTTP, HTTPS, TCP, UDP, TLS passthrough, gRPC natively modeled
Traffic splitting / canaryRequires vendor-specific annotationsNative weighted backendRefs, no annotations needed
Cross-namespace routingNot supported nativelySupported via ReferenceGrant
Project status (Sept 2026)Stable, unchanged, not deprecatedCore 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 / ProjectMaintenance Status (Sept 2026)API ModelBest For
ingress-nginx (community)Retired March 2026, archived read-only, no more CVE patchesIngressLocal learning only, not new production clusters
F5 NGINX Ingress ControllerActively maintained, separate codebaseIngressTeams standardized on NGINX config syntax
TraefikActively maintainedIngress + own CRDsSimpler auto-discovery, built-in dashboard
HAProxy IngressActively maintainedIngressHigh-throughput, latency-sensitive workloads
Envoy GatewayActively maintained, CNCF projectGateway APITeams building for the long-term standard
NGINX Gateway FabricActively maintained by F5/NGINXGateway APINGINX 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 ingressClassName set 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 localhost work. 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 host field in your Ingress rule does not match the Host header the client sent. Double-check with curl -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 certificaterequest and kubectl describe order to 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.secretName field 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

Sofia Lindström

Sofia Lindström

Editor-in-Chief

Sofia Lindström is the Editor-in-Chief at Tech Insider, where she leads editorial strategy and oversees coverage across AI, cybersecurity, and enterprise technology. With over a decade in Swedish tech journalism, she previously served as technology editor at Dagens Industri and covered the Nordic startup ecosystem for Breakit. Sofia holds an MSc in Media Technology from KTH Royal Institute of Technology and is a frequent speaker at Web Summit and Slush. She is passionate about making complex technology accessible to business leaders.

View all articles