OneLinersCommand workbench
Guides
Containers & Kubernetes / Web & Proxy

Install and operate F5 NGINX Ingress Controller Open Source with TLS

Deploy the actively maintained F5 NGINX Ingress Controller with pinned Helm artifacts, explicit class ownership, hardened replicas, TLS routing, external verification, monitoring, and safe lifecycle controls.

120 min12 stepsChanges system stateRevision 2
Save or explore
Save to collectionCreate a collection in the sidebar first.
0 of 12 steps completed
Goal

Publish a production-ready TLS route through an explicit active controller while preventing new use of the retired community ingress-nginx project.

Supported environments
  • Kubernetes 1.29–1.36, 1.36
  • F5 NGINX Ingress Controller 5.5.4
  • NGINX Ingress Helm chart 2.6.4
  • Helm 3.19+
Prerequisites
  • Supported cluster Use a healthy conformant Kubernetes release supported by controller 5.5.4 and verify Helm 3.19 or newer.kubectl version && helm version
  • Controller inventory Inventory all IngressClasses, controllers, routes, hosts, and especially retired community ingress-nginx consumers before assigning a new class.
  • Network publication Approve the LoadBalancer implementation, address, source ranges, externalTrafficPolicy, health checks, firewall, DNS ownership, and failure-domain capacity.
  • TLS material Provide a valid certificate chain and protected private key for the hostname through an approved issuance or certificate-management process.
  • Healthy backend Prove the backend Service port and ready EndpointSlices before exposing it externally.kubectl -n {{appNamespace}} get service {{backendService}} && kubectl -n {{appNamespace}} get endpointslice -l kubernetes.io/service-name={{backendService}}
Operating boundary

OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.

Full guide

What you will build

System
  • A pinned, actively maintained F5 NGINX Ingress Controller deployment using NGINX Open Source, Helm chart 2.6.4, controller 5.5.4, a non-default dedicated IngressClass, two replicas, a read-only root filesystem, bounded resources, snippets disabled, and explicit LoadBalancer exposure.
  • A namespace-scoped TLS application route whose certificate Secret, host, backend Service, EndpointSlices, DNS, HTTP-to-HTTPS behavior, SNI certificate, and external response are verified independently.
  • An operating model for high availability, logs, events, metrics, certificate rotation, Helm revision control, upgrade rendering, rollback, CRD ownership, and migration away from the retired community ingress-nginx project.
Observable outcome
  • Two F5 NGINX Ingress Controller Open Source replicas are Ready across appropriate nodes, the IngressClass controller string is `nginx.org/ingress-controller`, and no new workload is assigned to retired ingress-nginx.
  • The test or production hostname resolves to the reviewed endpoint, negotiates the intended TLS certificate, redirects HTTP according to policy, and reaches ready backend endpoints through the explicit class.
  • Operators can distinguish controller, load balancer, class, TLS, and backend failures; monitor them; rotate certificates; and perform a rendered Helm upgrade or rollback without deleting shared CRDs blindly.

Architecture

How the parts fit together

F5 NGINX Ingress Controller watches Kubernetes resources assigned to its IngressClass and renders NGINX Open Source configuration. A LoadBalancer Service sends client traffic to two controller Pods. Each Ingress maps an SNI host and path to a namespace-local Service, while a kubernetes.io/tls Secret supplies the certificate. EndpointSlices identify ready backends. Helm owns the namespaced controller resources and installs shared CRDs. This project is distinct from the retired Kubernetes community ingress-nginx controller.

F5 NGINX Ingress ControllerActively maintained controller whose Open Source edition runs NGINX and reconciles standard Ingress plus optional F5 custom resources.
Dedicated IngressClassUses the controller value `nginx.org/ingress-controller` and the explicit class name selected for this deployment, preventing accidental capture of unrelated Ingresses.
LoadBalancer ServicePublishes ports 80 and 443, preserves client source IP with Local policy where the platform supports it, and provides the address reported into Ingress status.
TLS SecretNamespace-local `kubernetes.io/tls` object containing the certificate chain and private key referenced by one Ingress.
Application IngressDeclares host, TLS Secret, path type, backend Service, and explicit IngressClass.
Backend Service and EndpointSlicesSelect ready application Pods and expose the port NGINX uses for upstream traffic.
Helm release and CRDsVersion and render the controller resources. CRDs are cluster-scoped and shared by all releases, so they require lifecycle care beyond a simple Helm rollback.
Retired ingress-nginxFormer community controller identified by `k8s.io/ingress-nginx`; it stopped receiving releases and security fixes after March 2026 and is migration-only, never an installation target here.
  1. Inventory all existing controllers, classes, and routes, and block new use of retired ingress-nginx.
  2. Verify Kubernetes, Helm, load-balancer, DNS, certificate, backend, policy, capacity, and recovery prerequisites.
  3. Write and review pinned values, render the exact OCI chart locally, and inspect RBAC, class, Service, Deployment, CRDs, security context, and images.
  4. Create the namespace-local TLS Secret without printing private key material and install the F5 controller release.
  5. Publish an explicit-class Ingress, verify EndpointSlices, then direct DNS to the LoadBalancer and test SNI plus HTTP and HTTPS externally.
  6. Exercise replica loss, observe logs and metrics, document certificate rotation, and rehearse versioned Helm upgrade and rollback controls.

Assumptions

  • The cluster runs a conformant Kubernetes version from 1.29 through 1.36; examples and acceptance target Kubernetes 1.36 and Helm 3.19 or newer.
  • The actively maintained product selected is F5 NGINX Ingress Controller 5.5.4 with NGINX Open Source and chart 2.6.4, not the retired community ingress-nginx controller.
  • A platform LoadBalancer implementation or an approved external load balancer can expose ports 80 and 443 and supply health checks compatible with externalTrafficPolicy Local.
  • DNS ownership and a valid certificate chain plus private key exist for the chosen hostname; certificate files remain outside source control and terminal output.
  • The backend Service already selects healthy application Pods and exposes a tested HTTP port. Application state and authentication are outside the controller's rollback guarantee.
  • The organization has decided whether the controller watches all namespaces or a supported restricted set and has reviewed the resulting Secret and RBAC access.
  • The example disables custom snippets and does not make this class the cluster default. Any extension, WAF, Plus licensing, TCP/UDP stream, or TLS passthrough feature needs separate design.

Key concepts

Ingress controller
A reconciler that watches Ingress and related resources and configures a data-plane proxy. Kubernetes does not provide one automatically.
IngressClass
A cluster-scoped object associating Ingress resources with one controller implementation. The resource's name and `spec.controller` string both matter.
F5 NGINX versus ingress-nginx
F5 NGINX Ingress Controller uses `nginx.org/ingress-controller` and remains active. Community ingress-nginx used `k8s.io/ingress-nginx` and was retired in March 2026. Similar names do not make their charts, annotations, or defaults interchangeable.
TLS termination
NGINX presents the certificate for the requested SNI host, decrypts client HTTPS, and normally forwards HTTP to the backend unless upstream TLS is configured separately.
Namespace-local Secret
The TLS Secret referenced by standard Ingress must exist in the same namespace as that Ingress. A class or controller namespace does not relocate this boundary.
EndpointSlice
The scalable API representation of Service backend addresses, ports, zones, and readiness. A Service without ready endpoints cannot produce a healthy upstream.
OCI Helm chart
A versioned chart artifact pulled from `ghcr.io/nginx/charts/nginx-ingress`. The chart version and controller version are related but distinct identifiers.
CRD lifecycle
F5 custom resource definitions are cluster-scoped and can be shared by releases. Helm installs them, but upgrades and removals require explicit schema and consumer review.

Before you copy

Values used in this guide

{{controllerNamespace}}

Dedicated namespace containing the Helm release.

Example: nginx-ingress
{{release}}

Stable Helm release name used in labels, Service, Deployment, history, and rollback.

Example: edge
{{chartVersion}}

Pinned stable F5 NGINX Ingress Controller Helm chart version.

Example: 2.6.4
{{ingressClass}}

Non-default class name explicitly referenced by application Ingresses.

Example: nginx-f5
{{appNamespace}}

Application namespace containing Ingress, TLS Secret, Service, and Pods.

Example: shop
{{ingressName}}

Application Ingress resource name.

Example: shop-web
{{host}}

DNS hostname whose certificate and route are managed by this Ingress.

Example: shop.example.com
{{tlsSecret}}secret

Namespace-local kubernetes.io/tls Secret name, never the private key value.

Example: shop-example-com-tls
{{certificatePath}}secret

Protected local path to the PEM certificate chain used only by kubectl.

Example: /secure/certificates/shop.example.com/fullchain.pem
{{privateKeyPath}}secret

Protected local path to the PEM private key; never commit, echo, or attach it.

Example: /secure/certificates/shop.example.com/privkey.pem
{{backendService}}

Existing Service with ready EndpointSlices behind the Ingress.

Example: shop-web
{{backendPort}}

Service port number used by the Ingress backend.

Example: 8080

Security and production boundaries

  • Do not install the retired community ingress-nginx controller. After March 2026 it receives no releases, bug fixes, or security fixes; inventory and migrate existing deployments rather than treating retained artifacts as supported.
  • F5 NGINX Ingress Controller can require broad Secret reads when watching all namespaces. Design watch scope, tenancy, RBAC, Secret encryption at rest, and audit before admitting mutually untrusted namespaces.
  • Keep custom snippets disabled unless a separate threat model, admission policy, and ownership process accepts arbitrary NGINX configuration risk.
  • Private keys never belong in values files, Git, shell arguments displayed by process listings, logs, screenshots, or community submissions. Use an approved certificate manager or protected files with the client-side dry-run pipeline.
  • Use a pinned stable chart and review rendered manifests, image provenance, release notes, and CRDs before every install or upgrade. Do not use the edge chart in production.
  • Read-only root filesystems, seccomp RuntimeDefault, non-root execution, resource controls, topology, and multiple replicas reduce impact but do not replace network policy, load-balancer controls, TLS policy, or application authentication.

