Blog Thumb

Written by Vikrant Nalawade

| May 20, 2026

10 min read

Migrating to NGINX Plus Ingress Controller: A Production-Grade Migration Plan

Article Summary
The Kubernetes community deprecated the ingress-nginx controller in early 2026, marking the end of maintenance for one of the most widely adopted Ingress controllers. This article offers a practitioner’s guide to migrating from the community ingress-nginx controller to the F5 NGINX Ingress Controller — with or without NGINX Plus — using a structured, phased approach that ensures minimal or near-zero downtime and production reliability. Topics covered include the architectural differences between the two controllers, CRD-based configuration, annotation-to-CRD migration mapping, a phased execution plan, and post-migration validation.

1. The Inflection Point: Why Migration Can No Longer Wait

For years, the Kubernetes community-maintained ingress-nginx controller served as the default entry point for HTTP traffic into Kubernetes clusters. At its peak, it was estimated to be deployed in more than 40% of Kubernetes clusters, accumulating over 10 million Docker Hub downloads. However, on March 24, 2026, the project was officially archived. Version 1.15.1 was its final release. No further security patches, bug fixes, or feature development will follow.

This was not an overnight decision. For years the project operated on thin maintenance — largely sustained by one or two engineers working outside business hours. The situation became critical in early 2025 when CVE-2025-1974, commonly known as IngressNightmare, was disclosed with a CVSS score of 9.8. This critical remote code execution vulnerability, capable of enabling full cluster takeover, underscored just how high the stakes are for unmaintained ingress infrastructure.

The practical consequence is clear: clusters still running ingress-nginx are now accumulating unpatched CVEs with no remediation path. For organizations in regulated sectors — financial services, insurance, or telecommunications — operating with end-of-life infrastructure is not merely a technical concern but a compliance and governance risk.

Important Note:
There are two distinct NGINX projects that are commonly confused. The community Kubernetes/ingress-nginx project (EOL, archived March 2026) is entirely separate from the F5 NGINX Ingress Controller (nginx/kubernetes-ingress). The latter is actively maintained by a dedicated full-time team at F5, open-source under Apache 2.0, and continues to receive regular security updates, features, and enterprise support. This article focuses on migrating to the F5 NGINX Ingress Controller.

2. Understanding the F5 NGINX Ingress Controller

The F5 NGINX Ingress Controller (nginx/kubernetes-ingress) is a Kubernetes-native Ingress implementation built and maintained directly by the NGINX engineering team at F5. It supports both NGINX Open Source and NGINX Plus as its data plane and is available in two tiers:

  • NGINX Ingress Controller (OSS) — Apache 2.0 licensed, open source, with advanced traffic management, VirtualServer CRDs, TLS termination, TCP/UDP load balancing, Prometheus metrics, and OpenTelemetry support.
  • NGINX Plus Ingress Controller — The enterprise tier built on NGINX Plus, adding dynamic reconfiguration without worker process reloads, active health checks, the NGINX Plus live activity monitoring dashboard, NGINX App Protect WAF, and vendor commercial support with SLAs.

A key differentiator from the community controller is that F5 develops and maintains both the load balancer engine (NGINX/NGINX Plus) and the controller application itself. This unified ownership model means that the ingress layer benefits from native NGINX capabilities — with significantly less dependency on Lua-driven extension behavior compared with the community ingress-nginx controller, reducing the associated memory leak and OOM crash risks.

2.1 Why NGINX Plus Matters for Enterprise Deployments

The Open Source tier of NGINX Ingress Controller is production-capable for many use cases. However, in enterprise environments — particularly those governed by SLAs, zero-downtime mandates, or security compliance frameworks — NGINX Plus unlocks a qualitatively different operational posture:

Capability OSS vs NGINX Plus Ingress Controller
Dynamic Reconfiguration NGINX Plus uses the NGINX Plus API to apply upstream changes without process reloads — critical for zero-downtime deployments in high-traffic environments.
Active Health Checks NGINX Plus supports active (proactive) health checks versus passive-only in OSS. Upstream failures are detected before they impact real user traffic.
NGINX App Protect WAF Enterprise-grade WAF with signature-based protection, policy-based controls, bot defense capabilities, and integration with F5 WAF for NGINX — deeply embedded into the Ingress layer without a separate sidecar.
Live Activity Monitoring Built-in dashboard exposing real-time upstream health, request rates, error rates, and connection counts — no external tooling required.
mTLS & JWT Authentication Policy CRDs for mutual TLS and JWT validation are available in both tiers, but NGINX Plus integrates with external identity providers and key management systems more robustly.
Commercial Support SLA-backed support, CVE patch access, and direct escalation paths — essential for production incident response in mission-critical environments.

3. Architectural Shift: From Annotations to CRDs

