NGINX Ingress Migration Tool

Warning: The community-maintained kubernetes/ingress-nginx project has reached end of maintenance. v1.15.1 (March 19, 2026) is the final release: it will receive no further releases, bugfixes, or security patches. NGINX Ingress Controller (nginx/kubernetes-ingress) is actively maintained and is the recommended migration target.
Version reference: This tool is based on Ingress-NGINX v1.15.1 (final release) and NGINX Ingress Controller v5.7.0.

Overview

This interactive guide helps you migrate from the community-maintained Ingress-NGINX controller (kubernetes/ingress-nginx) to NGINX Ingress Controller (nginx/kubernetes-ingress). It covers 130+ annotation mappings, ConfigMap key translations, and CRD migration examples across three pages, listed in the sidebar:

  • Getting started: background on why to migrate, CRD overviews, installation, and a migration checklist
  • Ingress-NGINX config analyzer: paste an Ingress manifest and get automatic migration suggestions with ready-to-use YAML
  • Reference guide: complete annotation and ConfigMap mapping tables with side-by-side examples

Why migrate?

The two controllers take fundamentally different approaches to configuring NGINX in Kubernetes.

  • Ingress-NGINX is annotation-driven: it relies on annotations and ConfigMap keys to customize NGINX. These are opaque strings with no schema validation, so a single Ingress resource may carry dozens of annotations as unstructured key-value pairs.
  • NGINX Ingress Controller is CRD-native: it uses Custom Resource Definitions (VirtualServer, Policy, TransportServer) as its primary config model, providing structured, schema-validated YAML with IDE autocompletion.

While NGINX Ingress Controller still supports annotations for common settings, CRDs unlock capabilities that annotations cannot express:

  • Schema validation: Kubernetes validates CRD fields at apply time, catching misconfigurations before they reach NGINX
  • Structured configuration: nested YAML objects replace long annotation strings, making complex routing, traffic splitting, and security policies readable and maintainable
  • Reusability: Policies (rate limiting, auth, WAF) are defined once and attached to any number of routes
  • Layer 4 support: TransportServer CRDs handle TCP/UDP workloads that annotations cannot express
  • IDE and tooling support: CRD schemas enable autocompletion, linting, and documentation in editors and CI pipelines

Phased migration strategy

Rather than performing a "big bang" cutover, F5 recommends a phased migration that allows you to validate each workload before decommissioning the community controller. Both controllers can run side-by-side in the same cluster using separate IngressClass resources.

  1. Deploy side-by-side: install NGINX Ingress Controller alongside the community controller using a distinct IngressClass such as nginx-nic (see Install alongside the community controller). Existing traffic is unaffected.
  2. Migrate service-by-service: update each Ingress resource's ingressClassName to point to the new controller. Convert annotations as described in the reference guide, validate routing and TLS behavior, and promote to production before moving on to the next service.
  3. Decommission: after all workloads have been migrated and validated, remove the community controller deployment, its IngressClass, and any unused ConfigMaps.
Tip: Use the Ingress-NGINX config analyzer to process each Ingress manifest during step 2. It flags unsupported annotations and generates the equivalent NGINX Ingress Controller configuration.

Install alongside the community controller

Both Helm charts name their class nginx by default, and an IngressClass name can belong to only one controller. Give NGINX Ingress Controller its own class:

Helm (example)

helm install nginx-ingress oci://ghcr.io/nginx/charts/nginx-ingress \
  --set controller.ingressClass.name=nginx-nic \
  --set controller.config.name=nginx-config \
  --set controller.enableSnippets=true \
  --set controller.enableTLSPassthrough=true

Keep only the flags your Ingresses need:

  • controller.config.name: the examples on this page write the ConfigMap nginx-config, the name the manifests install. The chart names it after the release instead (nginx-ingress for this command) unless this is set, and NGINX Ingress Controller reads only the ConfigMap its -nginx-configmaps flag names, so keys written to any other one are silently unused.
  • controller.enableSnippets: off by default. With it off, NGINX Ingress Controller rejects any resource that carries a snippet, so configuration-snippet, server-snippet and every mapping that becomes a snippet fail. Snippets are copied as written: directives from modules the community image included, such as more_set_headers (headers-more) and Lua, do not exist in NGINX Ingress Controller and fail the configuration reload.
  • controller.enableTLSPassthrough: off by default. ssl-passthrough becomes a TransportServer on the built-in tls-passthrough listener, which exists only with this flag.
  • controller.enableCertManager: off by default. cert-manager HTTP-01 challenges for a VirtualServer's host pass its HTTPS redirect and authentication only with this flag (v5.7.0 and later); on an Ingress they do without it. The community controller exempted /.well-known/acme-challenge by default.
  • controller.enableTopologyAwareRouting: off by default, like the community controller's --enable-topology-aware-routing, and new in v5.7.0. With it on, each controller pod sends traffic to the endpoints in its own zone, or on its own node, when the Service sets spec.trafficDistribution (or the older service.kubernetes.io/topology-mode: auto), falling back to every ready endpoint otherwise.