Stop before continuing if

  • Stop if an installation instruction or manifest refers to the community ingress-nginx chart, `registry.k8s.io/ingress-nginx`, or class controller `k8s.io/ingress-nginx` as a new deployment.
  • Stop if Kubernetes, Helm, chart, controller image, CRDs, load-balancer implementation, or cluster admission policy does not match the supported and rendered design.
  • Stop if the certificate and key cannot be handled without exposing material, or if DNS and hostname ownership are disputed.
  • Stop if the backend has no ready endpoints, the class would capture unrelated Ingresses, or two controllers claim the same class or host.
  • Stop after any failed TLS, route, source-IP, replica-loss, load-balancer, observability, or rollback checkpoint; keep the previous controller or route available until diagnosis completes.
01

warning

Distinguish active F5 NGINX from retired community ingress-nginx

read-only

List controller Pods, IngressClasses, controller strings, and route assignments, then block new use of the retired `k8s.io/ingress-nginx` implementation.

Why this step matters

The two NGINX-named controllers are separate projects with different class controller strings, charts, annotations, defaults, and support status. Inventory prevents an operator from installing the retired community implementation or assigning routes to the wrong data plane.

What to understand

Community ingress-nginx Pods commonly carry `app.kubernetes.io/name=ingress-nginx` and classes use `k8s.io/ingress-nginx`. Its maintenance, releases, bug fixes, and security fixes ended after March 2026.

F5 NGINX Ingress Controller uses `nginx.org/ingress-controller`. Record every class, which one is default, each Ingress's class and host, and the Service endpoint currently carrying traffic.

Similar annotations are not guaranteed to have equivalent behavior. Plan migration per route with TLS, redirects, rewrites, source IP, timeouts, body size, websockets, gRPC, and failure behavior tested.

System changes

  • No cluster state changes; controller Pods, classes, and routes are read across namespaces.

Syntax explained

-l app.kubernetes.io/name=ingress-nginx
Uses the Kubernetes retirement notice's selector to detect common community ingress-nginx deployments.
.spec.controller
Distinguishes the implementation behind an IngressClass rather than relying on the class name alone.
.spec.ingressClassName
Shows the explicit class assignment for every standard Ingress.
Command
kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx -o wide && kubectl get ingressclass -o custom-columns=NAME:.metadata.name,CONTROLLER:.spec.controller,DEFAULT:.metadata.annotations.ingressclass\.kubernetes\.io/is-default-class && kubectl get ingress -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,CLASS:.spec.ingressClassName,HOSTS:.spec.rules[*].host
Example output / evidence
No resources found
NAME       CONTROLLER                     DEFAULT
nginx-f5  nginx.org/ingress-controller    <none>
NAMESPACE  NAME      CLASS      HOSTS
shop       shop-web  nginx-f5  shop.example.com

Checkpoint: Every existing route has one known controller and migration state

kubectl get ingressclass -o custom-columns=NAME:.metadata.name,CONTROLLER:.spec.controller && kubectl get ingress -A --no-headers | wc -l

Continue whenThe inventory distinguishes active F5 classes from retired community classes and assigns an owner to every route.

Stop whenA class has unknown ownership, two controllers claim it, an unclassified route relies on a default unexpectedly, or the proposed chart is community ingress-nginx.

If this step fails

The cluster contains Pods or an IngressClass associated with `k8s.io/ingress-nginx`.

Likely causeThe retired community ingress-nginx controller is still installed, or its class remains referenced by workloads.

Safe checks
  • kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx -o wide
  • kubectl get ingressclass -o custom-columns=NAME:.metadata.name,CONTROLLER:.spec.controller
  • kubectl get ingress -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,CLASS:.spec.ingressClassName

ResolutionDo not deploy new traffic to the retired controller. Inventory all dependent Ingress resources, migrate them deliberately to an actively maintained controller or Gateway API, test behavior, and remove the retired deployment only after every route and rollback path is accepted.

Security notes

  • Retained installation artifacts do not imply ongoing security support. Do not deploy them into a new production path.

Alternatives

  • Migrate retired routes to Gateway API or another actively maintained controller when F5 NGINX semantics do not fit.

Stop conditions

  • Do not continue if any command or values file installs `ingress-nginx` from the retired community project.
02

verification

Verify cluster, Helm, admission, capacity, backend, and publication prerequisites

read-only

Confirm supported versions, controller permissions, two-node capacity, LoadBalancer support, backend endpoints, DNS ownership, and protected certificate files before rendering.

Why this step matters

A controller can deploy successfully while network publication, certificate ownership, backend health, or admission prevents usable traffic. Preflight tests each external dependency and ensures two replicas can occupy independent nodes before cluster-scoped resources are created.

What to understand

Confirm the exact Kubernetes and Helm versions against F5 technical specifications. Chart 2.6.4 installs controller 5.5.4, tested on Kubernetes 1.29 through 1.36.

Verify the platform supports a LoadBalancer Service or document the external load-balancer design. Review source ranges, Local traffic policy, node health checks, firewall, IP allocation, and DNS change authority.

Require the backend Service and ready EndpointSlices first. A controller cannot repair a selector, targetPort, unhealthy Pod, or application protocol mismatch.

Check certificate and private-key files are readable only by the authorized operator and that an external recovery copy exists. This command tests file access without printing key contents.

System changes

  • No cluster state changes; versions, permissions, nodes, backends, and local protected file readability are checked.

Syntax explained

kubectl auth can-i
Checks whether the current identity can create required resources without attempting the change.
get endpointslice -l kubernetes.io/service-name=...
Verifies that the backend Service has ready addresses and ports before exposure.
test -r
Confirms local certificate files are readable without displaying certificate private-key content.
Command
Fill variables0/5 ready

Values stay on this page and are never sent or saved.

kubectl version && helm version && kubectl auth can-i create deployments -n {{controllerNamespace}} && kubectl get nodes -o wide && kubectl -n {{appNamespace}} get service {{backendService}} && kubectl -n {{appNamespace}} get endpointslice -l kubernetes.io/service-name={{backendService}} -o wide && test -r {{certificatePath}} && test -r {{privateKeyPath}}
Example output / evidence
Client Version: v1.36.1
Server Version: v1.36.1
version.BuildInfo{Version:"v3.19.0"}
yes
NAME       STATUS   ROLES    VERSION
worker-1   Ready    <none>   v1.36.1
worker-2   Ready    <none>   v1.36.1
NAME              ADDRESSTYPE   PORTS   ENDPOINTS
shop-web-v7k2m    IPv4          8080    10.42.1.7,10.42.2.8

Checkpoint: Controller, publication, TLS, and backend prerequisites are approved

kubectl get nodes --no-headers | head -n 2

Continue whenAt least two eligible Ready nodes, a working LoadBalancer path, healthy backend endpoints, DNS authority, and protected TLS material are documented.

Stop whenVersions are unsupported, only one eligible node exists without accepted risk, backend endpoints are absent, or certificate and network ownership are unresolved.

If this step fails

The controller is Running but its LoadBalancer Service remains Pending.

Likely causeThe cluster has no compatible cloud load-balancer implementation, annotations or loadBalancerClass are wrong, quota is exhausted, or an on-premises address pool is unavailable.

Safe checks
  • kubectl -n {{controllerNamespace}} describe service {{release}}-nginx-ingress-controller
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp
  • kubectl get nodes -o wide

ResolutionConfigure the platform-supported LoadBalancer integration or deliberately choose NodePort behind an approved external load balancer. Do not publish a random node address or bypass the network owner's source-range and health-check policy.

HTTPS returns 502 or 503 while the controller and TLS handshake are healthy.

Likely causeThe backend Service has no ready endpoints, targetPort is wrong, Pods reject traffic, protocol expectations differ, or NetworkPolicy blocks the controller.

Safe checks
  • kubectl -n {{appNamespace}} get service {{backendService}} -o yaml
  • kubectl -n {{appNamespace}} get endpointslice -l kubernetes.io/service-name={{backendService}} -o wide
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=200

ResolutionRestore ready EndpointSlices and correct the Service port, application listener, upstream protocol, or NetworkPolicy. Do not hide a backend outage with a controller-wide timeout increase or error interception.

One controller replica is Ready but the other repeatedly restarts or cannot schedule.

Likely causeResource requests, topology, taints, architecture, image pull, read-only filesystem configuration, or node capacity differs between placements.

Safe checks
  • kubectl -n {{controllerNamespace}} get pods -o wide
  • kubectl -n {{controllerNamespace}} describe pod -l app.kubernetes.io/instance={{release}}
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp

ResolutionKeep traffic on healthy replicas, diagnose the affected node or scheduling constraint, and restore the declared replica count across failure domains. Do not relax security context or pin both replicas to the same node as a shortcut.

Security notes

  • The operator identity needs cluster-scoped access for CRDs and IngressClass. Use a time-bounded installation role and retain audit evidence.

Alternatives

  • Use NodePort behind an approved load balancer when the cluster lacks a LoadBalancer implementation, with equivalent health and source-IP testing.

Stop conditions

  • Do not create a controller before backend, certificate, address, health-check, and failure-domain prerequisites pass.
03

config

Create pinned, non-default, hardened controller values

read-only

Define two replicas, an explicit non-default class, disabled snippets, bounded resources, read-only filesystems, LoadBalancer exposure, source-IP preservation, and stable telemetry policy in a reviewed file.

Why this step matters

A reviewed values file makes controller identity, security posture, exposure, resources, replicas, telemetry, and metrics reproducible. Explicit settings prevent a future chart default from silently changing a production boundary.

What to understand