The most significant conceptual change when moving to the F5 NGINX Ingress Controller is the configuration philosophy. The community ingress-nginx controller was annotation-driven. Complex behaviors — rewrite rules, canary weights, rate limits, authentication — were expressed as opaque key-value string annotations on standard Kubernetes Ingress resources. This approach, while flexible, had serious drawbacks: no schema validation, no IDE autocompletion, and brittle Lua-based extensions that could introduce memory leaks and out-of-memory crashes.

The F5 NGINX Ingress Controller is CRD-native. Its primary configuration model uses Custom Resource Definitions that are schema-validated by the Kubernetes API server at apply time — misconfigured resources are rejected before they ever reach NGINX. The five core CRDs are:

CRD Purpose
VirtualServer The primary resource for HTTP/HTTPS load balancing. Replaces annotation-heavy Ingress resources with structured upstreams, routes, TLS blocks, path rewrites, header manipulation, traffic splitting, and error pages.
VirtualServerRoute Extends VirtualServer for multi-team environments — allows delegated routing configuration per sub-path or sub-domain within a shared VirtualServer.
Policy Defines reusable security and traffic policies: rate limiting, JWT authentication, mTLS (client certificate verification), access control (IP allowlist/denylist), and CORS. Attachable to both VirtualServer and, from v5.4.0 onwards, standard Ingress resources.
TransportServer Layer 4 TCP/UDP load balancing and TLS Passthrough. Replaces ConfigMap-based TCP/UDP service exposure used in ingress-nginx.
GlobalConfiguration Cluster-wide listener and global NGINX settings — defines the TCP/UDP listeners that TransportServer resources bind to.

