NGINX Ingress Migration Tool
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.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.
- Deploy side-by-side: install NGINX Ingress Controller alongside the community controller using a distinct
IngressClasssuch asnginx-nic(see Install alongside the community controller). Existing traffic is unaffected. - Migrate service-by-service: update each Ingress resource's
ingressClassNameto 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. - Decommission: after all workloads have been migrated and validated, remove the community controller deployment, its
IngressClass, and any unused ConfigMaps.
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 ConfigMapnginx-config, the name the manifests install. The chart names it after the release instead (nginx-ingressfor this command) unless this is set, and NGINX Ingress Controller reads only the ConfigMap its-nginx-configmapsflag 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, soconfiguration-snippet,server-snippetand every mapping that becomes a snippet fail. Snippets are copied as written: directives from modules the community image included, such asmore_set_headers(headers-more) and Lua, do not exist in NGINX Ingress Controller and fail the configuration reload.controller.enableTLSPassthrough: off by default.ssl-passthroughbecomes a TransportServer on the built-intls-passthroughlistener, 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-challengeby 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 setsspec.trafficDistribution(or the olderservice.kubernetes.io/topology-mode: auto), falling back to every ready endpoint otherwise.
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.
| Setting | Ingress-NGINX | NGINX Ingress Controller | What to check |
|---|---|---|---|
| Response buffering | Off | On (proxy-buffering) | Server-sent events and streamed responses arrive in chunks. Set proxy-buffering: "false" for them. |
| HTTP/2 | On | Off (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". |
| HSTS | On | Off (hsts) | Browsers stop receiving Strict-Transport-Security. |
| Upstream keepalive connections | 320 | 0 (keepalive) | Each request opens a new connection to the Pod. |
| Upstream connect timeout | 5s | 60s (proxy-connect-timeout) | An unreachable Pod is retried later. |
| Load-balancing method | Round robin | random two least_conn (lb-method) | Request distribution changes. |
| HTTP-to-HTTPS redirect code | 308 | 301 (http-redirect-code) | Clients can turn a redirected POST into a GET. The analyzer sets 308. |
Server header version | Hidden | Shown (server-tokens) | Set server-tokens: "false" to hide the NGINX version. |
| ACME HTTP-01 challenges | Exempt from HTTPS redirect and auth | Exempt for cert-manager since v5.7.0 | Before 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.
crds.yaml file contains all CRDs (VirtualServer, VirtualServerRoute, Policy, TransportServer, GlobalConfiguration) in a single manifest.Migration checklist
- Install CRDs via Helm (
controller.enableCustomResources=true) or manifests (deploy/crds.yaml) - Install with its own
IngressClass(controller.ingressClass.name=nginx-nic), and turn oncontroller.enableSnippetsorcontroller.enableTLSPassthroughif 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 directnginx.org/equivalent (see NGINX mappings) - Map rewrite-target annotation to
nginx.org/rewrite-target(notnginx.org/rewrites) - Migrate ConfigMap keys that differ between controllers (see ConfigMap mappings)
- Convert HSTS ConfigMap keys to
nginx.org/hstsannotations for per-Ingress control, or anhstsPolicy on a VirtualServer (v5.6.0+) - Convert
server-tokensConfigMap key tonginx.org/server-tokensannotation - 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/egressMTLSpolicies - Convert CORS annotations to the
corsPolicy CRD vianginx.org/policies(v5.4.0+), falling back to snippets only where needed - Convert custom error pages to
nginx.org/custom-http-errorswith aspec.defaultBackend(v5.6.0+), or VirtualServererrorPages - 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-redirectwithnginx.org/ssl-redirect(not the deprecatedingress.kubernetes.io/ssl-redirect) - Verify snippets (
configuration-snippet,server-snippet) use only directives NGINX Ingress Controller ships, with nomore_set_headersor Lua - Test in staging environment
- Update monitoring and alerting
Command line tool
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
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.Ingress-NGINX config analyzer
Paste a Kubernetes Ingress YAML manifest that uses Ingress-NGINX annotations to get migration suggestions for NGINX Ingress Controller.
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.Key differences
| Aspect | Ingress-NGINX | NGINX Ingress Controller |
|---|---|---|
| Repository | kubernetes/ingress-nginx | nginx/kubernetes-ingress |
| Data plane | NGINX, built with OpenResty's Lua modules | NGINX Open Source or NGINX Plus |
| Annotation prefix | nginx.ingress.kubernetes.io/ | nginx.org/ or nginx.com/ or appprotect.f5.com/ |
| ConfigMap name | ingress-nginx-controller | nginx-config |
| CRD support | None | VirtualServer, VirtualServerRoute, Policy, TransportServer, GlobalConfiguration |
| Enterprise support | None | NGINX Plus (F5 WAF for NGINX, F5 DoS for NGINX, JWT, OIDC, active health checks, zone sync) |
proxy-http-version maps to nginx.org/proxy-http-version or a VirtualServer upstream's proxy-http-version instead of a snippet, and HTTP/2 to upstreams joins it. gRPC turns HTTP/2 on per host with nginx.org/http2 or spec.http2, with or without TLS. opentelemetry-trust-incoming-span maps to the otel-trace-context ConfigMap key, and the community's ewma load balancing to least_time last_byte, now on NGINX Open Source. An Opaque Secret works wherever a CA, TLS, or htpasswd Secret did, so the community controller's Secrets can be used as they are; basic auth still needs its key renamed from auth to htpasswd. The use-forwarded-headers ConfigMap key keeps the forwarded headers a proxy in front has set. cert-manager HTTP-01 challenges pass the HTTPS redirect and authentication, as the community controller's defaults let them, and -enable-topology-aware-routing arrives with the community flag's name.nginx.org/custom-http-errors serves intercepted errors from the Ingress's spec.defaultBackend, and nginx.org/upstream-vhost sets the Host header sent upstream. A new hsts Policy CRD sets HSTS on a VirtualServer, including preload on request. With NGINX Plus, the new oidcNative Policy runs OpenID Connect in the built-in module and attaches to Ingress through nginx.com/policies.externalAuth Policy CRD replaces the community's generic auth_request annotations (auth-url, auth-signin, auth-snippet), the headline migration win of this release. The ingressMTLS, egressMTLS, and waf (Plus, via nginx.com/policies) Policy CRDs can now also be attached directly to Ingress resources. New direct annotation mappings arrived for proxy-redirect-from/proxy-redirect-to, alongside a new inline nginx.org/add-header (plus nginx.org/add-header-inherit) for response header injection.accessControl, cors) can now be referenced directly from Ingress resources using the nginx.org/policies annotation, with no VirtualServer required. This is the recommended migration path for features that support it. Look for the (Recommended) tab in the mapping examples.NGINX mappings
These features are available in both NGINX Open Source and NGINX Plus editions. All items are annotations unless otherwise indicated with a badge (e.g. ConfigMap, Policy CRD, VirtualServer CRD). Community annotations use the nginx.ingress.kubernetes.io/ prefix; NGINX Ingress Controller annotations use nginx.org/.
- Annotation-to-annotation rows ▶: Direct mappings where you change the annotation name/prefix. Click to expand and see before/after Ingress YAML examples.
- CRD-based rows ▶: Annotations that map to a CRD (VirtualServer, Policy, or TransportServer). Click to expand for full CRD examples with installation instructions.
- Grouped annotations: Related annotations that work together are grouped in a single expandable row, showing how they map to a unified configuration.
- NGINX Ingress Controller-only rows: Rows marked No direct equivalent on the left are features unique to NGINX Ingress Controller with no community equivalent.
Access control
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/denylist-source-rangenginx.ingress.kubernetes.io/whitelist-source-range |
Policy CRD accessControl.allow / accessControl.deny + nginx.org/policies New v5.4.0— or —Policy CRD accessControl.allow / accessControl.deny + VirtualServer policies[] |
Ingress-NGINXNGINX Ingress ControllerAllow plus deny: When a resource references an allow and a deny Policy, NGINX Ingress Controller applies only the allow list. The community controller checked the deny list first, so an address inside an allowed range could still be denied. NGINX takes the first
allow or deny that matches, and a location with rules of its own inherits none from its server, where a Policy in spec.policies or nginx.org/policies renders. So never pair a location-snippet deny with such a Policy: it lifts the allow list for every client (verified on nginx 1.31.7: an address outside it got 200). Put the denies first in the same location instead, as above. Denied ranges that overlap no allowed range need nothing, because allow-only already blocks them. Snippets need the controller's -enable-snippets flag.New in v5.4.0: AccessControl policies can now be referenced directly from Ingress resources using the
nginx.org/policies annotation. This is the simplest migration path: you stay on Ingress resources.Ingress-NGINXNGINX Ingress ControllerAllow plus deny: When a resource references an allow and a deny Policy, NGINX Ingress Controller applies only the allow list. The community controller checked the deny list first, so an address inside an allowed range could still be denied. NGINX takes the first
allow or deny that matches, and a location with rules of its own inherits none from its server, where a Policy in spec.policies or nginx.org/policies renders. So never pair a location-snippet deny with such a Policy: it lifts the allow list for every client (verified on nginx 1.31.7: an address outside it got 200). Put the denies first in the same location instead, as above. Denied ranges that overlap no allowed range need nothing, because allow-only already blocks them. Snippets need the controller's -enable-snippets flag.Note: The VirtualServer approach has been available since before v5.4.0. Use this if you are already migrating to VirtualServer resources for other reasons (e.g., advanced routing, traffic splitting).
| |
nginx.ingress.kubernetes.io/satisfy |
VirtualServer CRD routes[].location-snippets— or —nginx.org/location-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
Authentication (API key)
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
| No direct equivalent | Policy CRD apiKey.suppliedIn / apiKey.clientSecret |
Ingress-NGINXNGINX Ingress ControllerNote: The
apiKey Policy works with NGINX Open Source, unlike the jwt, oidc, and waf Policies, it is not gated on NGINX Plus. Attach it to a VirtualServer or VirtualServerRoute via spec.policies (the apiKey Policy is not attachable to an Ingress via nginx.org/policies). | |
Authentication (basic)
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/auth-realmnginx.ingress.kubernetes.io/auth-secretnginx.ingress.kubernetes.io/auth-secret-typenginx.ingress.kubernetes.io/auth-type: "basic" |
Policy CRD basicAuth.secret / basicAuth.realm— or —nginx.org/basic-auth-realmnginx.org/basic-auth-secret |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller
Note: NGINX Ingress Controller reads one htpasswd file from the Secret's
htpasswd key, in the resource's own namespace. The community controller read it from the
auth key (auth-secret-type: auth-file), or one user per key (auth-map).
The analyzer gives the kubectl command that adds the htpasswd key for either
format, keeping the existing keys so the community controller still reads the Secret, or copies the Secret
into the resource's namespace when auth-secret names another one.
Since v5.7.0 the Secret can stay Opaque; type: nginx.org/htpasswd is no longer required. The auth-type
annotation is also implicit: NGINX Ingress Controller only supports basic auth
via these annotations.
| |
Authentication (external)
externalAuth Policy CRD is the recommended migration path for the community's generic auth_request pattern (auth-url, auth-signin, auth-snippet). Unlike the OIDC Policies (oidcNative and oidc, NGINX Plus only), externalAuth works in NGINX Open Source and NGINX Plus and can be attached directly to Ingress, VirtualServer, and VirtualServerRoute resources via nginx.org/policies. For OIDC itself, see the OIDC authentication section in NGINX Plus mappings.| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/auth-always-set-cookie |
VirtualServer CRD routes[].location-snippets— or —nginx.org/location-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: NGINX Ingress Controller adds the
auth_request directive for the externalAuth Policy itself, so the snippet carries only the cookie lines. It does not pass the auth service's Set-Cookie on by itself, as the community controller did for every auth-url; always sends it with every response code, which is what auth-always-set-cookie added. The auth service is reached through a Service: auth.example.com lies outside the cluster, so auth-svc stands for an ExternalName Service in front of it, which NGINX Ingress Controller supports only with NGINX Plus. On NGINX Open Source, run the auth service in the cluster and name its Service. Snippets need the controller's -enable-snippets flag. (The Plus-only oidc Policy handles cookies internally.) | |
nginx.ingress.kubernetes.io/auth-cache-durationnginx.ingress.kubernetes.io/auth-cache-keynginx.ingress.kubernetes.io/auth-methodnginx.ingress.kubernetes.io/auth-proxy-set-headersnginx.ingress.kubernetes.io/auth-request-redirectnginx.ingress.kubernetes.io/auth-response-headersnginx.ingress.kubernetes.io/auth-signinnginx.ingress.kubernetes.io/auth-signin-redirect-paramnginx.ingress.kubernetes.io/auth-snippetnginx.ingress.kubernetes.io/auth-url |
Policy CRD externalAuth.* + nginx.org/policies New v5.5.0— or —Policy CRD externalAuth.* + VirtualServer policies[] New v5.5.0 |
Ingress-NGINXNGINX Ingress ControllerNew in v5.5.0: The
externalAuth Policy CRD can be referenced directly from Ingress resources using the nginx.org/policies annotation. This is the simplest migration path: you stay on Ingress resources. auth-url is split into authURI (path) and authServiceName (<namespace>/<service>). The community's auth-method, auth-response-headers, auth-cache-duration, auth-cache-key, auth-proxy-set-headers, auth-request-redirect, and auth-signin-redirect-param annotations have no dedicated Policy fields; express these through authSnippets when needed. If you are not adopting a Policy, place the same directives directly in nginx.org/location-snippets alongside the auth_request directive instead. NGINX Ingress Controller's authSigninURI is a relative URI (validated ^/.*$): a full auth-signin URL is reduced to its path, and a sign-in page on a different host cannot be expressed by this field. NGINX Ingress Controller proxies sign-in requests to the auth Service from a location at authSigninRedirectBasePath (default /oauth2); never set it to / or to a path a route already uses, because NGINX rejects the duplicate location. The analyzer sets it to the sign-in path's parent directory, or to the path itself when that parent is /, as for /login here.Ingress-NGINXNGINX Ingress ControllerNote: Use this if you are already migrating to VirtualServer/VirtualServerRoute resources for other reasons (e.g., advanced routing, traffic splitting). Policies can also be scoped per-route via the route-level
policies[] field. | |
nginx.ingress.kubernetes.io/auth-keepalivenginx.ingress.kubernetes.io/auth-keepalive-requestsnginx.ingress.kubernetes.io/auth-keepalive-share-varsnginx.ingress.kubernetes.io/auth-keepalive-timeout |
No direct equivalent |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNo direct equivalent: The community controller's
auth-keepalive, auth-keepalive-requests, auth-keepalive-share-vars, and auth-keepalive-timeout annotations kept a dedicated pool of connections to the external auth service open between checks. NGINX Ingress Controller has no setting for the auth Service alone. On a VirtualServer it opens a new connection for each check; on an Ingress, the auth Service shares the Ingress's nginx.org/keepalive (or the ConfigMap's keepalive) with every other upstream. Nothing replaces auth-keepalive-requests, auth-keepalive-timeout, or auth-keepalive-share-vars. Expect more connections to the auth Service, and size it for that.Note: The auth service is reached through a Service:
auth.example.com lies outside the cluster, so auth-svc stands for an ExternalName Service in front of it, which NGINX Ingress Controller supports only with NGINX Plus. On NGINX Open Source, run the auth service in the cluster and name its Service. The externalAuth Policy is covered in the row above. | |
nginx.ingress.kubernetes.io/enable-global-auth |
Not applicable |
Ingress-NGINXNGINX Ingress ControllerNote: The community controller supports global external authentication via ConfigMap keys (
global-auth-url, etc.). The enable-global-auth: "false" annotation opts an Ingress out of this global setting. NGINX Ingress Controller does not have global external auth: authentication is always configured per-resource (via the externalAuth/oidc/jwt Policy CRDs or location-snippets). This annotation can be safely removed during migration. | |
Buffering
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/client-body-buffer-size |
VirtualServer CRD upstreams[].client-body-buffer-size— or —nginx.org/client-body-buffer-size |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/proxy-body-size |
VirtualServer CRD upstreams[].client-max-body-size— or —nginx.org/client-max-body-size |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/proxy-buffer-size |
VirtualServer CRD upstreams[].buffer-size— or —nginx.org/proxy-buffer-size |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: The community controller sizes its 4 proxy buffers with
proxy-buffer-size too. NGINX Ingress Controller does not, and NGINX rejects a buffer size larger than its default buffers allow, so the migration also sets the buffers. | |
nginx.ingress.kubernetes.io/proxy-buffering |
VirtualServer CRD upstreams[].buffering— or —nginx.org/proxy-buffering |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote:
nginx.org/proxy-buffering takes true or false. NGINX Ingress Controller rejects the Ingress for on or off, the values the community controller uses. | |
nginx.ingress.kubernetes.io/proxy-buffers-number |
VirtualServer CRD upstreams[].buffers— or —nginx.org/proxy-buffers |
Ingress-NGINXNGINX Ingress ControllerNote: The NGINX Ingress Controller annotation uses a combined format:
"number size" (e.g., "4 8k"). The size is proxy-buffer-size, as on the community controller, or its 4k default.Ingress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/proxy-busy-buffers-size |
VirtualServer CRD upstreams[].busy-buffers-size— or —nginx.org/proxy-busy-buffers-size |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/proxy-max-temp-file-size |
nginx.org/proxy-max-temp-file-size |
Ingress-NGINXNGINX Ingress ControllerNote: VirtualServer has no field for
proxy_max_temp_file_size, so on a VirtualServer it is a location snippet, which needs the controller's -enable-snippets flag. To set it for every resource at once, use the proxy-max-temp-file-size key in the nginx-config ConfigMap instead. | |
nginx.ingress.kubernetes.io/proxy-request-buffering |
VirtualServer CRD routes[].location-snippets— or —nginx.org/location-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: NGINX Ingress Controller does not have a dedicated
proxy-request-buffering annotation or ConfigMap key. Use location-snippets to set the proxy_request_buffering directive. | |
Canary / traffic splitting
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/canarynginx.ingress.kubernetes.io/canary-by-cookienginx.ingress.kubernetes.io/canary-by-headernginx.ingress.kubernetes.io/canary-by-header-patternnginx.ingress.kubernetes.io/canary-by-header-valuenginx.ingress.kubernetes.io/canary-weightnginx.ingress.kubernetes.io/canary-weight-total |
VirtualServer CRD splits[] + VirtualServer CRD matches[] |
Ingress-NGINXNGINX Ingress ControllerNote: A canary Ingress names only the canary Service, so the stable upstream comes from the main Ingress for the same host and path. Paste both into the analyzer and it merges them into one VirtualServer; pasted alone, the stable Service is left for you to fill in. The community controller ignores
canary-by-header-pattern when canary-by-header-value is set; on its own, the pattern becomes a regex match value (~^(test|staging)$). canary-weight-total takes effect only above 100: the community controller split a smaller total out of 100, so canary-weight: "10" with a total of 50 sent 10% to the canary, not 20%. | |
Configuration snippets
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/configuration-snippet |
VirtualServer CRD routes[].location-snippets— or —nginx.org/location-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/server-snippet |
VirtualServer CRD spec.server-snippets— or —nginx.org/server-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/stream-snippet |
ConfigMap stream-snippets— or —GlobalConfiguration CRD +TransportServer CRD |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: The analyzer moves the snippet as-is into the
stream-snippets ConfigMap key, which, like http-snippets, does not need the -enable-snippets flag. Its upstreams name Services by DNS, which NGINX resolves once at reload, so a Pod change is not picked up until the next one. A TransportServer behind a GlobalConfiguration listener tracks the Pod endpoints instead, which makes it the long-term form. | |
CORS / header manipulation
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/cors-allow-credentialsnginx.ingress.kubernetes.io/cors-allow-headersnginx.ingress.kubernetes.io/cors-allow-methodsnginx.ingress.kubernetes.io/cors-allow-originnginx.ingress.kubernetes.io/cors-expose-headersnginx.ingress.kubernetes.io/cors-max-agenginx.ingress.kubernetes.io/enable-cors |
Policy CRD cors.* + nginx.org/policies New v5.4.0— or —VirtualServer CRD routes[].action.proxy.responseHeaders— or —nginx.org/server-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller — Policy CRDNew in v5.4.0: The CORS Policy CRD is the recommended migration path. Create a reusable Policy and reference it via
nginx.org/policies on Ingress or VirtualServer. The analyzer emits this Policy under either strategy; the other two tabs are alternatives you write by hand.Ingress-NGINXNGINX Ingress ControllerRecommended: The
responseHeaders approach provides a fully declarative way to manage CORS and other response headers without raw NGINX snippets. Headers are set per-route via action.proxy.responseHeaders, and the always flag ensures headers are added regardless of response status code. See the VirtualServer CRD docs for additional options like hide, ignore, and pass. | |
Deprecated
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/http2-push-preload |
Not supported |
Ingress-NGINXNGINX Ingress ControllerNote: HTTP/2 Server Push (
http2_push_preload) was removed from NGINX in version 1.25.1 after major browsers deprecated the feature. This annotation can be safely removed during migration. | |
Error handling
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/custom-http-errorsnginx.ingress.kubernetes.io/default-backend |
nginx.org/custom-http-errors New v5.6.0 + spec.defaultBackend— or —VirtualServer CRD errorPages[] |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerRecommended: Annotation Approach. This is the one construct in this guide where the annotation is preferred over the CRD.
nginx.org/custom-http-errors serves error pages from a Service, as the community controller does, while VirtualServer errorPages can only return a canned response or redirect to a URL. Under CRD-first the analyzer keeps this Ingress an Ingress with the annotation, and says why; the CRD tab is what a VirtualServer can do instead, and what nic-migrate convert --target virtualserver emits.Not identical to the community controller: NGINX Ingress Controller sends intercepted errors only to the Ingress's own
spec.defaultBackend, which must be in the same namespace, and none of the community's X-Code, X-Format, or X-Original-URI headers reach it, so error pages that branch on them need rework. Clients receive the original upstream status. On its own, default-backend has no annotation equivalent for its fallback when a Service has no ready endpoints; on NGINX Plus, VirtualServer upstreams[].backup is the closest match.New in v5.6.0:
nginx.org/custom-http-errors intercepts upstream responses with the listed status codes and serves them from the Ingress's spec.defaultBackend. It takes codes from 300 to 599 plus the 4xx and 5xx shorthands. NGINX Ingress Controller rejects the Ingress for an invalid code, including 499, which NGINX refuses in error_page. Without a spec.defaultBackend the annotation does nothing. The default-backend annotation names only a Service, so set the port in spec.defaultBackend to the one that Service serves on; the community controller used its first port. | |
Headers
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/connection-proxy-header |
VirtualServer CRD upstreams[].keepalive— or —nginx.org/keepalive |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: NGINX Ingress Controller sets the
Connection header itself, from upstream keepalive: empty when keepalive is on, close when it is off. So keep-alive becomes keepalive on (320 connections, the community default) and close becomes 0. Setting the header directly, with a snippet or requestHeaders, sends both values to the backend. | |
nginx.ingress.kubernetes.io/custom-headers |
VirtualServer CRD responseHeaders.add |
Ingress-NGINXNGINX Ingress ControllerNote: The headers live in the ConfigMap the annotation names, which the analyzer cannot read. It writes a placeholder header and gives the
kubectl command that prints each key and value of that ConfigMap, ready to replace it. For the ConfigMap above, those are X-Frame-Options: DENY and X-Content-Type-Options: nosniff. The community controller sent them with more_set_headers, on every response code, so each header keeps always: true. To keep an Ingress instead, use nginx.org/add-header, with always as a third field: X-Frame-Options: DENY: always. | |
ConfigMap server-tokens |
nginx.org/server-tokens |
Ingress-NGINXNGINX Ingress ControllerNote: The Community controller configures server tokens via ConfigMap only. NGINX Ingress Controller provides a per-Ingress annotation.
| |
nginx.ingress.kubernetes.io/upstream-vhostnginx.ingress.kubernetes.io/x-forwarded-prefix |
VirtualServer CRD requestHeaders.set— or —nginx.org/proxy-set-headersnginx.org/upstream-vhost New v5.6.0 |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerLiteral values only on the annotation path:
nginx.org/upstream-vhost accepts a lowercase hostname with no port and no NGINX variables, and nginx.org/proxy-set-headers rejects $ in a value. NGINX Ingress Controller rejects the whole Ingress otherwise. For a port or a variable, use the CRD approach, which writes variables in braces (${host}). A capture group such as $1 in x-forwarded-prefix has no equivalent in either.New in v5.6.0:
nginx.org/upstream-vhost sets the Host header sent upstream (grpc_set_header for gRPC Services); minion Ingresses inherit it from their master. Don't also set Host in nginx.org/proxy-set-headers: that writes a second directive and NGINX Ingress Controller warns on the Ingress. | |
| No direct equivalent | nginx.org/add-header New v5.5.0nginx.org/add-header-inherit New v5.5.0 |
Ingress-NGINXNGINX Ingress ControllerNew in v5.5.0: The
nginx.org/add-header annotation adds response headers inline using a comma-separated Name: Value list, with no snippet or external ConfigMap reference required. The nginx.org/add-header-inherit annotation controls how the directive is inherited from outer contexts: on (default: inherit), off (do not inherit), or merge (combine). Both are also available as ConfigMap keys (add-header and add-header-inherit) for global defaults. | |
| No direct equivalent | nginx.org/proxy-set-headers |
Ingress-NGINXNGINX Ingress ControllerNote:
nginx.org/proxy-set-headers takes a comma-separated list of headers inline, each written Name: value. A name without a value forwards the client's own header of that name (X-Request-Source above sends $http_x_request_source). Values are literal: NGINX Ingress Controller rejects the Ingress for a $, a newline, or an unescaped quote in one. Set the upstream Host with nginx.org/upstream-vhost instead. | |
HSTS
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
ConfigMap hstsConfigMap hsts-include-subdomainsConfigMap hsts-max-ageConfigMap hsts-preload |
Policy CRD hsts New v5.6.0— or —nginx.org/hstsnginx.org/hsts-include-subdomainsnginx.org/hsts-max-age |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: The community controller sets HSTS only controller-wide, through its ConfigMap. NGINX Ingress Controller sets it per resource. The
nginx.org/hsts annotation always adds the preload directive, so the community hsts-preload key has no separate annotation.New in v5.6.0: The
hsts Policy CRD adds preload only when asked, which needs includeSubDomains: true and a maxAge of at least 31536000. Reference it from a VirtualServer's spec.policies; NGINX Ingress Controller returns a 500 for an hsts Policy on a route, a VirtualServerRoute, or an Ingress. It needs TLS on the VirtualServer, or behindProxy: true when TLS terminates upstream. | |
| No direct equivalent | nginx.org/hsts-behind-proxy |
Ingress-NGINXNGINX Ingress ControllerNote:
nginx.org/hsts-behind-proxy controls whether HSTS headers are added when NGINX sits behind a proxy or load balancer. When true, HSTS headers are added regardless of the connection protocol. Only NGINX Ingress Controller has it; the community controller has no equivalent. | |
Ingress configuration
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
| Several Ingresses on one host | VirtualServer CRD One VirtualServer with every path— or —One Ingress with every path |
One host, one resource: The community controller served every Ingress that names a host from one server block. NGINX Ingress Controller gives a host to one resource and rejects a second that claims it, so the analyzer merges the Ingresses of a host into one resource. What applies to the whole host (TLS, HTTP/2, server snippets, an
ingressMTLS Policy) comes from one Ingress: where two set it differently, the community controller kept the oldest Ingress's, and so does the analyzer. An Ingress that routes several hosts is split into one per host first.Ingress-NGINXNGINX Ingress ControllerNote: Each Ingress keeps its own upstreams, so a setting such as the
proxy-read-timeout above stays with api, and its Policies go on its own routes. When one Ingress on the host sets rewrite-target or use-regex, the community controller matched every path of the host as a regular expression, longest first, and the VirtualServer lists its routes in that order.Ingress-NGINXNGINX Ingress ControllerSettings become host-wide: In one Ingress, only the per-Service annotations (
nginx.org/ssl-services, nginx.org/grpc-services, nginx.org/websocket-services, nginx.org/rewrites, nginx.org/sticky-cookie-services) stay with the Service they name. Every other setting covers every path, so the proxy-read-timeout above, set only on api, applies to web too; the community controller scoped it to api's paths. A rewrite-target or use-regex on one Ingress rewrites or matches the other's paths as well. The analyzer names each setting that changes scope. To keep them per Ingress, use the VirtualServer approach, where each Ingress keeps its own upstreams.A resource per team: NGINX Ingress Controller can also delegate a host's paths to VirtualServerRoutes, or to mergeable minion Ingresses under a master, and NGINX Gateway Fabric attaches several HTTPRoutes to one Gateway listener. Each gives a team a resource of its own, and each needs setting up with the result in mind: regex paths in different VirtualServerRoutes, for one, are not tried longest first. The analyzer does not emit them.
| |
Load balancing
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/load-balance |
VirtualServer CRD upstreams[].lb-method— or —nginx.org/lb-method |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: Supported methods differ. NGINX Ingress Controller supports:
round_robin, least_conn, ip_hash, hash, random, random two, random two least_conn, and, since v5.7.0 on NGINX Open Source too, least_time header and least_time last_byte (each optionally with inflight). The community controller's ewma has no method of that name; least_time last_byte is the closest: ewma sent each request to the backend with the lowest decayed average of connect and response time, and least_time last_byte picks by average time to the full response, weighing active connections too. The NGINX Ingress Controller's default is random two least_conn (not round_robin), so load balancing behavior may change even without explicit configuration. | |
nginx.ingress.kubernetes.io/service-upstream |
VirtualServer CRD upstreams[].use-cluster-ip— or —nginx.org/use-cluster-ip |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/upstream-hash-bynginx.ingress.kubernetes.io/upstream-hash-by-subsetnginx.ingress.kubernetes.io/upstream-hash-by-subset-size |
VirtualServer CRD upstreams[].lb-method— or —nginx.org/lb-method |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: NGINX Ingress Controller has no dedicated
upstream-hash-by equivalent. Instead, the hash key is specified as part of the lb-method value (e.g., hash $request_uri consistent). Subset hashing (upstream-hash-by-subset) is not supported; use consistent hashing for similar distribution. | |
Logging
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/enable-access-log |
ConfigMap access-log-off |
Ingress-NGINXNGINX Ingress ControllerNote: The community
enable-access-log annotation is per-Ingress (disable access logging for a single Ingress), but NGINX Ingress Controller's access-log-off is a global ConfigMap key applied to the whole controller; there is no per-Ingress equivalent. | |
nginx.ingress.kubernetes.io/enable-rewrite-log |
VirtualServer CRD routes[].location-snippets— or —nginx.org/location-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: NGINX Ingress Controller does not have a dedicated
rewrite-log ConfigMap key. Use location-snippets to enable the rewrite_log directive. Rewrite logging outputs to the error log at notice level. | |
ModSecurity / WAF
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/enable-modsecuritynginx.ingress.kubernetes.io/enable-owasp-core-rulesnginx.ingress.kubernetes.io/modsecurity-snippetnginx.ingress.kubernetes.io/modsecurity-transaction-id |
No direct replacement on NGINX Open Source. See WAF (NGINX Plus) |
Ingress-NGINXNGINX Ingress ControllerNo NGINX Open Source equivalent: NGINX Ingress Controller does not include ModSecurity or any built-in WAF module. For WAF capabilities, F5 WAF for NGINX is available with NGINX Plus; see the WAF section in NGINX Plus mappings for migration examples.
Note: The community controller's ModSecurity support relies on a third-party module compiled into the controller image; it is not a native Kubernetes-integrated WAF solution. Neither controller provides an open-source WAF alternative.
| |
mTLS (backend/egress)
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/proxy-ssl-ciphersnginx.ingress.kubernetes.io/proxy-ssl-namenginx.ingress.kubernetes.io/proxy-ssl-protocolsnginx.ingress.kubernetes.io/proxy-ssl-secretnginx.ingress.kubernetes.io/proxy-ssl-server-namenginx.ingress.kubernetes.io/proxy-ssl-verifynginx.ingress.kubernetes.io/proxy-ssl-verify-depth |
Policy CRD egressMTLS.* + nginx.org/policies New v5.5.0— or —Policy CRD egressMTLS.* + VirtualServer policies[] |
Ingress-NGINXNGINX Ingress ControllerSecrets and upstream TLS: The community controller read the client certificate and the CA from one
proxy-ssl-secret. NGINX Ingress Controller reads tls.crt and tls.key for tlsSecret and ca.crt for trustedCertSecret, by key rather than by Secret type since v5.7.0, so both fields can name that same Secret, in the Policy's namespace. The Policy applies only to an upstream that already speaks TLS, which backend-protocol: "HTTPS" sets up as nginx.org/ssl-services or upstream tls.enable.New in v5.5.0: The
egressMTLS Policy CRD can now be referenced directly from Ingress resources using the nginx.org/policies annotation. This is the simplest migration path: you stay on Ingress resources. Combine with nginx.org/ssl-services to identify which backend services should use TLS.Ingress-NGINXNGINX Ingress ControllerSecrets and upstream TLS: The community controller read the client certificate and the CA from one
proxy-ssl-secret. NGINX Ingress Controller reads tls.crt and tls.key for tlsSecret and ca.crt for trustedCertSecret, by key rather than by Secret type since v5.7.0, so both fields can name that same Secret, in the Policy's namespace. The Policy applies only to an upstream that already speaks TLS, which backend-protocol: "HTTPS" sets up as nginx.org/ssl-services or upstream tls.enable.Note: The VirtualServer approach has been available since before v5.5.0. TLS to upstream is enabled per-upstream via
tls.enable: true (replacing the Ingress-level nginx.org/ssl-services annotation). | |
mTLS (client certificate verification)
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/auth-tls-error-pagenginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstreamnginx.ingress.kubernetes.io/auth-tls-secretnginx.ingress.kubernetes.io/auth-tls-verify-clientnginx.ingress.kubernetes.io/auth-tls-verify-depth |
Policy CRD ingressMTLS.* + nginx.org/policies New v5.5.0— or —Policy CRD ingressMTLS.* + VirtualServer policies[] |
Ingress-NGINXNGINX Ingress ControllerCA Secret and TLS: NGINX Ingress Controller reads the CA from the Secret's
ca.crt key, as the community controller did, so the same Secret works; since v5.7.0 it can stay Opaque rather than type nginx.org/ca. It must be in the Policy's namespace, so drop the ns/ prefix. The Policy takes effect only on a host that terminates TLS: without it, NGINX Ingress Controller answers every request with 500. The community controller sent the ssl-client-verify, ssl-client-subject-dn, and ssl-client-issuer-dn headers whenever a CA was set, and ssl-client-cert only with auth-tls-pass-certificate-to-upstream, so the snippet carries all four. Snippets need the controller's -enable-snippets flag.New in v5.5.0: The
ingressMTLS Policy CRD can now be referenced directly from Ingress resources using the nginx.org/policies annotation. This is the simplest migration path: you stay on Ingress resources.Ingress-NGINXNGINX Ingress ControllerCA Secret and TLS: NGINX Ingress Controller reads the CA from the Secret's
ca.crt key, as the community controller did, so the same Secret works; since v5.7.0 it can stay Opaque rather than type nginx.org/ca. It must be in the Policy's namespace, so drop the ns/ prefix. The Policy takes effect only on a host that terminates TLS: without it, NGINX Ingress Controller answers every request with 500. The community controller sent the ssl-client-verify, ssl-client-subject-dn, and ssl-client-issuer-dn headers whenever a CA was set, and ssl-client-cert only with auth-tls-pass-certificate-to-upstream, so the snippet carries all four. Snippets need the controller's -enable-snippets flag.Note: The VirtualServer approach has been available since before v5.5.0. Use this if you are already migrating to VirtualServer resources for other reasons (e.g., advanced routing, traffic splitting).
| |
nginx.ingress.kubernetes.io/auth-tls-match-cn |
VirtualServer CRD routes[].location-snippets— or —nginx.org/location-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: The NGINX Ingress Controller's
ingressMTLS Policy does not have a built-in CN matching feature. Use location-snippets to add custom CN validation logic. | |
OpenTelemetry
enable-opentelemetry, opentelemetry-trust-incoming-span). NGINX Ingress Controller configures OpenTelemetry globally via ConfigMap: tracing applies to all routes when enabled. Per-Ingress toggling is not supported.| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/enable-opentelemetry |
ConfigMap otel-trace-in-http + otel-trace-context New v5.7.0 |
Ingress-NGINXNGINX Ingress ControllerNote:
otel-trace-in-http is the direct replacement for enable-opentelemetry, but only with otel-exporter-endpoint set: without it, NGINX Ingress Controller reports the ConfigMap invalid and leaves tracing off. otel-service-name keeps the community controller's nginx; without it, NGINX reports unknown_service:nginx. otel-exporter-header-name and otel-exporter-header-value are optional. The community controller continued an incoming trace and passed it on; otel-trace-context: propagate keeps that, where NGINX Ingress Controller would otherwise ignore the trace headers. Unlike the community annotation, this enables tracing globally, not per Ingress, and there is no per-Ingress opt-out. | |
nginx.ingress.kubernetes.io/opentelemetry-trust-incoming-span |
ConfigMap otel-trace-context New v5.7.0 |
Ingress-NGINXNGINX Ingress ControllerNew in v5.7.0:
otel-trace-context sets how NGINX handles W3C trace context: propagate continues an incoming trace and passes it on, which is what the community controller did by default; inject starts a new trace and passes that on, the closest match to opentelemetry-trust-incoming-span: "false". It needs otel-exporter-endpoint, and it is global: one value for every Ingress and VirtualServer, where the community annotation was per Ingress. | |
| No direct equivalent | ConfigMap otel-exporter-endpointConfigMap otel-exporter-header-nameConfigMap otel-exporter-header-valueConfigMap otel-service-name |
Ingress-NGINXNGINX Ingress ControllerNote: These ConfigMap keys exist only in NGINX Ingress Controller and configure the OpenTelemetry exporter.
otel-exporter-endpoint is required when tracing is enabled. otel-service-name sets the service name in traces; without it, NGINX reports unknown_service:nginx. otel-exporter-header-name and otel-exporter-header-value add custom headers to exporter requests (e.g., for authentication). | |
Passive health checks
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
| No direct equivalent | VirtualServer CRD upstreams[].fail-timeoutVirtualServer CRD upstreams[].max-fails— or —nginx.org/fail-timeoutnginx.org/max-fails |
Ingress-NGINXNGINX Ingress ControllerNote: The community controller has no passive health check tuning: its load balancer does not support
max_fails/fail_timeout, and upstream failures are handled through proxy-next-upstream retries (see Proxy settings). NGINX Ingress Controller adds per-upstream passive health checks via the nginx.org/max-fails and nginx.org/fail-timeout annotations.Ingress-NGINXNGINX Ingress ControllerNote: The VirtualServer CRD allows passive health check settings per upstream. For active health checks that proactively probe endpoints, see Active health checks in NGINX Plus mappings.
| |
Proxy settings
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/proxy-http-version |
VirtualServer CRD upstreams[].proxy-http-version New v5.7.0— or —nginx.org/proxy-http-version New v5.7.0 |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNew in v5.7.0:
nginx.org/proxy-http-version and the VirtualServer upstream proxy-http-version take the community's 1.0 and 1.1 unchanged, and also 2 for upstreams that speak cleartext HTTP/2. 1.0 sends Connection: close. Left unset, NGINX Ingress Controller uses HTTP/2 for a Service port with appProtocol: kubernetes.io/h2c and HTTP/1.1 otherwise. A proxy_http_version snippet is no longer needed. | |
nginx.ingress.kubernetes.io/proxy-next-upstreamnginx.ingress.kubernetes.io/proxy-next-upstream-timeoutnginx.ingress.kubernetes.io/proxy-next-upstream-tries |
VirtualServer CRD upstreams[].next-upstreamVirtualServer CRD upstreams[].next-upstream-timeoutVirtualServer CRD upstreams[].next-upstream-tries— or —nginx.org/proxy-next-upstream New v5.4.0nginx.org/proxy-next-upstream-timeout New v5.4.0nginx.org/proxy-next-upstream-tries New v5.4.0 |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNew in v5.4.0: The
nginx.org/proxy-next-upstream, nginx.org/proxy-next-upstream-timeout, and nginx.org/proxy-next-upstream-tries annotations are new. They provide direct 1:1 mappings from the community annotations without requiring a VirtualServer CRD. | |
Rate limiting
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/limit-burst-multipliernginx.ingress.kubernetes.io/limit-connectionsnginx.ingress.kubernetes.io/limit-ratenginx.ingress.kubernetes.io/limit-rate-afternginx.ingress.kubernetes.io/limit-rpmnginx.ingress.kubernetes.io/limit-rpsnginx.ingress.kubernetes.io/limit-whitelist |
Policy CRD rateLimit.*— or —nginx.org/limit-req-burstnginx.org/limit-req-ratenginx.org/location-snippets |
Ingress-NGINXNGINX Ingress ControllerNot carried over: An Ingress takes one
nginx.org/limit-req-rate, so this approach keeps limit-rps and drops limit-rpm; use the CRD Approach to keep both. NGINX Ingress Controller has no rate-limit exemption list for limit-whitelist. To keep 10.0.0.0/8 and 172.16.0.0/12 unlimited, set nginx.org/limit-req-key from a geo map in the ConfigMap's http-snippets that yields an empty key for them, because NGINX does not count an empty key.Ingress-NGINXNGINX Ingress ControllerNo exemption list: NGINX Ingress Controller has no rate-limit exemption list for
limit-whitelist. To keep 10.0.0.0/8 and 172.16.0.0/12 unlimited, set the rateLimit key from a geo map in the ConfigMap's http-snippets that yields an empty key for them, because NGINX does not count an empty key.Note: The rateLimit Policy CRD covers
limit-rps, limit-rpm (as rate in r/m), and limit-burst-multiplier; a VirtualServer can reference one Policy per rate, as the community controller enforced both. The community controller always used nodelay and answered 503, so the Policies say so. limit-connections, limit-rate, and limit-rate-after have no Policy fields and become a location snippet; limit_conn needs a limit_conn_zone, which goes in the ConfigMap's http-snippets (no -enable-snippets needed there). The location snippet does need -enable-snippets. | |
ConfigMap limit-req-status-code |
nginx.org/limit-req-reject-code |
Ingress-NGINX ConfigMapNGINX Ingress ControllerNote: The community controller sets the rate limit rejection status code globally via ConfigMap. NGINX Ingress Controller sets it per-Ingress via the
nginx.org/limit-req-reject-code annotation (default: 429). | |
| No direct equivalent | nginx.org/limit-req-delaynginx.org/limit-req-dry-runnginx.org/limit-req-keynginx.org/limit-req-log-levelnginx.org/limit-req-no-delaynginx.org/limit-req-reject-codenginx.org/limit-req-scalenginx.org/limit-req-zone-size |
Ingress-NGINXNGINX Ingress ControllerNote: These annotations, which only NGINX Ingress Controller has, provide fine-grained rate limiting control.
limit-req-key customizes the rate limiting key (default: ${binary_remote_addr}). limit-req-scale divides the rate by pod count for consistency during autoscaling. limit-req-reject-code sets the response code for rejected requests (default: 429). limit-req-dry-run enables logging without enforcing limits. | |
Redirects
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/from-to-www-redirectnginx.ingress.kubernetes.io/permanent-redirectnginx.ingress.kubernetes.io/permanent-redirect-codenginx.ingress.kubernetes.io/temporal-redirectnginx.ingress.kubernetes.io/temporal-redirect-code |
VirtualServer CRD action.redirect |
Ingress-NGINXNGINX Ingress Controller | |
Request mirroring
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/mirror-hostnginx.ingress.kubernetes.io/mirror-request-bodynginx.ingress.kubernetes.io/mirror-target |
VirtualServer CRD routes[].location-snippets / VirtualServer CRD spec.server-snippets— or —nginx.org/location-snippetsnginx.org/server-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: This is the internal location the community controller generated for
mirror-target. Without mirror-host, set Host to the target's hostname, as the community controller did; mirror_request_body is on unless mirror-request-body is off. A target with NGINX variables, such as $request_uri, makes NGINX resolve its hostname per request, and that needs a resolver. The community controller always configured one; NGINX Ingress Controller does not, so add a resolver line to the server snippet (on NGINX Plus, the resolver-addresses ConfigMap key also works), or the mirror requests fail. Snippets need the controller's -enable-snippets flag. | |
Rewrites
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/app-rootnginx.ingress.kubernetes.io/rewrite-targetnginx.ingress.kubernetes.io/use-regex |
VirtualServer CRD routes[].action.proxy.rewritePath— or —nginx.org/app-root New v5.4.0nginx.org/path-regexnginx.org/rewrite-target |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote:
rewrite-target or use-regex made the community controller treat every path on the host as a case-insensitive regex, so the route becomes ~ (?i)^/api… and nginx.org/path-regex is case_insensitive, which applies to every path on the Ingress. app-root redirects only a request for / itself, hence the exact =/ route; a VirtualServer redirect needs a scheme, so the target is ${scheme}://${host}/dashboard.New in v5.4.0: The
nginx.org/app-root annotation is new. It provides a direct 1:1 mapping from the community app-root annotation without requiring a VirtualServer CRD. | |
nginx.ingress.kubernetes.io/proxy-cookie-domainnginx.ingress.kubernetes.io/proxy-cookie-path |
VirtualServer CRD routes[].location-snippets— or —nginx.org/location-snippets |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: NGINX Ingress Controller does not have dedicated annotations for proxy cookie rewriting. Use
location-snippets to configure proxy_cookie_domain and proxy_cookie_path directives. | |
nginx.ingress.kubernetes.io/proxy-redirect-fromnginx.ingress.kubernetes.io/proxy-redirect-to |
nginx.org/proxy-redirect-from New v5.5.0nginx.org/proxy-redirect-to New v5.5.0 |
Ingress-NGINXNGINX Ingress ControllerNew in v5.5.0: The
nginx.org/proxy-redirect-from and nginx.org/proxy-redirect-to annotations are new and provide direct 1:1 mappings from the community annotations. The from value also accepts off, default, or a regex prefixed with ~/~*. VirtualServer has no field for proxy_redirect, so there it is a location snippet, which needs the controller's -enable-snippets flag. | |
| No direct equivalent | nginx.org/rewrites (per-service URL rewriting) |
Ingress-NGINXNGINX Ingress ControllerNote:
nginx.org/rewrites enables per-service URL rewriting within a single Ingress resource. Each semicolon-separated entry specifies a service name and its rewrite path. Only NGINX Ingress Controller has it; the community controller has no equivalent. | |
Route delegation
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/server-alias |
VirtualServer CRD one resource per hostname— or —One Ingress per hostname |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: The community
server-alias annotation adds an additional hostname to the same NGINX server block. NGINX Ingress Controller has no equivalent annotation and gives a host to one resource. So the analyzer gives each alias its own VirtualServer, or its own copy of the Ingress, with the same paths, settings and TLS Secret. That certificate must cover the alias, as the community controller required. A server_name line in nginx.org/server-snippets also answers for the alias, but NGINX Ingress Controller cannot see that host, so it neither covers it with the Ingress's TLS certificate nor detects collisions with other resources. | |
| No direct equivalent | VirtualServerRoute CRD routes[].route |
Tip: This feature requires CRDs. See Installing CRDs for Helm and manifest installation instructions.
Note: The community controller has no equivalent for cross-namespace route delegation. VirtualServerRoute allows different teams to manage their own routing rules while a central VirtualServer controls the domain. The analyzer migrates several Ingresses on one host into one VirtualServer instead, which keeps the community routing order; see Ingress configuration. Since v5.7.0, a VirtualServerRoute may omit
spec.host and be referenced by any number of VirtualServers, so paths that several hosts share, migrated from Ingresses that repeated them per host, can be defined once.Ingress-NGINXNGINX Ingress Controller | |
Session affinity / sticky sessions
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/affinity: "cookie"nginx.ingress.kubernetes.io/affinity-modenginx.ingress.kubernetes.io/session-cookie-change-on-failurenginx.ingress.kubernetes.io/session-cookie-conditional-samesite-nonenginx.ingress.kubernetes.io/session-cookie-domainnginx.ingress.kubernetes.io/session-cookie-expiresnginx.ingress.kubernetes.io/session-cookie-max-agenginx.ingress.kubernetes.io/session-cookie-namenginx.ingress.kubernetes.io/session-cookie-pathnginx.ingress.kubernetes.io/session-cookie-samesitenginx.ingress.kubernetes.io/session-cookie-secure |
VirtualServer CRD upstreams[].sessionCookie New v5.4.0— or —nginx.org/sticky-cookie-services |
Ingress-NGINXNGINX Ingress ControllerNote: The
nginx.org/sticky-cookie-services annotation works with both NGINX Open Source and NGINX Plus and provides a simpler way to configure session persistence via Ingress annotations (the legacy nginx.com/sticky-cookie-services form is Plus-only and kept for backward compatibility). For more granular control (domain, secure, samesite, httpOnly), use the VirtualServer CRD approach.Ingress-NGINXNGINX Ingress ControllerNew in v5.4.0: Session persistence via
sessionCookie is now available in both NGINX Open Source and NGINX Plus. Community-specific features affinity-mode, session-cookie-change-on-failure, and session-cookie-conditional-samesite-none have no direct equivalents. | |
nginx.ingress.kubernetes.io/affinity-canary-behavior |
No direct equivalent |
Ingress-NGINXNGINX Ingress ControllerNo direct equivalent: The community controller's
affinity-canary-behavior: "sticky" (the default) kept a client that a canary had served on the canary. NGINX Ingress Controller picks the split for every request, and sessionCookie keeps a client on one Pod within the chosen upstream, not on the canary. To pin a client to the canary, route it with a cookie or header match (canary-by-cookie or canary-by-header), as above. The sessionCookie feature is available in both NGINX Open Source and NGINX Plus. | |
SSL/TLS
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/backend-protocol |
VirtualServer CRD upstreams[].tls.enableVirtualServer CRD upstreams[].type— or —nginx.org/grpc-servicesnginx.org/ssl-services |
Ingress-NGINXNGINX Ingress ControllerNote: For gRPC backends, use
nginx.org/grpc-services: "my-service" instead.Ingress-NGINXNGINX Ingress ControllerNote: For gRPC backends, use
type: grpc on the upstream instead. For gRPC over TLS, combine both: type: grpc and tls.enable: true. gRPC also needs HTTP/2: since v5.7.0 set spec.http2: true on the VirtualServer (or nginx.org/http2: "true" on an Ingress), with or without TLS. Cleartext gRPC on the default listeners still follows the http2 ConfigMap key, because NGINX takes HTTP/2 for a plaintext port from its default server. | |
nginx.ingress.kubernetes.io/force-ssl-redirect |
nginx.org/redirect-to-https |
Ingress-NGINXNGINX Ingress ControllerNote:
force-ssl-redirect in the Community controller forces HTTPS redirect even without a TLS certificate. Use nginx.org/redirect-to-https for similar behavior. | |
nginx.ingress.kubernetes.io/preserve-trailing-slash |
Not needed, trailing slash preserved by default |
Ingress-NGINXNGINX Ingress ControllerNote: The community
preserve-trailing-slash annotation has no NGINX Ingress Controller equivalent because trailing slashes are preserved by default. You can safely drop it during migration. | |
nginx.ingress.kubernetes.io/ssl-ciphers |
nginx.org/ssl-ciphers |
Ingress-NGINXNGINX Ingress ControllerNote: VirtualServer has no field for
ssl_ciphers, so there it is a server snippet, which needs the controller's -enable-snippets flag. To set it for every host at once, use the ssl-ciphers key in the nginx-config ConfigMap instead. | |
nginx.ingress.kubernetes.io/ssl-passthrough |
TransportServer CRD TLS_PASSTHROUGH (built-in listener) |
Ingress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/ssl-prefer-server-ciphers |
nginx.org/ssl-prefer-server-ciphers |
Ingress-NGINXNGINX Ingress ControllerNote: VirtualServer has no field for
ssl_prefer_server_ciphers, so there it is a server snippet, which needs the controller's -enable-snippets flag. To set it for every host at once, use the ssl-prefer-server-ciphers key in the nginx-config ConfigMap instead. | |
nginx.ingress.kubernetes.io/ssl-redirect |
VirtualServer CRD tls.redirect.code: 308— or —nginx.org/redirect-to-https / nginx.org/ssl-redirect New v5.4.0 + nginx.org/http-redirect-code: "308" New v5.4.0 |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerImportant distinction:
Note: The community controller's
ssl-redirect defaults to a 308 Permanent Redirect, which preserves the original request method and body. NGINX Ingress Controller defaults to 301, which clients may downgrade to GET and drop the request body. To match the community behavior exactly, set nginx.org/http-redirect-code: "308" on the Ingress, or tls.redirect.code: 308 on the VirtualServer. Both controllers redirect only on a host with a TLS certificate, which is why the example carries a tls block; where TLS ends at a load balancer instead, use nginx.org/redirect-to-https.New in v5.4.0: The
nginx.org/ssl-redirect and nginx.org/http-redirect-code annotations are new. Previously, only nginx.org/redirect-to-https was available, with a fixed 301 response code. Use ssl-redirect as the direct 1:1 mapping from the community annotation. | |
| No direct equivalent | nginx.org/http-redirect-code (used with nginx.org/redirect-to-https) New v5.4.0 |
Ingress-NGINXNGINX Ingress ControllerNew in v5.4.0: Customizes the HTTP redirect status code used when redirecting to HTTPS (default: 301). Available as both a per-Ingress annotation and a global ConfigMap key (
http-redirect-code). Common values: 301 (permanent), 302 (temporary), 307 (temporary, preserves method), 308 (permanent, preserves method). | |
| No direct equivalent | nginx.org/listen-ports / nginx.org/listen-ports-ssl |
Ingress-NGINXNGINX Ingress ControllerNote: These annotations configure custom HTTP and HTTPS listen ports for the Ingress resource. Useful when the controller needs to listen on non-standard ports.
| |
TCP/UDP load balancing
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/stream-snippetConfigMap tcp-servicesConfigMap udp-services |
GlobalConfiguration CRD +TransportServer CRD |
Ingress-NGINXNGINX Ingress Controller | |
Timeouts
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/proxy-connect-timeout |
VirtualServer CRD upstreams[].connect-timeout— or —nginx.org/proxy-connect-timeout |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/proxy-read-timeout |
VirtualServer CRD upstreams[].read-timeout— or —nginx.org/proxy-read-timeout |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
nginx.ingress.kubernetes.io/proxy-send-timeout |
VirtualServer CRD upstreams[].send-timeout— or —nginx.org/proxy-send-timeout |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress Controller | |
Upstream configuration
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
| No direct equivalent | VirtualServer CRD upstreams[] fields— or —nginx.org/fail-timeoutnginx.org/keepalivenginx.org/max-connsnginx.org/max-failsnginx.org/upstream-zone-size |
Ingress-NGINXNGINX Ingress ControllerIngress-NGINXNGINX Ingress ControllerNote: These are NGINX Ingress Controller-only features for fine-tuning upstream behavior. The community controller does not provide per-Ingress control over these settings.
| |
| No direct equivalent (automatic with HTTP/1.1) | nginx.org/websocket-services |
Ingress-NGINXNGINX Ingress ControllerNote: The Community controller handles WebSocket upgrades automatically. NGINX Ingress Controller provides an explicit annotation to enable WebSocket support for specific services.
| |
NGINX Plus mappings
These features require an NGINX Plus subscription and provide enterprise-grade capabilities. Most Plus features use CRD-based configuration. Click the expandable rows ▶ to see full examples. All items are annotations unless otherwise indicated with a badge. Community annotations use the nginx.ingress.kubernetes.io/ prefix; NGINX Ingress Controller Plus annotations use the nginx.com/ prefix (or appprotect.f5.com/ for F5 WAF for NGINX).
- Annotation-to-annotation rows ▶: Direct mappings where you change the annotation name/prefix. Click to expand and see before/after Ingress YAML examples.
- CRD-based rows ▶: Annotations that map to a CRD (VirtualServer, Policy, or TransportServer). Click to expand for full CRD examples with installation instructions.
- Grouped annotations: Related annotations that work together are grouped in a single expandable row, showing how they map to a unified configuration.
- NGINX Ingress Controller-only rows: Rows marked No direct equivalent on the left are features unique to NGINX Ingress Controller with no community equivalent.
Active health checks
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
| No direct equivalent | VirtualServer CRD Plus upstreams[].healthCheck— or —nginx.com/health-checksnginx.com/health-checks-mandatorynginx.com/health-checks-mandatory-queuenginx.com/slow-start |
Note: Active health checks proactively probe upstream endpoints at configurable intervals, rather than waiting for client requests to fail. These require NGINX Plus. For more granular control (custom paths, intervals, status matching), use the VirtualServer CRD approach. For passive health checks, which NGINX Open Source has, see Passive health checks in NGINX mappings.
Note: The VirtualServer CRD provides the most granular control over active health checks. Active health checks and
slowStart require NGINX Plus. For passive health checks, which NGINX Open Source has, see Passive health checks in NGINX mappings. | |
JWT authentication
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
| No direct equivalent | Policy CRD Plus jwt— or —nginx.com/jwt-keynginx.com/jwt-login-urlnginx.com/jwt-realmnginx.com/jwt-token |
Ingress-NGINXNGINX Ingress Controller (Plus)Note: The annotation approach requires a Kubernetes Secret containing the JWK. The Policy CRD approach is recommended for new deployments as it provides more granular control including JWKS URI support and key caching.
Ingress-NGINXNGINX Ingress Controller (Plus)Note: The Policy CRD approach is recommended for new deployments. It supports JWKS URIs, key caching, and more granular configuration. The
jwt-login-url annotation does not have a direct Policy CRD field; configure redirect logic separately via VirtualServer errorPages or server-snippets for 401 responses. | |
OIDC authentication
auth_request (non-OIDC) migration? Use the new externalAuth Policy CRD documented in the Authentication (external) section; it works in NGINX Open Source and NGINX Plus. The oidcNative and oidc Policies below make NGINX Plus the OpenID Connect client itself, with no oauth2-proxy.| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
nginx.ingress.kubernetes.io/auth-cache-durationnginx.ingress.kubernetes.io/auth-cache-keynginx.ingress.kubernetes.io/auth-methodnginx.ingress.kubernetes.io/auth-proxy-set-headersnginx.ingress.kubernetes.io/auth-request-redirectnginx.ingress.kubernetes.io/auth-response-headersnginx.ingress.kubernetes.io/auth-signinnginx.ingress.kubernetes.io/auth-signin-redirect-paramnginx.ingress.kubernetes.io/auth-url |
Policy CRD Plus oidcNative + nginx.com/policies New v5.6.0— or —Policy CRD Plus oidc |
Ingress-NGINXNGINX Ingress Controller (Plus)New in v5.6.0: The
oidcNative Policy runs the whole OpenID Connect flow inside NGINX Plus with its built-in OIDC module. Endpoints come from the provider's discovery document, so you give only the issuer. Unlike oidc, it attaches to an Ingress, through nginx.com/policies (not nginx.org/policies). It needs the -enable-oidc flag and a resolver-addresses ConfigMap entry so NGINX can reach the provider.Ingress-NGINXNGINX Ingress Controller (Plus)Note: A VirtualServer takes either Policy:
oidcNative as in the first tab, or the NJS-based oidc shown here, which needs the provider's endpoints set by hand and cannot be used on an Ingress. The two are mutually exclusive on a route.Note: Either Policy removes the oauth2-proxy sidecar. The community
auth-url/auth-signin pattern uses the auth_request module (typically with oauth2-proxy) and supports any external auth service, not just OIDC. The community annotations auth-cache-duration, auth-cache-key, auth-method, auth-proxy-set-headers, auth-request-redirect, auth-response-headers, and auth-signin-redirect-param have no direct OIDC Policy equivalents; the Policy manages these concerns internally. If you need generic external auth (non-OIDC), prefer the new externalAuth Policy CRD (v5.5.0 and later, no NGINX Plus needed) documented in the Authentication (external) section, or fall back to nginx.org/location-snippets with the auth_request directive. | |
WAF (web application firewall)
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
| No direct equivalent. See ModSecurity / WAF (NGINX mappings) | Policy CRD Plus waf (F5 WAF for NGINX) + nginx.com/policies New v5.5.0— or —appprotect.f5.com/app-protect-enableappprotect.f5.com/app-protect-policyappprotect.f5.com/app-protect-security-log-enableappprotect.f5.com/app-protect-security-logappprotect.f5.com/app-protect-security-log-destination |
New in v5.5.0: The
waf Policy CRD can now be referenced directly from Ingress resources using the nginx.com/policies annotation. This is the simplest migration path: you stay on Ingress resources. Previously, WAF Policies were VirtualServer-only.
Note: Use this if you are already migrating to VirtualServer resources for other reasons (e.g., advanced routing, traffic splitting). Policies can also be scoped per-route via the route-level
policies[] field.
Note: The
appprotect.f5.com/ annotations enable F5 WAF for NGINX directly on the Ingress without a Policy CRD. The Policy + Ingress approach (v5.5.0+) is recommended for new deployments as it provides reusability and a more consistent configuration model with the other NGINX Ingress Controller Policies.Note: F5 WAF for NGINX provides enterprise WAF capabilities beyond ModSecurity. The Policy CRD approach allows WAF policies to be shared across multiple Ingress / VirtualServer resources. For community ModSecurity annotations, see the ModSecurity / WAF section in NGINX mappings. WAF IP Intelligence (threat-feed-based blocking) can also be enabled in v5.5.0 via the
-enable-app-protect-ip-intelligence controller flag (or the controller.appprotect.ipIntelligence.enable Helm value) and configured inside the APPolicy resource.New in v5.6.0: The
waf Policy can fetch compiled bundles itself: apBundleSource for the policy and apLogBundleSource for each security log, from an HTTPS URL, F5 NGINX Instance Manager, or F5 NGINX One Console, optionally polling for updates. Use one of apPolicy, apBundle, or apBundleSource. | |
ConfigMap mappings
Both controllers use a Kubernetes ConfigMap for global NGINX settings. Many keys share the same name, but some are renamed or have different semantics. The keys listed below are community ConfigMap keys that have an equivalent NGINX Ingress Controller ConfigMap key.
ingress-nginx-controller. NGINX Ingress Controller reads the one its -nginx-configmaps <namespace>/<name> flag names: nginx-config in the manifests, and with Helm the release's name unless controller.config.name sets it (see Install alongside the community controller). The examples here use nginx-config.ACME challenges
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
no-auth-locations |
Automatic for cert-manager New v5.7.0 |
Ingress-NGINX ConfigMapNGINX Ingress ControllerNew in v5.7.0: The community default let ACME challenges under
/.well-known/acme-challenge past authentication. NGINX Ingress Controller exempts a challenge location only when it serves a cert-manager solver: a path under /.well-known/acme-challenge/ whose backend Service name starts with cm-acme-http-solver-, so other ACME clients and other paths listed in this key have no equivalent and need a route without the auth Policy. On a VirtualServer this needs -enable-cert-manager and the solver's Ingress in the VirtualServer's namespace; on an Ingress, cert-manager adds the path with acme.cert-manager.io/http01-edit-in-place: "true". Access control and rate limiting still apply to the challenge. | |
no-tls-redirect-locations |
Automatic for cert-manager New v5.7.0 |
Ingress-NGINX ConfigMapNGINX Ingress ControllerNew in v5.7.0: The community default kept HTTP-01 challenges on plain HTTP even with
ssl-redirect or force-ssl-redirect. NGINX Ingress Controller 5.7.0 does the same for an active cert-manager challenge: while one is active, every path under /.well-known/acme-challenge/ on that host skips tls.redirect, nginx.org/ssl-redirect and nginx.org/redirect-to-https. Earlier versions redirected the challenge and the certificate could not renew. Other paths listed in this key have no equivalent. | |
Headers
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
add-headers |
add-header New v5.5.0 |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNew in v5.5.0: The
add-header ConfigMap key adds globally-applied response headers using a comma-separated Name: Value list. Unlike the community add-headers key (which references a separate ConfigMap of headers), values are specified inline. | |
hide-headers |
proxy-hide-headers |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
| No direct equivalent | add-header-inherit New v5.5.0 |
Ingress-NGINXNGINX Ingress Controller ConfigMapNew in v5.5.0: The
add-header-inherit ConfigMap key sets the global default for add_header_inherit: on (inherit headers from outer contexts, the NGINX default), off (do not inherit), or merge (combine inner and outer headers). Per-Ingress override is available via the nginx.org/add-header-inherit annotation. | |
Identical keys
These keys have the same name and semantics in both controllers. No changes are needed when migrating.
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
client-body-buffer-size |
client-body-buffer-size |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
error-log-level |
error-log-level |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
hsts |
hsts |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
hsts-include-subdomains |
hsts-include-subdomains |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
hsts-max-age |
hsts-max-age |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
map-hash-bucket-size |
map-hash-bucket-size |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
otel-service-name |
otel-service-name |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
proxy-buffer-size |
proxy-buffer-size |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
proxy-buffering |
proxy-buffering |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
proxy-busy-buffers-size |
proxy-busy-buffers-size |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
proxy-connect-timeout |
proxy-connect-timeout |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
proxy-read-timeout |
proxy-read-timeout |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
proxy-send-timeout |
proxy-send-timeout |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
server-tokens |
server-tokens |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
ssl-ciphers |
ssl-ciphers |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
ssl-protocols |
ssl-protocols |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
ssl-redirect |
ssl-redirect |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
variables-hash-bucket-size |
variables-hash-bucket-size |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
variables-hash-max-size |
variables-hash-max-size |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
worker-cpu-affinity |
worker-cpu-affinity |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
worker-processes |
worker-processes |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
worker-shutdown-timeout |
worker-shutdown-timeout |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
Keepalive & timeouts
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
keep-alive |
keepalive-timeout |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: Both map to the NGINX
keepalive_timeout directive. The NGINX Ingress Controller key uses the NGINX-native name and expects a time unit suffix (e.g., "75s"). | |
keep-alive-requests |
keepalive-requests |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
Logging
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
disable-access-log |
access-log-off |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
log-format-escape-jsonlog-format-escape-none |
log-format-escaping |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: The community controller uses separate boolean keys (
log-format-escape-json and log-format-escape-none). NGINX Ingress Controller uses a single key with a string value: "json", "default", or "none". | |
log-format-stream |
stream-log-format |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: The word order is swapped: community uses
log-format-stream, while NGINX Ingress Controller uses stream-log-format. | |
log-format-upstream |
log-format |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
Network / real IP
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
proxy-real-ip-cidr |
set-real-ip-from |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: The community controller uses
proxy-real-ip-cidr and use-forwarded-headers. NGINX Ingress Controller uses the NGINX-native set-real-ip-from, real-ip-header, and real-ip-recursive ConfigMap keys. | |
use-forwarded-headers |
use-forwarded-headers New v5.7.0 + real-ip-header |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNew in v5.7.0: The community key did two things, and NGINX Ingress Controller has a key for each. It took the client address from
X-Forwarded-For, which real-ip-header does, with the proxies to trust in set-real-ip-from (the community proxy-real-ip-cidr). And it passed on the X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-Port a proxy in front had set, rather than its own, which use-forwarded-headers does. Turn it on only when every request arrives through that proxy; it cannot be combined with disable-forwarded-headers. | |
use-proxy-protocol |
proxy-protocol |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: When enabling PROXY protocol, also configure
set-real-ip-from and real-ip-header to properly extract client IPs from the PROXY protocol header. | |
OpenTelemetry
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
enable-opentelemetry |
otel-trace-in-http |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote:
otel-trace-in-http needs otel-exporter-endpoint: without it, NGINX Ingress Controller reports the ConfigMap invalid and leaves tracing off. | |
otlp-collector-hostotlp-collector-port |
otel-exporter-endpoint |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: The community controller uses separate keys for host and port. NGINX Ingress Controller combines them into a single endpoint URL.
| |
Protocol
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
use-http2 |
http2 |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
Proxy & buffering
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
proxy-body-size |
client-max-body-size |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: Both keys map to the NGINX
client_max_body_size directive. NGINX Ingress Controller uses the NGINX-native naming convention. | |
proxy-buffers-number |
proxy-buffers |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: The community key takes just a number (buffer count). The NGINX Ingress Controller key takes the full NGINX
proxy_buffers format: "number size" (e.g., "4 8k"). | |
Server & hash configuration
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
server-name-hash-bucket-size |
server-names-hash-bucket-size |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
server-name-hash-max-size |
server-names-hash-max-size |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: Subtle difference: the community uses
server-name-hash-max-size and server-name-hash-bucket-size (singular "name"), while NGINX Ingress Controller uses server-names-hash-max-size and server-names-hash-bucket-size (plural "names") matching the NGINX directives. | |
Snippet-based keys
These community ConfigMap keys have no direct equivalent in NGINX Ingress Controller. Use main-snippets, http-snippets, or server-snippets in the NGINX Ingress Controller ConfigMap to inject the equivalent NGINX directives.
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
access-log-path |
http-snippets |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
custom-http-errors |
server-snippets— or —nginx.org/custom-http-errors New v5.6.0 |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapIngress-NGINX ConfigMapNGINX Ingress ControllerNote: The community key is controller-wide and sends errors to the controller's default backend; NGINX Ingress Controller has no ConfigMap key for it. Controller-wide, a
server-snippets entry must define both the error_page and the named location it targets, and the Service in proxy_pass must exist when NGINX reloads or the reload fails. Per Ingress, nginx.org/custom-http-errors (v5.6.0+) does the same without snippets, sending errors to that Ingress's spec.defaultBackend; see Error handling. | |
enable-brotli |
http-snippets |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
error-log-path |
main-snippets |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
Snippets
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
http-snippetlocation-snippetmain-snippetserver-snippetstream-snippet |
http-snippetslocation-snippetsmain-snippetsserver-snippetsstream-snippets |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: All snippet keys change from singular to plural:
http-snippet → http-snippets, location-snippet → location-snippets, main-snippet → main-snippets, server-snippet → server-snippets, stream-snippet → stream-snippets. | |
SSL/TLS
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
force-ssl-redirect |
redirect-to-https |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: The community controller uses an
X-Forwarded-Proto header check to determine whether to redirect. NGINX Ingress Controller performs a direct HTTP-to-HTTPS redirect. | |
hsts-preload |
(included automatically) |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: The NGINX Ingress Controller's
hsts: "true" includes the preload directive by default. There is no separate hsts-preload key; just enable hsts. For HSTS without preload, use the hsts Policy CRD (v5.6.0+) on a VirtualServer; see HSTS. | |
ssl-dh-param |
ssl-dhparam-file |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: The community key references a Kubernetes secret name, while the NGINX Ingress Controller key expects a file path to the DH parameters file mounted into the pod.
| |
| No direct equivalent | http-redirect-code New v5.4.0 |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNew in v5.4.0: The
http-redirect-code ConfigMap key sets the global HTTP redirect status code used when redirecting to HTTPS (default: 301). Can also be set per-Ingress via the nginx.org/http-redirect-code annotation. Common values: 301 (permanent), 302 (temporary), 307 (temporary, preserves method), 308 (permanent, preserves method). | |
Upstream & load balancing
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
load-balance |
lb-method |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMapNote: The community controller defaults to
round_robin and supports methods like ewma and least_conn. NGINX Ingress Controller defaults to random two least_conn and uses standard NGINX load balancing method names. For ewma, use least_time last_byte, on NGINX Open Source since v5.7.0: both pick the backend with the lowest average response time. | |
upstream-keepalive-connections |
keepalive |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
Worker & performance
| Ingress-NGINX | NGINX Ingress Controller |
|---|---|
max-worker-connections |
worker-connections |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |
max-worker-open-files |
worker-rlimit-nofile |
Ingress-NGINX ConfigMapNGINX Ingress Controller ConfigMap | |