Note: The NGINX Ingress Controller documentation covers this in Run multiple Ingress controllers and the ingress-nginx migration guide.

Defaults that change

A setting you never configured still changes if the two controllers default it differently. Set the NGINX Ingress Controller value in the nginx-config ConfigMap, or per resource, wherever the old default matters to you.

SettingIngress-NGINXNGINX Ingress ControllerWhat to check
Response bufferingOffOn (proxy-buffering)Server-sent events and streamed responses arrive in chunks. Set proxy-buffering: "false" for them.
HTTP/2OnOff (http2)gRPC needs HTTP/2. Since v5.7.0, turn it on per host with nginx.org/http2 or VirtualServer spec.http2, or for every host with http2: "true".
HSTSOnOff (hsts)Browsers stop receiving Strict-Transport-Security.
Upstream keepalive connections3200 (keepalive)Each request opens a new connection to the Pod.
Upstream connect timeout5s60s (proxy-connect-timeout)An unreachable Pod is retried later.
Load-balancing methodRound robinrandom two least_conn (lb-method)Request distribution changes.
HTTP-to-HTTPS redirect code308301 (http-redirect-code)Clients can turn a redirected POST into a GET. The analyzer sets 308.
Server header versionHiddenShown (server-tokens)Set server-tokens: "false" to hide the NGINX version.
ACME HTTP-01 challengesExempt from HTTPS redirect and authExempt for cert-manager since v5.7.0Before v5.7.0, a host with a redirect or auth Policy failed HTTP-01 renewal. A VirtualServer needs controller.enableCertManager.

Gateway API

The community project recommends that new users adopt a Gateway API implementation. NGINX Gateway Fabric is the NGINX implementation of the Gateway API, and ingress2gateway converts Ingress resources to it (--providers=ingress-nginx for community annotations). This tool covers the move to NGINX Ingress Controller, which keeps the Ingress API.

CRDs summary

NGINX Ingress Controller uses Custom Resource Definitions (CRDs) to extend Kubernetes with NGINX-specific configuration. CRDs provide type-safe, validated configuration that goes beyond what standard Ingress annotations can offer.

VirtualServer CRD

The primary CRD for configuring HTTP/HTTPS load balancing. It replaces Ingress resources and provides advanced traffic management capabilities with full validation.

  • Traffic Splitting: Route percentages of traffic to different backends for canary/blue-green deployments
  • Content-Based Routing: Route based on headers, cookies, arguments, or variables
  • Path Rewriting: Modify request URIs with regex support before forwarding to backends
  • Error Pages: Custom responses for specific HTTP error codes
  • Request/Response Headers: Add, modify, or remove headers in transit
  • Health Checks: Active health monitoring of upstream servers (NGINX Plus)
  • Session Persistence: Cookie-based sticky sessions

VirtualServerRoute CRD

Enables delegation of route configuration to different namespaces. Allows teams to manage their own routing rules while maintaining centralized control over the domain.

  • Cross-Namespace Routing: Reference services in different namespaces
  • Path Delegation: Delegate path prefixes to other teams/namespaces
  • Independent Updates: Teams update routes without modifying the parent VirtualServer
  • Access Control: Limit which namespaces can define routes for a host

Policy CRD

Reusable security and traffic policies that can be attached to VirtualServers or specific routes. Policies enable consistent security controls across multiple applications.

  • Rate Limiting: Limit request rates by IP, header, or custom key
  • Access Control: IP allowlists and denylists
  • Basic Auth: Username/password authentication via htpasswd
  • JWT Validation: Validate JSON Web Tokens (Plus)
  • OIDC: OpenID Connect sign-in, through the built-in OIDC module or NGINX JavaScript (Plus)
  • API Key Auth: Authenticate via API keys in headers/query
  • mTLS: Client certificate verification (ingress) and backend TLS (egress)
  • WAF: F5 WAF for NGINX web application firewall (Plus)

TransportServer CRD

Configures TCP/UDP load balancing for non-HTTP protocols. Essential for databases, message queues, gaming servers, and other Layer 4 applications.

  • TCP Load Balancing: MySQL, PostgreSQL, Redis, custom TCP services
  • UDP Load Balancing: DNS, RADIUS, gaming protocols
  • TLS Passthrough: Pass encrypted traffic directly to backends
  • Health Checks: TCP/UDP health monitoring (NGINX Plus; ignored on NGINX Open Source)
  • Session Persistence: IP hash-based session affinity

GlobalConfiguration CRD

Cluster-wide listeners for NGINX Ingress Controller: the TCP, UDP and HTTP ports that TransportServers and VirtualServers reference by name.

  • TCP/UDP Listeners: Define ports for non-HTTP traffic
  • HTTP Listeners: Extra HTTP or HTTPS ports a VirtualServer selects in spec.listener