For teams that need a phased migration approach, the F5 controller also supports standard Kubernetes Ingress resources with its own annotation syntax (nginx.org/* prefix, distinct from ingress-nginx’s nginx.ingress.kubernetes.io/* prefix). This means you can initially migrate the controller without rewriting every Ingress manifest, then progressively adopt CRDs where advanced behavior is needed.

v5.4.0 Migration Milestone
NGINX Ingress Controller v5.4.0 introduced Policy CRDs directly attachable to kind: Ingress resources via the nginx.org/policies annotation. This removes the previous requirement to adopt VirtualServer CRDs just to use policy features like CORS or access control — significantly reducing migration friction for annotation-heavy deployments.

4. Before You Migrate: Audit and Inventory

A successful migration begins with a thorough audit of the current state. Ingress migrations that fail typically do so because teams discover undocumented annotation usage mid-migration. The inventory phase is non-negotiable.

4.1 Identify Existing Controller and Resources

First, confirm you are running the community ingress-nginx controller:

kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx

Then enumerate all Ingress resources cluster-wide:

kubectl get ingress --all-namespaces -o yaml > ingress-inventory.yaml

Extract all unique annotations in use across your Ingress resources:

kubectl get ingress --all-namespaces -o json | \
jq -r '.items[].metadata.annotations | keys[]' | sort -u

4.2 Classify Your Annotations

Once you have the full annotation inventory, classify each annotation into one of three migration categories:

Category Description
Direct Map Annotation has a near-equivalent in NGINX Ingress Controller’s own annotation syntax (nginx.org/* prefix). Low-effort translation, no CRD adoption required.
CRD Migration Required Annotation maps to a VirtualServer, Policy, or TransportServer field. Migration requires authoring new CRD manifests but gains validation and richer capabilities.
Custom / Snippet No direct equivalent exists. Behavior must be replicated using NGINX configuration snippets or custom templates. Requires careful testing.

4.3 Document Global ConfigMap Settings

The community controller uses a ConfigMap for global NGINX settings. Document all keys in use and map them to their NGINX Ingress Controller ConfigMap equivalents — the key names often differ. The official migration guide at docs.nginx.com provides a complete ConfigMap key translation table.

4.4 Define Goals and Success Criteria

Before executing the migration, define measurable success criteria:

  • Minimal or near-zero production downtime during the cutover window
  • All existing routing rules, TLS terminations, and backend health checks functioning identically
  • Security policies (WAF, rate limiting, mTLS) migrated and validated
  • Prometheus metrics and alerting dashboards updated for the new controller
  • Old controller fully decommissioned within an agreed timeframe

5. The Phased Migration Plan

Attempting a big-bang migration of a production Ingress controller is high-risk and unnecessary. Both controllers can run concurrently in the same cluster using separate IngressClass resources, enabling a controlled, service-by-service migration with real-time validation at each step.

01 Parallel Deploy

  • Install NGINX Ingress Controller in a separate namespace (e.g., nginx-ingress) using Helm
  • Register a distinct IngressClass (e.g., nginx-nic) so the new controller manages no resources yet
  • For NGINX Plus: pull the controller image from the F5 Container Registry using your JWT license key
  • Validate the new controller is running and healthy — it should process zero Ingress resources at this stage
  • Install CRDs: VirtualServer, VirtualServerRoute, Policy, TransportServer, GlobalConfiguration

02 Configure & Convert

  • Select a low-risk, non-critical service as the first migration target
  • Author a VirtualServer (or annotated Ingress) equivalent of the existing Ingress manifest
  • Convert annotations using the NGINX Ingress Migration Tool at kubernetes.nginx.org — paste your YAML, get migration suggestions with 130+ annotation mappings
  • For Policy CRDs (rate limiting, mTLS, JWT): author Policy resources and attach via nginx.org/policies annotation
  • For TCP/UDP services: replace ConfigMap-based exposure with TransportServer + GlobalConfiguration CRDs
  • Ensure TLS secrets are present in the same namespace as the VirtualServer or Ingress resource

03 Test & Validate

  • Set ingressClassName to nginx-nic on the migrated resource — it is now managed by the new controller
  • Run functional tests: routing correctness, TLS termination, header manipulation, error pages
  • Run security tests: WAF policy enforcement, rate limiting, mTLS handshake validation
  • Check Prometheus metrics endpoint on the new controller and validate dashboards
  • For NGINX Plus: verify live activity monitoring dashboard shows accurate upstream health
  • Confirm cert-manager integration: if using VirtualServer CRDs, retain a minimal ‘shim’ Ingress for HTTP-01 ACME challenge or switch to DNS-01 validation

04 Traffic Cutover

  • Use VirtualServer traffic splitting (VirtualServer.Route.Splits) to shift traffic by weight — e.g., 90% old controller, 10% new — for gradual validation with real user traffic
  • Monitor latency, error rates, and upstream health throughout the shift using observability tooling
  • Progressively increase traffic weight to the new controller (50/50, then 90/10, then 100/0)
  • If issues are detected, shift traffic back immediately — both controllers remain live throughout
  • Once 100% traffic is on the new controller, monitor for a stability window (typically 2–7 days) before decommission

05 Decommission

  • Once all services are migrated and validated at 100% traffic, remove the old ingress-nginx Deployment and DaemonSet
  • Delete the old IngressClass resource (ingressClassName: nginx)
  • Remove unused ConfigMaps that held ingress-nginx global configuration
  • Clean up any legacy namespace and RBAC artifacts from the community controller
  • Update internal documentation, runbooks, and CI/CD pipelines to reference the new controller

6. Common Annotation-to-CRD Conversions

The following are the most commonly encountered annotation translations. The complete 130+ annotation mapping reference is available at kubernetes.nginx.org/ingress-nginx-migration.html.

6.1 Path Rewriting

Before (ingress-nginx):

annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
...
path: /api(/|$)(.*)

After (VirtualServer):

routes:
- path: ~/api(/|$)(.*)
action:
proxy:
upstream: api-svc
rewritePath: /$2

6.2 Canary / Blue-Green Deployments

Before (ingress-nginx):

annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "20"

After (VirtualServer with traffic splitting):

routes:
- path: /
splits:
- weight: 80
action:
pass: app-stable
- weight: 20
action:
pass: app-canary

6.3 Rate Limiting

Before (ingress-nginx):

annotations:
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-connections: "5"

After (Policy CRD + VirtualServer or Ingress reference):

# Policy resource
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: rate-limit-policy
spec:
rateLimit:
rate: 10r/s
burst: 20
zoneSize: 10m
key: ${binary_remote_addr}

# Attach to Ingress (v5.4.0+)
annotations:
nginx.org/policies: rate-limit-policy

6.4 mTLS Client Certificate Authentication

Before (ingress-nginx):

annotations:
nginx.ingress.kubernetes.io/auth-tls-secret: default/ca-secret
nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"

After (Policy CRD):

apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: mtls-policy
spec:
ingressMTLS:
clientCertSecret: ca-secret
verifyClient: "on"
verifyDepth: 2

7. Special Cases and Known Challenges

7.1 cert-manager with VirtualServer CRDs

cert-manager’s HTTP-01 ACME solver requires a standard Kubernetes Ingress resource to serve the challenge token. When migrating to VirtualServer CRDs, a common and well-proven pattern is retaining a minimal ‘shim’ Ingress resource with cert-manager annotations solely for certificate issuance, while all routing configuration moves to VirtualServer. Alternatively, DNS-01 challenge validation can be used to eliminate this dependency entirely.

7.2 Long-Lived Connections (WebSocket, gRPC)

WebSocket and gRPC connections that are active during a controller reload or transition may be terminated. With NGINX Plus, the dynamic reconfiguration API eliminates reload events for upstream changes, significantly reducing this risk. For the OSS tier, schedule traffic cutover during low-traffic windows and use connection draining to allow in-flight requests to complete.

7.3 TCP/UDP Service Exposure

The community controller exposed TCP/UDP services via a dedicated ConfigMap. The F5 controller replaces this with the TransportServer CRD, which offers a structured schema, per-listener timeout configuration, health checks, and TLS Passthrough. GlobalConfiguration registers the cluster-wide TCP/UDP listeners that TransportServer resources reference.

7.4 Custom Lua Extensions in ingress-nginx

Some advanced ingress-nginx behaviors were implemented via custom Lua extensions — particularly for complex load balancing algorithms and sticky session logic. These have no direct equivalent and must be re-implemented using NGINX Ingress Controller’s native capabilities: the Policy CRD for session persistence (sticky cookies are now available in the OSS tier as of v5.4.0), or custom NGINX configuration snippets for lower-level behaviors. It is worth noting that the removal of Lua also eliminates the associated memory leak and OOM crash risks.

8. Post-Migration Validation Checklist

Before decommissioning the old controller, validate each of the following:

Routing & Traffic
All host-based and path-based routing rules produce correct backend responses. SSL/TLS termination is functioning for all secured hosts. HTTP to HTTPS redirects are active where required. Load balancing distribution across replicas is confirmed. gRPC and WebSocket services remain functional.
Security Policies
Rate limiting policies enforce configured thresholds under load. mTLS is rejecting requests without valid client certificates. WAF policies (if NGINX App Protect is deployed) are blocking test payloads. JWT validation rejects invalid or expired tokens. IP allowlist/denylist rules are enforced correctly.
Observability
Prometheus metrics endpoint on the new controller is reachable. Existing dashboards have been updated for the new controller’s metric labels. NGINX Plus dashboard (if applicable) shows accurate upstream health and request rates. Alerting rules have been updated and test-fired.
Operational
cert-manager certificate renewal has been validated end-to-end. Helm upgrade and rollback procedures have been documented and tested. RBAC and namespace isolation for multi-tenant clusters are functioning as expected. CI/CD pipelines reference the new ingressClassName and CRD manifests.

9. The Road Ahead: NGINX Gateway Fabric and the Gateway API

The F5 NGINX Ingress Controller is the pragmatic migration target for today — it provides the lowest friction path away from ingress-nginx while delivering enterprise-grade capabilities. However, the Kubernetes ecosystem is actively evolving toward the Gateway API as the next-generation standard for traffic management.

The Gateway API addresses fundamental limitations of the Ingress API: role-based access control across infrastructure owners, namespace owners, and application developers; multi-protocol support as a first-class concern; and extensibility through a well-defined object model rather than annotation proliferation.

F5 NGINX’s answer to the Gateway API is NGINX Gateway Fabric — a fully conformant implementation of the Kubernetes Gateway API with control and data plane separation, multi-cloud support, traffic splitting, and mTLS. For teams migrating today, NGINX Ingress Controller provides a stable operating foundation while Gateway Fabric matures. F5 provides the ingress2gateway CLI tool to automate conversion of Ingress resources to Gateway API HTTPRoute manifests when the time comes.

Strategic Recommendation
Migrate to F5 NGINX Ingress Controller now to eliminate the security risk of running EOL software. Use this migration as an opportunity to adopt VirtualServer CRDs and Policy resources — these are architecturally aligned with Gateway API concepts, which means the eventual move to NGINX Gateway Fabric will be incremental rather than disruptive.

10. Conclusion

The end-of-maintenance of the community ingress-nginx controller is a forcing function, not just a lifecycle event. Organizations that act now — with a structured, phased migration plan — will not only eliminate security exposure but emerge with a more capable, maintainable, and future-proof Kubernetes networking layer.

The F5 NGINX Ingress Controller offers a genuinely low-friction migration path. Built on the same NGINX foundation, with a more structured and supportable Kubernetes control plane, a dedicated full-time engineering team, a published CVE lifecycle, structured CRD-based configuration, and a clear roadmap toward the Kubernetes Gateway API. For enterprises that need the full spectrum of capabilities — App Protect WAF, dynamic reconfiguration, active health checks, and commercial SLAs — NGINX Plus integration delivers a production-hardened ingress layer built for mission-critical workloads.

At Ashnik, as an authorized F5 NGINX partner operating across India and Southeast Asia, we have worked with organizations across the BFSI, telecom, and insurance sectors through Kubernetes infrastructure modernization initiatives. Our teams have hands-on experience designing and executing ingress migrations, including complex multi-tenant deployments, air-gapped cluster environments, and observability-integrated ingress architectures. We are well-positioned to help your organization plan and execute this migration — from initial audit through production cutover.


Go to Top