Use a dedicated non-default class. Applications must opt in through `spec.ingressClassName`, avoiding accidental capture of existing unclassified Ingress resources.

Two replicas, read-only root filesystem, RuntimeDefault seccomp from chart defaults, bounded resources, and disabled snippets establish a safer baseline. Validate node capacity and topology separately.

externalTrafficPolicy Local preserves source IP where the platform routes only to nodes with local ready endpoints. Verify the load balancer's health checks and capacity behavior during replica movement.

Telemetry policy is explicit. Prometheus metrics are enabled but the metrics Service must remain inside the monitoring trust boundary.

System changes

  • Creates a local reviewed values document; `helm lint` downloads and validates the pinned chart without changing the cluster.

Syntax explained

controller.ingressClass.setAsDefaultIngress: false
Requires explicit route opt-in and prevents cluster-wide capture of Ingresses that omit a class.
controller.enableSnippets: false
Keeps arbitrary embedded NGINX configuration disabled.
controller.readOnlyRootFilesystem: true
Uses the chart's supported temporary volumes while preventing writes to the container root filesystem.
controller.service.externalTrafficPolicy: Local
Preserves client source IP but requires correct load-balancer health checks and replica placement.
File nginx-ingress-values.yaml
Configuration
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

controller:
  replicaCount: 2
  ingressClass:
    name: {{ingressClass}}
    create: true
    setAsDefaultIngress: false
  enableSnippets: false
  readOnlyRootFilesystem: true
  telemetryReporting:
    enable: false
  resources:
    requests:
      cpu: 200m
      memory: 256Mi
    limits:
      cpu: "1"
      memory: 512Mi
  service:
    create: true
    type: LoadBalancer
    externalTrafficPolicy: Local
  reportIngressStatus:
    enable: true
    enableLeaderElection: true
prometheus:
  create: true
  service:
    create: true
rbac:
  create: true
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

chartDir=$(mktemp -d) && helm pull oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}} --untar --untardir "$chartDir" && helm lint "$chartDir/nginx-ingress" -f nginx-ingress-values.yaml
Example output / evidence
Pulled: ghcr.io/nginx/charts/nginx-ingress:2.6.4
Digest: sha256:bb452d593c31b6be39f459f9604882e170227429821bac01e7ddd7da16d91ba1
==> Linting oci://ghcr.io/nginx/charts/nginx-ingress
1 chart(s) linted, 0 chart(s) failed

Checkpoint: The pinned values lint and express every operating boundary

chartDir=$(mktemp -d) && helm pull oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}} --untar --untardir "$chartDir" && helm lint "$chartDir/nginx-ingress" -f nginx-ingress-values.yaml

Continue whenThe exact stable chart lints with zero failures and the values review covers class, snippets, security, resources, replicas, Service, status, metrics, and telemetry.

Stop whenThe chart is edge or unpinned, lint fails, the class is unintentionally default, snippets are enabled, or platform policy conflicts with generated security settings.

If this step fails

Helm reports that the chart version or OCI manifest cannot be found.

Likely causeThe chart version is mistyped, the environment cannot reach ghcr.io, a proxy blocks OCI requests, or an edge version was selected instead of the reviewed stable release.

Safe checks
  • helm version
  • helm show chart oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}}
  • getent ahosts ghcr.io

ResolutionConfirm the stable chart version in the official F5 NGINX technical specifications and installation page, repair approved proxy or registry access, and pull the exact OCI chart. Do not switch to `0.0.0-edge` or an untrusted mirror in production.

Security notes

  • Values files may contain infrastructure annotations and addresses but must never contain TLS private keys, registry credentials, or NGINX Plus license material.

Alternatives

  • Store the reviewed values in the approved deployment repository and have GitOps render the same exact chart.

Stop conditions

  • Do not compensate for admission failures by removing security controls before understanding the exact generated resource.
04

verification

Render and inspect the exact chart before installation

read-only

Render the pinned OCI chart with the reviewed values, save the manifest, use server-side dry-run, and inspect images, RBAC, CRDs, class, Service, security contexts, resources, and namespace ownership.

Why this step matters

Helm values are not the deployed objects. Rendering reveals cluster-scoped CRDs and RBAC, controller image, security contexts, class string, Service exposure, selectors, and resources before those decisions reach admission or affect existing controllers.

What to understand

Include CRDs in the rendered file and inspect their versions. They are shared cluster resources and may outlive or span Helm releases.

Run server-side dry-run against the real API and admission chain. This catches unsupported APIs, schema failures, policy rejection, naming collisions, and missing permissions without persisting objects.

Compare rendered ClusterRole Secret permissions with watch scope and tenancy. F5 notes that default ServiceAccount access can cover all Secrets; narrow the architecture where required.

Record the OCI digest printed by Helm and the controller image tag or digest from the manifest for supply-chain evidence.

System changes

  • Writes a local rendered manifest and invokes API admission in dry-run mode; no Kubernetes object is persisted.

Syntax explained

--include-crds
Includes cluster-scoped custom resource definitions that ordinary Helm template output can omit.
kubectl apply --dry-run=server
Runs API schema and admission checks without storing the objects.
grep kind/image/class
Creates a concise review view; the full rendered manifest remains the authoritative artifact.
Command
Fill variables0/4 ready

Values stay on this page and are never sent or saved.

helm template {{release}} oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}} --namespace {{controllerNamespace}} --include-crds -f nginx-ingress-values.yaml > nginx-ingress-rendered.yaml && kubectl apply --dry-run=server -f nginx-ingress-rendered.yaml && grep -E '^(kind:|  image:|  name: {{ingressClass}}|  controller: nginx.org/ingress-controller)' nginx-ingress-rendered.yaml | head -n 30
Example output / evidence
customresourcedefinition.apiextensions.k8s.io/virtualservers.k8s.nginx.org configured (server dry run)
serviceaccount/edge-nginx-ingress configured (server dry run)
ingressclass.networking.k8s.io/nginx-f5 configured (server dry run)
service/edge-nginx-ingress-controller configured (server dry run)
deployment.apps/edge-nginx-ingress configured (server dry run)
kind: IngressClass
  name: nginx-f5
  controller: nginx.org/ingress-controller

Checkpoint: Rendered resources pass API and security review

kubectl apply --dry-run=server -f nginx-ingress-rendered.yaml

Continue whenEvery CRD, RBAC object, class, Service, Deployment, and monitoring resource passes server dry-run with no ownership collision.

Stop whenAdmission rejects a resource, RBAC or Secret scope is excessive, class collides, Service replacement is possible, or image and chart provenance are unapproved.

If this step fails

The controller Deployment remains unavailable because required CRDs are missing.

Likely causeThe chart was installed with CRDs skipped while custom resources remained enabled, or the installation identity could not create cluster-scoped CRDs.

Safe checks
  • kubectl -n {{controllerNamespace}} get pods -o wide
  • kubectl get crd | grep 'k8s.nginx.org\|nginx.org'
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=100

ResolutionApply the exact CRDs shipped with the same stable controller release or reinstall with the documented chart defaults. Do not grant broad permanent privileges merely to hide a failed one-time CRD operation.

The controller Pod is rejected by Pod Security or another admission policy.

Likely causeThe values file requests a forbidden security context, the namespace policy differs from the tested environment, or another admission controller rejects generated resources.

Safe checks
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp
  • helm template {{release}} oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}} -n {{controllerNamespace}} -f nginx-ingress-values.yaml
  • kubectl auth can-i create deployments -n {{controllerNamespace}}

ResolutionRead the exact admission message, compare the rendered securityContext with cluster policy, and use supported chart settings. Do not label the namespace privileged or disable admission without a reviewed security exception.

The controller can read Secrets outside the intended application scope.

Likely causeIt watches all namespaces with the chart's cluster-wide RBAC defaults, or watch and Secret namespace settings were broader than the tenancy design.

Safe checks
  • helm -n {{controllerNamespace}} get values {{release}} -a
  • kubectl get clusterrole,role -l app.kubernetes.io/instance={{release}} -o yaml
  • kubectl auth can-i list secrets --as=system:serviceaccount:{{controllerNamespace}}:{{release}}-nginx-ingress

ResolutionDefine the supported watch namespace or label and Secret scope, then design RBAC accordingly before hosting untrusted tenants. Do not assume IngressClass alone is a Secret confidentiality boundary.

Security notes

  • Treat rendered ClusterRoles as executable security policy and review Secret verbs plus namespace scope explicitly.

Alternatives

  • Use the organization's policy-as-code and manifest-diff pipeline on the same rendered artifact before installation.

Stop conditions

  • Never proceed from values review alone; require full rendered manifest, API dry-run, and CRD ownership evidence.
05

command

Create or rotate the namespace-local TLS Secret without printing the key

caution

Validate certificate hostname, validity, chain, and key match outside the cluster, then generate the Kubernetes Secret manifest client-side and apply it directly without writing plaintext YAML.

Why this step matters

The TLS Secret is the private-key boundary for the route. Validating SAN, dates, issuer chain, and key match before upload avoids installing a malformed pair, while client-side generation piped to apply keeps the key out of a saved manifest and terminal output.

What to understand

The certificate SAN must contain the exact host and the full chain must match client trust. Check notBefore and notAfter against synchronized UTC time and monitoring policy.

Hash the public key from certificate and private key to prove they form a pair, including ECDSA certificates. The displayed hashes are not private key material, but the key file path and permissions remain protected.

A standard Ingress references a TLS Secret in its own namespace. Creating it in the controller namespace will not satisfy the application Ingress.

For normal operations, prefer an approved certificate controller or secret manager and rehearse rotation before the expiry alert window.

System changes

  • Creates or updates a namespace-local kubernetes.io/tls Secret containing the supplied certificate chain and private key.

Syntax explained