Installing CRDs

CRDs can be installed via Helm or manually using manifests, depending on your deployment method. See the NGINX Ingress Controller installation docs for full instructions.

Helm (example)

If you install NGINX Ingress Controller via Helm, CRDs are included automatically. To ensure they are enabled:

helm install nginx-ingress oci://ghcr.io/nginx/charts/nginx-ingress --version 2.8.0 --set controller.enableCustomResources=true

The controller.enableCustomResources flag is true by default. If you previously disabled it, re-enable it to use CRD resources. See the Helm installation guide for details.

Manifests (example)

If you deploy using manifests, install CRDs manually before deploying the controller:

kubectl apply -f https://raw.githubusercontent.com/nginx/kubernetes-ingress/v5.7.0/deploy/crds.yaml

Replace the version number with your target controller version. See the manifest installation guide for the full step-by-step process.

Note: The crds.yaml file contains all CRDs (VirtualServer, VirtualServerRoute, Policy, TransportServer, GlobalConfiguration) in a single manifest.
Note: Replace version numbers with your target controller version.

Migration checklist

Progress is saved in your browser.
  • Install CRDs via Helm (controller.enableCustomResources=true) or manifests (deploy/crds.yaml)
  • Install with its own IngressClass (controller.ingressClass.name=nginx-nic), and turn on controller.enableSnippets or controller.enableTLSPassthrough if your Ingresses need them
  • Review the defaults that change (response buffering, HTTP/2, HSTS, keepalive, timeouts, load balancing) and set the ones that matter in nginx-config
  • Review annotation mappings: not all nginx.ingress.kubernetes.io/ annotations have a direct nginx.org/ equivalent (see NGINX mappings)
  • Map rewrite-target annotation to nginx.org/rewrite-target (not nginx.org/rewrites)
  • Migrate ConfigMap keys that differ between controllers (see ConfigMap mappings)
  • Convert HSTS ConfigMap keys to nginx.org/hsts annotations for per-Ingress control, or an hsts Policy on a VirtualServer (v5.6.0+)
  • Convert server-tokens ConfigMap key to nginx.org/server-tokens annotation
  • Convert access control annotations to Policy CRD (accessControl)
  • Convert authentication annotations to Policy CRD (apiKey, basicAuth, externalAuth, jwt, oidc, oidcNative)
  • Convert canary/traffic splitting to VirtualServer splits/matches
  • Convert mTLS annotations to ingressMTLS/egressMTLS policies
  • Convert CORS annotations to the cors Policy CRD via nginx.org/policies (v5.4.0+), falling back to snippets only where needed
  • Convert custom error pages to nginx.org/custom-http-errors with a spec.defaultBackend (v5.6.0+), or VirtualServer errorPages
  • Convert header manipulation to VirtualServer requestHeaders/responseHeaders
  • Convert rate limiting annotations to Policy CRD (rateLimit)
  • Convert redirect annotations to VirtualServer action.redirect
  • Convert session affinity annotations to VirtualServer upstream sessionCookie
  • Convert SSL passthrough to TransportServer CRD
  • Convert TCP/UDP services to TransportServer CRD
  • Replace ssl-redirect with nginx.org/ssl-redirect (not the deprecated ingress.kubernetes.io/ssl-redirect)
  • Verify snippets (configuration-snippet, server-snippet) use only directives NGINX Ingress Controller ships, with no more_set_headers or Lua
  • Test in staging environment
  • Update monitoring and alerting

Command line tool

Beta: nic-migrate is new and under active development. Review its output before applying anything to a cluster: command names, flags, and the manifests it generates may still change.

The analyzer on this page works on what you paste. nic-migrate runs the same engine from a terminal, across every Ingress in a directory or read straight from a running cluster. It loads the mapping database out of this site's own source rather than keeping a second copy, so it cannot drift from the tables in the reference guide. It is not distributed as a download; clone the repository for this site and run it with Node 18 or newer, with nothing to install.

Review what would change

Prints a summary per Ingress: annotation swaps, the Policies and CRDs required, and anything the analyzer cannot express on its own, graded blocking, review, or note.

node tools/nic-migrate/nic-migrate.js report -f ./manifests

Generate manifests

Merges those results into resources you can apply: one VirtualServer per host carrying every route and backend, with Policies referenced, TLS preserved, and the namespace kept. --target ingress instead keeps your Ingress resources and rewrites their annotations.

node tools/nic-migrate/nic-migrate.js convert -f ./manifests --validate

Neither command drops anything silently: where a construct has no equivalent on the target you choose, it is named rather than omitted. Read the notes each run prints, keep --validate on so the output is checked against a live cluster with kubectl apply --dry-run=server, and see the full option reference and known limitations.

Additional resources