openssl x509 -noout ...
Reads only public certificate identity, issuer, validity, and SAN without displaying the private key.
create secret tls --dry-run=client -o yaml
Builds a correct kubernetes.io/tls object locally for direct piping without saving plaintext YAML.
kubectl apply -f -
Creates or rotates the Secret idempotently from standard input; API audit records the change.
Command
Fill variables0/4 ready

Values stay on this page and are never sent or saved.

openssl x509 -in {{certificatePath}} -noout -subject -issuer -dates -ext subjectAltName && test "$(openssl x509 -in {{certificatePath}} -pubkey -noout | openssl pkey -pubin -outform DER | openssl sha256)" = "$(openssl pkey -in {{privateKeyPath}} -pubout -outform DER | openssl sha256)" && kubectl -n {{appNamespace}} create secret tls {{tlsSecret}} --cert={{certificatePath}} --key={{privateKeyPath}} --dry-run=client -o yaml | kubectl apply -f -
Example output / evidence
subject=CN=shop.example.com
issuer=C=US, O=Example Trust, CN=Example RSA 2026
notBefore=Jul 20 00:00:00 2026 GMT
notAfter=Oct 18 23:59:59 2026 GMT
X509v3 Subject Alternative Name:
    DNS:shop.example.com
secret/shop-example-com-tls created

Checkpoint: TLS identity is correct and key material remains protected

kubectl -n {{appNamespace}} get secret {{tlsSecret}} -o jsonpath='{.type}{" "}{.metadata.name}{" keys="}{range $k,$v := .data}{$k}{","}{end}{"\n"}'

Continue whenReturns type kubernetes.io/tls with only tls.crt and tls.key key names, while SAN, validity, chain, and key match evidence is retained separately.

Stop whenHostname is absent, certificate is expired or not yet valid, chain validation fails, key mismatch occurs, file permissions are unsafe, or the command would expose private content.

If this step fails

TLS clients receive a certificate for another host or a default certificate.

Likely causeThe Ingress references the wrong Secret, the Secret is in another namespace, SNI host does not match the rule, the certificate chain is incomplete, or DNS points to another ingress endpoint.

Safe checks
  • kubectl -n {{appNamespace}} get ingress {{ingressName}} -o jsonpath='{.spec.tls[*].hosts}{" "}{.spec.tls[*].secretName}{"\n"}'
  • kubectl -n {{appNamespace}} get secret {{tlsSecret}} -o jsonpath='{.type}{" "}{.metadata.name}{"\n"}'
  • openssl s_client -connect {{host}}:443 -servername {{host}} -showcerts </dev/null

ResolutionRepair the namespace-local kubernetes.io/tls Secret through the approved certificate workflow, align the SNI host and Ingress rule, include the required chain, and verify DNS. Never print the private key or copy a TLS Secret across tenants casually.

Security notes

  • Never use `kubectl get secret -o yaml`, shell tracing, clipboard sharing, or committed manifest files to inspect this Secret.

Alternatives

  • Use cert-manager or another approved certificate lifecycle controller so the private key is generated, stored, and rotated under policy.

Stop conditions

  • Revoke and replace the key immediately if it appears in logs, shell history, source control, screenshots, or an untrusted workstation.
06

warning

Install the active F5 NGINX controller from the pinned OCI chart

caution

Create the dedicated namespace and use Helm upgrade-install with atomic rollback, timeout, exact chart, reviewed values, and release history.

Why this step matters

A pinned atomic Helm install provides a versioned release and removes newly created namespaced resources if readiness fails. It does not remove the need to inspect cluster-scoped CRDs, class ownership, or application routes separately.

What to understand

Use the dedicated namespace and stable release name. Changing names later can replace the LoadBalancer Service and external address, so record them as interface contracts.

`--atomic` includes wait behavior and rolls back a failed install within the timeout. Preserve Helm output and events because automatic cleanup can remove the failed Pod evidence.

The stable chart installs required CRDs by default. Do not use `--skip-crds` while custom resources remain enabled, and do not apply CRDs from a different controller version.

Immediately record release revision, chart version, app version, OCI digest, values hash, and deployment ticket.

System changes

  • Creates the controller namespace and installs CRDs, RBAC, IngressClass, Deployment, Services, ConfigMaps, and Helm release metadata.

Syntax explained

upgrade --install
Uses one idempotent release command for first install and later reviewed upgrades.
--version {{chartVersion}}
Pins the exact stable OCI chart instead of resolving a moving version.
--atomic --timeout 10m
Waits for readiness and automatically rolls back failed namespaced release resources within a bounded interval.
Command
Fill variables0/3 ready

Values stay on this page and are never sent or saved.

kubectl create namespace {{controllerNamespace}} --dry-run=client -o yaml | kubectl apply -f - && helm upgrade --install {{release}} oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}} --namespace {{controllerNamespace}} -f nginx-ingress-values.yaml --atomic --timeout 10m && helm -n {{controllerNamespace}} history {{release}}
Example output / evidence
namespace/nginx-ingress created
Pulled: ghcr.io/nginx/charts/nginx-ingress:2.6.4
Release "edge" does not exist. Installing it now.
NAME: edge
NAMESPACE: nginx-ingress
STATUS: deployed
REVISION: 1
REVISION  UPDATED                  STATUS      CHART                APP VERSION  DESCRIPTION
1         Tue Jul 28 12:10:00 2026 deployed   nginx-ingress-2.6.4  5.5.4        Install complete

Checkpoint: Helm release is deployed at the reviewed version

helm -n {{controllerNamespace}} status {{release}} && helm -n {{controllerNamespace}} history {{release}}

Continue whenStatus is deployed, revision is recorded, chart is 2.6.4, app is 5.5.4, and no failed revision remains unexplained.

Stop whenAtomic install fails, CRD ownership conflicts, the class already belongs to another controller, Service address would replace a live endpoint, or rendered and installed objects differ.

If this step fails

Helm reports that the chart version or OCI manifest cannot be found.

Likely causeThe chart version is mistyped, the environment cannot reach ghcr.io, a proxy blocks OCI requests, or an edge version was selected instead of the reviewed stable release.

Safe checks
  • helm version
  • helm show chart oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}}
  • getent ahosts ghcr.io

ResolutionConfirm the stable chart version in the official F5 NGINX technical specifications and installation page, repair approved proxy or registry access, and pull the exact OCI chart. Do not switch to `0.0.0-edge` or an untrusted mirror in production.

The controller Deployment remains unavailable because required CRDs are missing.

Likely causeThe chart was installed with CRDs skipped while custom resources remained enabled, or the installation identity could not create cluster-scoped CRDs.

Safe checks
  • kubectl -n {{controllerNamespace}} get pods -o wide
  • kubectl get crd | grep 'k8s.nginx.org\|nginx.org'
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=100

ResolutionApply the exact CRDs shipped with the same stable controller release or reinstall with the documented chart defaults. Do not grant broad permanent privileges merely to hide a failed one-time CRD operation.

The controller Pod is rejected by Pod Security or another admission policy.

Likely causeThe values file requests a forbidden security context, the namespace policy differs from the tested environment, or another admission controller rejects generated resources.

Safe checks
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp
  • helm template {{release}} oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}} -n {{controllerNamespace}} -f nginx-ingress-values.yaml
  • kubectl auth can-i create deployments -n {{controllerNamespace}}

ResolutionRead the exact admission message, compare the rendered securityContext with cluster policy, and use supported chart settings. Do not label the namespace privileged or disable admission without a reviewed security exception.

Security notes

  • Time-bound the installer's cluster-scoped privileges and review Helm release Secrets because they contain rendered values and manifests.

Alternatives

  • Use a reviewed GitOps Helm release with equivalent pinning, rendering, wait, health, and rollback evidence.

Stop conditions

  • Do not retry with broader privileges or fewer security settings until the exact failed admission or readiness condition is understood.
07

verification

Verify replicas, class ownership, Service address, and controller logs

read-only

Require two Ready replicas, correct image and security context, a class controlled by F5 NGINX, an assigned LoadBalancer address, successful leader election, and no reconciliation or RBAC errors.

Why this step matters

A deployed Helm revision is not necessarily a working data plane. Replica readiness, class identity, LoadBalancer address, image version, leader election, reconciliation logs, and placement prove that the active controller can accept explicit routes.

What to understand

Require two replicas on appropriate nodes and inspect securityContext plus resources from the live Deployment. A temporary Ready state before repeated restarts is not acceptance.

Verify the class controller string exactly. `nginx.org/ingress-controller` identifies F5 NGINX; `k8s.io/ingress-nginx` identifies the retired community project.

Wait for a stable external address and confirm Local health-check behavior. A Pending address is a network integration issue, not a reason to hardcode an arbitrary node IP.

Logs should show the expected version and ordinary leader election or reconciliation, with no forbidden RBAC, missing Secret, CRD, syntax, reload, or watch errors.

System changes

  • No cluster state changes; live Deployment, Pods, Service, class, and logs are inspected.

Syntax explained

rollout status --timeout=5m
Waits for the Deployment's declared replica readiness with a bounded failure point.
custom-columns=...CONTROLLER,DEFAULT
Makes implementation identity and default-class behavior explicit.
logs deployment/... --tail=50
Collects bounded recent controller startup and reconciliation evidence across the Deployment.
Command
Fill variables0/3 ready

Values stay on this page and are never sent or saved.

kubectl -n {{controllerNamespace}} rollout status deployment/{{release}}-nginx-ingress --timeout=5m && kubectl -n {{controllerNamespace}} get pods,service -o wide && kubectl get ingressclass {{ingressClass}} -o custom-columns=NAME:.metadata.name,CONTROLLER:.spec.controller,DEFAULT:.metadata.annotations.ingressclass\.kubernetes\.io/is-default-class && kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=50
Example output / evidence
deployment "edge-nginx-ingress" successfully rolled out
NAME                                      READY   STATUS    RESTARTS   NODE
pod/edge-nginx-ingress-76b9f646dd-2qktm   1/1     Running   0          worker-1
pod/edge-nginx-ingress-76b9f646dd-q9h7w   1/1     Running   0          worker-2
NAME                                    TYPE           EXTERNAL-IP      PORT(S)
service/edge-nginx-ingress-controller   LoadBalancer   203.0.113.42     80:32184/TCP,443:31872/TCP
NAME       CONTROLLER                    DEFAULT
nginx-f5  nginx.org/ingress-controller  <none>
I0728 12:13:04.481 Starting NGINX Ingress Controller Version=5.5.4

Checkpoint: Controller data plane is ready before application routing

kubectl -n {{controllerNamespace}} get deployment {{release}}-nginx-ingress -o jsonpath='ready={.status.readyReplicas} available={.status.availableReplicas}{" image="}{.spec.template.spec.containers[0].image}{"\n"}'

Continue whenTwo replicas are ready and available on controller 5.5.4, class identity is F5 NGINX, and the LoadBalancer has an approved address.

Stop whenA replica is unavailable or restarting, class identity is wrong, address is Pending or unapproved, or logs contain reconciliation, RBAC, TLS, reload, or CRD errors.

If this step fails

The controller is Running but its LoadBalancer Service remains Pending.

Likely causeThe cluster has no compatible cloud load-balancer implementation, annotations or loadBalancerClass are wrong, quota is exhausted, or an on-premises address pool is unavailable.

Safe checks
  • kubectl -n {{controllerNamespace}} describe service {{release}}-nginx-ingress-controller
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp
  • kubectl get nodes -o wide

ResolutionConfigure the platform-supported LoadBalancer integration or deliberately choose NodePort behind an approved external load balancer. Do not publish a random node address or bypass the network owner's source-range and health-check policy.

One controller replica is Ready but the other repeatedly restarts or cannot schedule.

Likely causeResource requests, topology, taints, architecture, image pull, read-only filesystem configuration, or node capacity differs between placements.

Safe checks
  • kubectl -n {{controllerNamespace}} get pods -o wide
  • kubectl -n {{controllerNamespace}} describe pod -l app.kubernetes.io/instance={{release}}
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp

ResolutionKeep traffic on healthy replicas, diagnose the affected node or scheduling constraint, and restore the declared replica count across failure domains. Do not relax security context or pin both replicas to the same node as a shortcut.

Security notes

  • Controller logs can include host, Service, Secret name, and configuration error details; restrict log access appropriately.

Alternatives

  • Use a canary controller class and internal LoadBalancer first when migrating high-risk routes.

Stop conditions

  • Do not create or migrate an application Ingress until controller and publication acceptance passes.
08

config

Create an explicit-class TLS Ingress for the healthy backend

caution

Apply a host, TLS Secret, Prefix path, Service name, and numeric Service port with the dedicated class, then require an accepted event and reported address.

Why this step matters

The application route must opt into the dedicated class and bind one host, namespace-local TLS Secret, path, Service, and numeric port. Explicit fields keep routing ownership and failure boundaries visible and portable.

What to understand

Use `networking.k8s.io/v1`, `pathType: Prefix`, and the current Service backend schema. Avoid implementation-specific annotations until base host, TLS, and backend behavior passes.

The host must be unique among F5 Ingress and VirtualServer resources unless a documented merge pattern is used. Resolve collisions before apply.

Describe output should show the F5 class, expected TLS host and Secret, backend EndpointSlices, assigned address, and an AddedOrUpdated event from the controller.

Keep the Ingress source in version control. Secret content remains outside it, but the Secret name and certificate owner should be documented.

System changes

  • Creates or updates the application Ingress, causing F5 NGINX to render and reload route configuration.

Syntax explained

spec.ingressClassName: {{ingressClass}}
Assigns this route explicitly to the active F5 controller without relying on a default class.
spec.tls[].secretName
References the namespace-local Kubernetes TLS Secret for the SNI host.
pathType: Prefix
Matches the declared path and its subpaths using standard Kubernetes semantics.
service.port.number
Selects the Service port exposed to NGINX; it must map to ready endpoints.
File shop-ingress.yaml
Configuration
Fill variables0/7 ready

Values stay on this page and are never sent or saved.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ingressName}}
  namespace: {{appNamespace}}
spec:
  ingressClassName: {{ingressClass}}
  tls:
    - hosts:
        - {{host}}
      secretName: {{tlsSecret}}
  rules:
    - host: {{host}}
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: {{backendService}}
                port:
                  number: {{backendPort}}
Command
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

kubectl apply -f shop-ingress.yaml && kubectl -n {{appNamespace}} describe ingress {{ingressName}} && kubectl -n {{appNamespace}} get ingress {{ingressName}} -o wide
Example output / evidence
ingress.networking.k8s.io/shop-web created
Name:             shop-web
Namespace:        shop
Address:          203.0.113.42
Ingress Class:    nginx-f5
TLS:
  shop-example-com-tls terminates shop.example.com
Rules:
  Host              Path  Backends
  shop.example.com  /     shop-web:8080 (10.42.1.7:8080,10.42.2.8:8080)
Events:
  Normal  AddedOrUpdated  nginx-ingress-controller  Configuration for shop/shop-web was added or updated

Checkpoint: F5 accepts the route and resolves ready upstreams

kubectl -n {{appNamespace}} describe ingress {{ingressName}}

Continue whenShows class nginx-f5, correct host and TLS Secret, LoadBalancer address, ready backend addresses, and an AddedOrUpdated Normal event.

Stop whenThe route is ignored, rejected, collides, references another class or namespace Secret, lacks endpoints, or produces a controller reload error.

If this step fails

An Ingress shows no address and the controller logs say it is ignoring the resource.

Likely causeThe Ingress uses another ingressClassName, the class controller string does not match `nginx.org/ingress-controller`, the namespace is outside watch scope, or host collision policy rejects it.

Safe checks
  • kubectl -n {{appNamespace}} describe ingress {{ingressName}}
  • kubectl get ingressclass {{ingressClass}} -o yaml
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=200

ResolutionSet the intended class explicitly, verify the F5 class controller string and namespace scope, and resolve duplicate hosts through ownership review. Do not make the controller default merely to capture unclassified Ingresses.

HTTPS returns 502 or 503 while the controller and TLS handshake are healthy.

Likely causeThe backend Service has no ready endpoints, targetPort is wrong, Pods reject traffic, protocol expectations differ, or NetworkPolicy blocks the controller.

Safe checks
  • kubectl -n {{appNamespace}} get service {{backendService}} -o yaml
  • kubectl -n {{appNamespace}} get endpointslice -l kubernetes.io/service-name={{backendService}} -o wide
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=200

ResolutionRestore ready EndpointSlices and correct the Service port, application listener, upstream protocol, or NetworkPolicy. Do not hide a backend outage with a controller-wide timeout increase or error interception.

Security notes

  • Do not enable snippets to copy a community ingress-nginx annotation. Translate behavior only through supported F5 fields after security review.

Alternatives

  • Use F5 VirtualServer only when advanced routing requirements justify custom resources and the organization accepts their CRD lifecycle.

Stop conditions

  • Do not publish DNS while the Ingress lacks an accepted event, address, TLS reference, or ready backend.
09

verification

Publish DNS and verify HTTP, SNI TLS, certificate, and HTTPS externally

read-only

After the network owner points the hostname to the reviewed LoadBalancer address, resolve it from an external client, inspect the SNI certificate and chain, verify redirect policy, and request the application over HTTPS.

Why this step matters

Only an external client exercises authoritative DNS, network firewall, LoadBalancer, source routing, SNI certificate selection, chain trust, redirect policy, NGINX route, Service, and backend together. Internal status cannot prove that path.

What to understand

Wait for DNS TTL and query the intended resolver. During a staged cutover, use a temporary hosts override or `curl --resolve` against the new address without changing public DNS.

Always pass `-servername` to openssl so the SNI virtual host selects the correct certificate. Require chain verification and inspect SAN plus dates.

Test HTTP and HTTPS independently. The expected redirect code and Location must match policy, and HTTPS must return an application-specific response rather than the default backend or another host.

Repeat from more than one network and retain latency, source-IP, and response-header evidence when the route serves public clients.

System changes

  • DNS publication is performed by the network owner outside the shown read-only command; the command only resolves and requests the live path.

Syntax explained

dig +short {{host}} A
Shows the IPv4 address currently returned for the production hostname.
openssl s_client -servername {{host}}
Performs an SNI TLS handshake for the intended virtual host rather than accepting a default certificate.
curl --fail --head
Checks response status and headers while treating HTTP errors as command failure without downloading a body.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

dig +short {{host}} A && echo | openssl s_client -connect {{host}}:443 -servername {{host}} -verify_return_error 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName && curl --fail --silent --show-error --head http://{{host}}/ && curl --fail --silent --show-error --head https://{{host}}/
Example output / evidence
203.0.113.42
subject=CN=shop.example.com
issuer=C=US, O=Example Trust, CN=Example RSA 2026
notBefore=Jul 20 00:00:00 2026 GMT
notAfter=Oct 18 23:59:59 2026 GMT
X509v3 Subject Alternative Name:
    DNS:shop.example.com
HTTP/1.1 301 Moved Permanently
Location: https://shop.example.com/
HTTP/2 200
server: nginx
content-type: text/html; charset=utf-8

Checkpoint: External consumer path passes DNS, TLS, redirect, and application checks

curl --fail --silent --show-error --head https://{{host}}/

Continue whenReturns the intended successful application status and headers with the reviewed certificate through the new endpoint.

Stop whenDNS points elsewhere, certificate SAN or chain fails, HTTP redirect is wrong, HTTPS reaches a default host, backend returns an error, or source-IP policy is violated.

If this step fails

TLS clients receive a certificate for another host or a default certificate.

Likely causeThe Ingress references the wrong Secret, the Secret is in another namespace, SNI host does not match the rule, the certificate chain is incomplete, or DNS points to another ingress endpoint.

Safe checks
  • kubectl -n {{appNamespace}} get ingress {{ingressName}} -o jsonpath='{.spec.tls[*].hosts}{" "}{.spec.tls[*].secretName}{"\n"}'
  • kubectl -n {{appNamespace}} get secret {{tlsSecret}} -o jsonpath='{.type}{" "}{.metadata.name}{"\n"}'
  • openssl s_client -connect {{host}}:443 -servername {{host}} -showcerts </dev/null

ResolutionRepair the namespace-local kubernetes.io/tls Secret through the approved certificate workflow, align the SNI host and Ingress rule, include the required chain, and verify DNS. Never print the private key or copy a TLS Secret across tenants casually.

HTTPS returns 502 or 503 while the controller and TLS handshake are healthy.

Likely causeThe backend Service has no ready endpoints, targetPort is wrong, Pods reject traffic, protocol expectations differ, or NetworkPolicy blocks the controller.

Safe checks
  • kubectl -n {{appNamespace}} get service {{backendService}} -o yaml
  • kubectl -n {{appNamespace}} get endpointslice -l kubernetes.io/service-name={{backendService}} -o wide
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=200

ResolutionRestore ready EndpointSlices and correct the Service port, application listener, upstream protocol, or NetworkPolicy. Do not hide a backend outage with a controller-wide timeout increase or error interception.

HTTP does not redirect to HTTPS as expected.

Likely causeThe Ingress has no TLS block, a global or annotation setting disables SSL redirect, or TLS terminates at an upstream load balancer without the expected forwarded protocol.

Safe checks
  • kubectl -n {{appNamespace}} get ingress {{ingressName}} -o yaml
  • curl -sS -D- -o /dev/null http://{{host}}/
  • kubectl -n {{controllerNamespace}} get configmap -o yaml

ResolutionDeclare TLS for the host and use the documented NGINX redirect behavior appropriate to the termination point. Preserve a bounded validation path for certificate issuance, and do not trust arbitrary forwarded headers from the public internet.

Security notes

  • Use test identities and non-sensitive request paths. Do not place bearer tokens or session cookies in shared command history.

Alternatives

  • Use `curl --resolve {{host}}:443:<new-address>` for pre-DNS testing while preserving correct Host and SNI.

Stop conditions

  • Roll back DNS or route promotion immediately if any region or network receives the wrong certificate, host, redirect, or backend.
10

warning

Exercise replica failure and preserve ready traffic

caution

Disrupt one controller Pod in an approved maintenance test, prove the remaining replica and LoadBalancer serve traffic, then require the Deployment to restore two Ready replicas across nodes.

Why this step matters

Two replicas are only an availability claim until one disappears. A controlled Pod deletion tests Deployment replacement, placement, LoadBalancer health, Local traffic policy, leader election, and continuous route service without changing the durable controller configuration.

What to understand

Run this only in an approved low-risk window after external health is stable. Watch request success continuously and ensure the remaining replica has capacity.

Deletion is appropriate here because the Deployment template is healthy and the purpose is a planned failure test, unlike deleting an unknown CrashLoopBackOff Pod as a repair.

The replacement should schedule on an eligible node, pull the pinned image, become Ready, rejoin the LoadBalancer, and preserve the Helm-controlled configuration.

Record any transient connection loss, TLS error, source-IP change, or leader-election warning even if Kubernetes eventually restores two replicas.

System changes

  • Deletes one controller Pod deliberately; the Deployment creates a replacement from the unchanged reviewed template.

Syntax explained

get pod -l app.kubernetes.io/instance={{release}}
Selects a Pod belonging only to the intended Helm release.
rollout status
Waits for the Deployment to restore its declared availability after the disruption.
curl --fail
Confirms the external route remains successful after controller replacement.
Command
Fill variables0/3 ready

Values stay on this page and are never sent or saved.

kubectl -n {{controllerNamespace}} delete pod $(kubectl -n {{controllerNamespace}} get pod -l app.kubernetes.io/instance={{release}} -o jsonpath='{.items[0].metadata.name}') && kubectl -n {{controllerNamespace}} rollout status deployment/{{release}}-nginx-ingress --timeout=5m && kubectl -n {{controllerNamespace}} get pods -o wide && curl --fail --silent --show-error --head https://{{host}}/
Example output / evidence
pod "edge-nginx-ingress-76b9f646dd-2qktm" deleted
deployment "edge-nginx-ingress" successfully rolled out
NAME                                  READY   STATUS    RESTARTS   NODE
edge-nginx-ingress-76b9f646dd-h5bq2   1/1     Running   0          worker-3
edge-nginx-ingress-76b9f646dd-q9h7w   1/1     Running   0          worker-2
HTTP/2 200
server: nginx

Checkpoint: One-replica failure does not interrupt accepted traffic

kubectl -n {{controllerNamespace}} get pods -l app.kubernetes.io/instance={{release}} -o custom-columns=NAME:.metadata.name,READY:.status.containerStatuses[*].ready,NODE:.spec.nodeName,RESTARTS:.status.containerStatuses[*].restartCount

Continue whenTwo Ready Pods run on acceptable nodes, external HTTPS remains healthy, and no persistent leader, LoadBalancer, or route error appears.

Stop whenTraffic fails, both replicas become unavailable, replacement cannot schedule, source IP changes unexpectedly, or the new Pod cannot reconcile routes.

If this step fails

One controller replica is Ready but the other repeatedly restarts or cannot schedule.

Likely causeResource requests, topology, taints, architecture, image pull, read-only filesystem configuration, or node capacity differs between placements.

Safe checks
  • kubectl -n {{controllerNamespace}} get pods -o wide
  • kubectl -n {{controllerNamespace}} describe pod -l app.kubernetes.io/instance={{release}}
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp

ResolutionKeep traffic on healthy replicas, diagnose the affected node or scheduling constraint, and restore the declared replica count across failure domains. Do not relax security context or pin both replicas to the same node as a shortcut.

Security notes

  • Do not combine this test with a node, network, certificate, or chart maintenance event; isolate the failure variable.

Alternatives

  • Use a platform chaos test that deletes one selected Pod with equivalent guardrails and automatic abort on synthetic failure.

Stop conditions

  • Stop the test and restore the previous traffic path if a single Pod loss causes customer-visible failure.
11

verification

Establish logs, metrics, certificate, route, and release operations

read-only

Record Helm values and revision, expose Prometheus metrics only through the intended monitoring path, monitor reconciliation errors and TLS expiry, and document the certificate rotation and route troubleshooting commands.

Why this step matters

Ingress is a continuously reconciled production data plane. Operators need release identity, effective values, controller logs, events, metrics, TLS expiry, backend health, and external probes before the next certificate or chart change creates an outage.

What to understand

Scrape NGINX metrics through the internal headless Service from the approved monitoring namespace, not from a public LoadBalancer. Alert on unavailable replicas, reload failures, reconciliation rejects, 4xx and 5xx rates, upstream latency, and connection saturation.

Monitor certificate expiry and SAN externally because a Secret object can exist while clients receive an incorrect, expired, or incomplete chain.

For route incidents, read Ingress events, controller logs, Service and EndpointSlices, then external TLS and HTTP. This keeps controller failure separate from backend failure.

Rotate TLS by applying a valid matching Secret through the protected workflow, observe dynamic reload and external certificate serial, and retain the previous certificate according to rollback policy without retaining an exposed private key.

System changes

  • No cluster state changes; Helm release, effective values, metrics Service, and recent logs are read for operational evidence.

Syntax explained

helm get values -a
Shows all effective chart values, including defaults, for drift and upgrade review.
-l service=nginx-ingress-prometheus-service
Selects the internal metrics Service created by the reviewed values.
logs --since=10m
Bounds controller reconciliation evidence to the current change window.
Command
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

helm -n {{controllerNamespace}} list && helm -n {{controllerNamespace}} get values {{release}} -a && kubectl -n {{controllerNamespace}} get service -l service=nginx-ingress-prometheus-service && kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --since=10m | tail -n 100
Example output / evidence
NAME  NAMESPACE      REVISION  UPDATED                  STATUS    CHART                APP VERSION
edge  nginx-ingress  1         2026-07-28 12:10:00Z   deployed  nginx-ingress-2.6.4  5.5.4
NAME                                  TYPE        CLUSTER-IP    PORT(S)
edge-nginx-ingress-prometheus-service ClusterIP   None          9113/TCP
I0728 12:28:41.072 Event(v1.ObjectReference{Kind:"Ingress", Namespace:"shop", Name:"shop-web"}): type: 'Normal' reason: 'AddedOrUpdated'

Checkpoint: Monitoring covers controller, TLS, load balancer, and backend

kubectl -n {{controllerNamespace}} get deployment,service,pods -l app.kubernetes.io/instance={{release}}

Continue whenDashboards and alerts identify release, replicas, reconciliation, NGINX traffic, TLS expiry, external route, LoadBalancer, Service, and backend health.

Stop whenMetrics are exposed publicly, logs contain unresolved errors, certificate expiry is unmonitored, or no external synthetic check exists.

If this step fails

An Ingress shows no address and the controller logs say it is ignoring the resource.

Likely causeThe Ingress uses another ingressClassName, the class controller string does not match `nginx.org/ingress-controller`, the namespace is outside watch scope, or host collision policy rejects it.

Safe checks
  • kubectl -n {{appNamespace}} describe ingress {{ingressName}}
  • kubectl get ingressclass {{ingressClass}} -o yaml
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=200

ResolutionSet the intended class explicitly, verify the F5 class controller string and namespace scope, and resolve duplicate hosts through ownership review. Do not make the controller default merely to capture unclassified Ingresses.

TLS clients receive a certificate for another host or a default certificate.

Likely causeThe Ingress references the wrong Secret, the Secret is in another namespace, SNI host does not match the rule, the certificate chain is incomplete, or DNS points to another ingress endpoint.

Safe checks
  • kubectl -n {{appNamespace}} get ingress {{ingressName}} -o jsonpath='{.spec.tls[*].hosts}{" "}{.spec.tls[*].secretName}{"\n"}'
  • kubectl -n {{appNamespace}} get secret {{tlsSecret}} -o jsonpath='{.type}{" "}{.metadata.name}{"\n"}'
  • openssl s_client -connect {{host}}:443 -servername {{host}} -showcerts </dev/null

ResolutionRepair the namespace-local kubernetes.io/tls Secret through the approved certificate workflow, align the SNI host and Ingress rule, include the required chain, and verify DNS. Never print the private key or copy a TLS Secret across tenants casually.

HTTPS returns 502 or 503 while the controller and TLS handshake are healthy.

Likely causeThe backend Service has no ready endpoints, targetPort is wrong, Pods reject traffic, protocol expectations differ, or NetworkPolicy blocks the controller.

Safe checks
  • kubectl -n {{appNamespace}} get service {{backendService}} -o yaml
  • kubectl -n {{appNamespace}} get endpointslice -l kubernetes.io/service-name={{backendService}} -o wide
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=200

ResolutionRestore ready EndpointSlices and correct the Service port, application listener, upstream protocol, or NetworkPolicy. Do not hide a backend outage with a controller-wide timeout increase or error interception.

The controller can read Secrets outside the intended application scope.

Likely causeIt watches all namespaces with the chart's cluster-wide RBAC defaults, or watch and Secret namespace settings were broader than the tenancy design.

Safe checks
  • helm -n {{controllerNamespace}} get values {{release}} -a
  • kubectl get clusterrole,role -l app.kubernetes.io/instance={{release}} -o yaml
  • kubectl auth can-i list secrets --as=system:serviceaccount:{{controllerNamespace}}:{{release}}-nginx-ingress

ResolutionDefine the supported watch namespace or label and Secret scope, then design RBAC accordingly before hosting untrusted tenants. Do not assume IngressClass alone is a Secret confidentiality boundary.

Security notes

  • Metrics and logs can reveal internal hosts, paths, status codes, upstream addresses, and traffic volume; keep them inside the observability trust boundary.

Alternatives

  • Use the platform's supported OpenTelemetry or managed monitoring integration when it preserves equivalent controller and external signals.

Stop conditions

  • Do not accept production ownership without an on-call route, dashboards, alerts, certificate owner, and documented incident commands.
12

verification

Rehearse rendered upgrades and bounded rollback

read-only

Preserve values and manifests, render any target chart before change, inspect release notes and CRDs separately, use Helm atomic upgrade for namespaced resources, and prove a route-level rollback plan without deleting shared CRDs.

Why this step matters

A future chart upgrade can change CRDs, selectors, Services, class behavior, templates, images, and defaults. Preserving current values and manifests plus rendering the target creates a reviewable diff; Helm history alone cannot safely reverse every cluster-scoped change.

What to understand

Check current F5 technical specifications and release notes for the chart-to-controller mapping and Kubernetes support. Pin the target and inspect its OCI digest.

Upgrade CRDs according to the official release procedure before the chart when required, but review stored versions and every custom-resource consumer. Never delete CRDs as a rollback shortcut.

Render target values, compare RBAC, Service names and selectors, IngressClass settings, image, security context, resources, and ConfigMap behavior. Test route semantics in staging or a canary class.

Use atomic Helm upgrade for namespaced resources and monitor external traffic. Before `helm rollback`, prove the previous controller supports the current CRD schema and configuration.

System changes

  • Writes local backups of effective values and rendered manifests; reads Helm history and CRD inventory without changing the cluster.

Syntax explained

helm get values -a
Preserves the full current input including chart defaults for target comparison.
helm get manifest
Preserves the actual namespaced release objects for diff and recovery evidence.
get crd | grep ...
Inventories shared F5 custom resource definitions which Helm rollback cannot treat like ordinary release objects.
Command
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

helm -n {{controllerNamespace}} get values {{release}} -a > nginx-ingress-values-live.yaml && helm -n {{controllerNamespace}} get manifest {{release}} > nginx-ingress-manifest-live.yaml && helm -n {{controllerNamespace}} history {{release}} && kubectl get crd -o name | grep 'k8s.nginx.org\|nginx.org'
Example output / evidence
REVISION  UPDATED                  STATUS    CHART                APP VERSION  DESCRIPTION
1         Tue Jul 28 12:10:00 2026 deployed  nginx-ingress-2.6.4  5.5.4        Install complete
customresourcedefinition.apiextensions.k8s.io/policies.k8s.nginx.org
customresourcedefinition.apiextensions.k8s.io/transportservers.k8s.nginx.org
customresourcedefinition.apiextensions.k8s.io/virtualserverroutes.k8s.nginx.org
customresourcedefinition.apiextensions.k8s.io/virtualservers.k8s.nginx.org

Checkpoint: Upgrade and rollback are evidence-backed before a future change

helm -n {{controllerNamespace}} history {{release}} && test -s nginx-ingress-values-live.yaml && test -s nginx-ingress-manifest-live.yaml

Continue whenCurrent revision, complete values, rendered manifest, CRDs, target diff procedure, route tests, and rollback compatibility decision are documented.

Stop whenTarget release notes or support are unread, CRD compatibility is unknown, selectors or Service identity change unexpectedly, or the previous revision cannot consume current resources.

If this step fails

A Helm upgrade completes but routes change unexpectedly.

Likely causeChart defaults, CRDs, templates, class settings, Service identity, annotations, or custom NGINX snippets changed between versions.

Safe checks
  • helm -n {{controllerNamespace}} history {{release}}
  • helm -n {{controllerNamespace}} get values {{release}} -a
  • helm -n {{controllerNamespace}} get manifest {{release}}

ResolutionFreeze rollout, compare the rendered old and new manifests plus release notes, roll back the Helm release only when the previous chart and CRDs remain compatible, and repeat route, TLS, source-IP, and failure tests. CRDs require special review because Helm rollback does not automatically reverse their schema safely.

Security notes

  • Helm values and manifests can reveal infrastructure and Secret names. Store them in the protected change record and scrub any secret-bearing values.

Alternatives

  • Use a parallel canary release and class, migrate selected hosts, and remove the old release only after a full acceptance window.

Stop conditions

  • Never use `helm uninstall` followed by CRD deletion as a routine upgrade or rollback strategy.

Finish line

Verification checklist

Active controller identitykubectl get ingressclass {{ingressClass}} -o jsonpath='{.spec.controller}{" default="}{.metadata.annotations.ingressclass\.kubernetes\.io/is-default-class}{"\n"}'Returns `nginx.org/ingress-controller` and no default=true annotation unless a separate cluster-wide decision approved it.
Controller availabilitykubectl -n {{controllerNamespace}} get deployment {{release}}-nginx-ingress -o jsonpath='desired={.spec.replicas} ready={.status.readyReplicas} available={.status.availableReplicas}{"\n"}'Desired, ready, and available all equal two, with replicas distributed according to the reviewed topology.
Backend publicationkubectl -n {{appNamespace}} get endpointslice -l kubernetes.io/service-name={{backendService}} -o wideShows the expected ready application addresses and backend port used by the Ingress.
TLS and external routecurl --fail --silent --show-error --head https://{{host}}/Returns the expected successful application response through external DNS, LoadBalancer, SNI TLS, controller, Service, and ready Pod.
Retired controller guardkubectl get pods -A -l app.kubernetes.io/name=ingress-nginx -o nameReturns no newly installed community ingress-nginx Pod; any legacy result is tracked in an active migration plan.
Release evidencehelm -n {{controllerNamespace}} list && helm -n {{controllerNamespace}} history {{release}}Shows the pinned stable chart deployed at an accepted revision with recoverable prior values and manifests.

Recovery guidance

Common problems and safe checks

The cluster contains Pods or an IngressClass associated with `k8s.io/ingress-nginx`.

Likely causeThe retired community ingress-nginx controller is still installed, or its class remains referenced by workloads.

Safe checks
  • kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx -o wide
  • kubectl get ingressclass -o custom-columns=NAME:.metadata.name,CONTROLLER:.spec.controller
  • kubectl get ingress -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,CLASS:.spec.ingressClassName

ResolutionDo not deploy new traffic to the retired controller. Inventory all dependent Ingress resources, migrate them deliberately to an actively maintained controller or Gateway API, test behavior, and remove the retired deployment only after every route and rollback path is accepted.

Helm reports that the chart version or OCI manifest cannot be found.

Likely causeThe chart version is mistyped, the environment cannot reach ghcr.io, a proxy blocks OCI requests, or an edge version was selected instead of the reviewed stable release.

Safe checks
  • helm version
  • helm show chart oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}}
  • getent ahosts ghcr.io

ResolutionConfirm the stable chart version in the official F5 NGINX technical specifications and installation page, repair approved proxy or registry access, and pull the exact OCI chart. Do not switch to `0.0.0-edge` or an untrusted mirror in production.

The controller Deployment remains unavailable because required CRDs are missing.

Likely causeThe chart was installed with CRDs skipped while custom resources remained enabled, or the installation identity could not create cluster-scoped CRDs.

Safe checks
  • kubectl -n {{controllerNamespace}} get pods -o wide
  • kubectl get crd | grep 'k8s.nginx.org\|nginx.org'
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=100

ResolutionApply the exact CRDs shipped with the same stable controller release or reinstall with the documented chart defaults. Do not grant broad permanent privileges merely to hide a failed one-time CRD operation.

The controller Pod is rejected by Pod Security or another admission policy.

Likely causeThe values file requests a forbidden security context, the namespace policy differs from the tested environment, or another admission controller rejects generated resources.

Safe checks
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp
  • helm template {{release}} oci://ghcr.io/nginx/charts/nginx-ingress --version {{chartVersion}} -n {{controllerNamespace}} -f nginx-ingress-values.yaml
  • kubectl auth can-i create deployments -n {{controllerNamespace}}

ResolutionRead the exact admission message, compare the rendered securityContext with cluster policy, and use supported chart settings. Do not label the namespace privileged or disable admission without a reviewed security exception.

The controller is Running but its LoadBalancer Service remains Pending.

Likely causeThe cluster has no compatible cloud load-balancer implementation, annotations or loadBalancerClass are wrong, quota is exhausted, or an on-premises address pool is unavailable.

Safe checks
  • kubectl -n {{controllerNamespace}} describe service {{release}}-nginx-ingress-controller
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp
  • kubectl get nodes -o wide

ResolutionConfigure the platform-supported LoadBalancer integration or deliberately choose NodePort behind an approved external load balancer. Do not publish a random node address or bypass the network owner's source-range and health-check policy.

An Ingress shows no address and the controller logs say it is ignoring the resource.

Likely causeThe Ingress uses another ingressClassName, the class controller string does not match `nginx.org/ingress-controller`, the namespace is outside watch scope, or host collision policy rejects it.

Safe checks
  • kubectl -n {{appNamespace}} describe ingress {{ingressName}}
  • kubectl get ingressclass {{ingressClass}} -o yaml
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=200

ResolutionSet the intended class explicitly, verify the F5 class controller string and namespace scope, and resolve duplicate hosts through ownership review. Do not make the controller default merely to capture unclassified Ingresses.

TLS clients receive a certificate for another host or a default certificate.

Likely causeThe Ingress references the wrong Secret, the Secret is in another namespace, SNI host does not match the rule, the certificate chain is incomplete, or DNS points to another ingress endpoint.

Safe checks
  • kubectl -n {{appNamespace}} get ingress {{ingressName}} -o jsonpath='{.spec.tls[*].hosts}{" "}{.spec.tls[*].secretName}{"\n"}'
  • kubectl -n {{appNamespace}} get secret {{tlsSecret}} -o jsonpath='{.type}{" "}{.metadata.name}{"\n"}'
  • openssl s_client -connect {{host}}:443 -servername {{host}} -showcerts </dev/null

ResolutionRepair the namespace-local kubernetes.io/tls Secret through the approved certificate workflow, align the SNI host and Ingress rule, include the required chain, and verify DNS. Never print the private key or copy a TLS Secret across tenants casually.

HTTPS returns 502 or 503 while the controller and TLS handshake are healthy.

Likely causeThe backend Service has no ready endpoints, targetPort is wrong, Pods reject traffic, protocol expectations differ, or NetworkPolicy blocks the controller.

Safe checks
  • kubectl -n {{appNamespace}} get service {{backendService}} -o yaml
  • kubectl -n {{appNamespace}} get endpointslice -l kubernetes.io/service-name={{backendService}} -o wide
  • kubectl -n {{controllerNamespace}} logs deployment/{{release}}-nginx-ingress --tail=200

ResolutionRestore ready EndpointSlices and correct the Service port, application listener, upstream protocol, or NetworkPolicy. Do not hide a backend outage with a controller-wide timeout increase or error interception.

HTTP does not redirect to HTTPS as expected.

Likely causeThe Ingress has no TLS block, a global or annotation setting disables SSL redirect, or TLS terminates at an upstream load balancer without the expected forwarded protocol.

Safe checks
  • kubectl -n {{appNamespace}} get ingress {{ingressName}} -o yaml
  • curl -sS -D- -o /dev/null http://{{host}}/
  • kubectl -n {{controllerNamespace}} get configmap -o yaml

ResolutionDeclare TLS for the host and use the documented NGINX redirect behavior appropriate to the termination point. Preserve a bounded validation path for certificate issuance, and do not trust arbitrary forwarded headers from the public internet.

One controller replica is Ready but the other repeatedly restarts or cannot schedule.

Likely causeResource requests, topology, taints, architecture, image pull, read-only filesystem configuration, or node capacity differs between placements.

Safe checks
  • kubectl -n {{controllerNamespace}} get pods -o wide
  • kubectl -n {{controllerNamespace}} describe pod -l app.kubernetes.io/instance={{release}}
  • kubectl -n {{controllerNamespace}} get events --sort-by=.lastTimestamp

ResolutionKeep traffic on healthy replicas, diagnose the affected node or scheduling constraint, and restore the declared replica count across failure domains. Do not relax security context or pin both replicas to the same node as a shortcut.

A Helm upgrade completes but routes change unexpectedly.

Likely causeChart defaults, CRDs, templates, class settings, Service identity, annotations, or custom NGINX snippets changed between versions.

Safe checks
  • helm -n {{controllerNamespace}} history {{release}}
  • helm -n {{controllerNamespace}} get values {{release}} -a
  • helm -n {{controllerNamespace}} get manifest {{release}}

ResolutionFreeze rollout, compare the rendered old and new manifests plus release notes, roll back the Helm release only when the previous chart and CRDs remain compatible, and repeat route, TLS, source-IP, and failure tests. CRDs require special review because Helm rollback does not automatically reverse their schema safely.

The controller can read Secrets outside the intended application scope.

Likely causeIt watches all namespaces with the chart's cluster-wide RBAC defaults, or watch and Secret namespace settings were broader than the tenancy design.

Safe checks
  • helm -n {{controllerNamespace}} get values {{release}} -a
  • kubectl get clusterrole,role -l app.kubernetes.io/instance={{release}} -o yaml
  • kubectl auth can-i list secrets --as=system:serviceaccount:{{controllerNamespace}}:{{release}}-nginx-ingress

ResolutionDefine the supported watch namespace or label and Secret scope, then design RBAC accordingly before hosting untrusted tenants. Do not assume IngressClass alone is a Secret confidentiality boundary.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Use Kubernetes Gateway API with an actively maintained implementation when its routing model and organizational support fit the platform; Kubernetes recommends migration from retired ingress-nginx.
  • Use another actively maintained Ingress controller with verified Kubernetes 1.36 support, documenting its class, annotations, security boundary, migration behavior, and rollback.
  • Use F5 NGINX Ingress Controller LTS or NGINX Plus only when support and licensing requirements justify it; do not mix Plus-only options into the Open Source values.
  • Terminate TLS at an approved external load balancer only when forwarded-protocol trust, certificate ownership, client identity, redirects, and backend encryption are designed explicitly.

Operate it safely

  • Automate certificate issuance and renewal through an approved controller, while retaining external expiry, hostname, chain, and SNI monitoring.
  • Add Prometheus scraping, alerting for unavailable replicas and reconciliation errors, external HTTP and TLS probes, LoadBalancer health, and backend error-rate dashboards.
  • Define a quarterly upgrade procedure that checks current F5 technical specifications, release notes, CRD changes, chart rendering, image provenance, canary traffic, and Helm rollback limits.
  • Migrate any remaining retired ingress-nginx routes to this actively maintained controller or Gateway API, testing annotation differences rather than copying them blindly.
  • Constrain watch namespaces and Secret access where tenancy requires it, and validate RBAC plus audit logs after every chart change.

Reference

Frequently asked questions

Is F5 NGINX Ingress Controller the same as ingress-nginx?

No. F5 NGINX Ingress Controller is an actively maintained implementation using `nginx.org/ingress-controller`. The Kubernetes community ingress-nginx project used `k8s.io/ingress-nginx` and was retired after March 2026.

Why is the IngressClass not made default?

Explicit class selection prevents the new controller from silently claiming existing or future unclassified Ingresses. Making a class default is a cluster-wide routing policy decision that needs separate inventory and approval.

Does HTTPS to NGINX also encrypt traffic to the backend?

Not automatically. Standard TLS termination protects the client-to-NGINX segment. Upstream HTTPS and certificate verification require an explicit supported configuration and trust design.

Can Helm rollback reverse every CRD change?

No. CRDs are cluster-scoped, may be shared, and are not safely treated like ordinary namespaced release resources. Review schema compatibility and consumers before upgrades or rollback.

Recovery

Rollback

Preserve the previous active route or Helm revision until the new F5 controller passes TLS, traffic, replica-loss, and observability acceptance; roll back namespaced release resources deliberately and treat CRDs plus retired-controller migration separately.

  1. Stop DNS or route promotion and preserve controller logs, Ingress events, rendered manifests, live values, Helm history, Service address, EndpointSlices, and TLS test output.
  2. For an application route error, restore the previous reviewed Ingress manifest, class, Service mapping, and TLS Secret reference while keeping certificate material protected.
  3. For a failed namespaced chart upgrade, confirm the previous controller image and chart remain compatible with current CRDs, then use `helm rollback {{release}} <revision> --wait --timeout 10m` and repeat every acceptance test.
  4. Do not delete CRDs during routine rollback or uninstall. They may be shared, and deletion removes all corresponding custom resources across namespaces.
  5. If replacing an existing actively serving controller, keep its class and endpoint available until DNS TTL, connections, route semantics, source IP, TLS, and backends are accepted on F5 NGINX.
  6. Do not roll back new traffic to retired community ingress-nginx after March 2026. Choose another supported controller or Gateway API and follow the incident continuity plan.

Evidence

Sources and review

Verified 2026-07-24Review due 2026-10-22
F5 NGINX Ingress Controller Open Source Helm installationofficialF5 NGINX Ingress Controller technical specificationsofficialF5 NGINX Ingress Controller Helm parametersofficialF5 NGINX Ingress Controller basic TLS configurationofficialF5 NGINX Ingress Controller security recommendationsofficialKubernetes ingress-nginx retirementofficial