Nginx Ingress
This article documents the deprecated nginx ingress controller and how to migrate from it to the HAProxy ingress controller.
The nginx ingress controller is deprecated. Since October 1, 2026, Nine no longer supports or monitors nginx ingress controllers. Migrate your ingress resources to the HAProxy ingress controller by following the migration guide.
IngressClass
To use the deprecated nginx ingress controller, set the ingressClassName
field in your Ingress resource to its configured ingress class, that is, the
class that was set when the controller was deployed.
Access Logs
The nginx ingress logs are available under the Loki label
app="ingress-nginx". Example query:
{app="ingress-nginx",ingress="shop-prod-frontend-80"}
Nginx Ingress Features
All available annotations can be found in the official nginx ingress controller documentation.
Basic authentication
kubectl --namespace=$INGRESS_NAMESPACE annotate ingress $INGRESS nginx.ingress.kubernetes.io/auth-type=basic
kubectl --namespace=$INGRESS_NAMESPACE annotate ingress $INGRESS nginx.ingress.kubernetes.io/auth-secret=$SECRET_NAMESPACE/basic-auth-secret
kubectl --namespace=$INGRESS_NAMESPACE annotate ingress $INGRESS nginx.ingress.kubernetes.io/auth-realm='Authentication required'
Rate limiting
All available options are documented in the official nginx ingress docs.
Temporary and persistent redirects
# Temporary redirect (HTTP 302)
nginx.ingress.kubernetes.io/temporal-redirect: <YOUR URL>
# Permanent redirect (HTTP 301)
nginx.ingress.kubernetes.io/permanent-redirect: <YOUR URL>
HTTPS redirect
nginx.ingress.kubernetes.io/ssl-redirect: "false"
IP whitelisting
nginx.ingress.kubernetes.io/whitelist-source-range: <YOUR CIDR RANGE>
Caching
The nginx ingress controllers allows to enable basic caching of backend
resources, which can be particularly useful for static content. You can enable caching on specific ingress resources by setting the following annotations in your ingress definition (metadata.annotations):
nginx.ingress.kubernetes.io/proxy-buffering: "on"
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_cache static-cache;
proxy_cache_valid 10m;
proxy_cache_use_stale error timeout updating http_404 http_500 http_502 http_503 http_504;
proxy_cache_bypass $http_x_purge;
add_header X-Cache-Status $upstream_cache_status;
In this example, the cache is invalidated after 10 minutes, and only HTTP status codes 200, 301 and 302 are cached. If another behaviour should be desired, proxy_cache_valid can also take a list of status codes in front of the time:
proxy_cache_valid 404 1m;
would cache 404 responses for one minute. There is also the special code any, which can be specified to cache any responses. Additionally, multiple proxy_cache_valid statements can be added on one ingress to specify different cache times for different status codes.
The default cache size is 100MB. Should you have other requirements, please do not hesitate to get in contact with us.
To check that the caching worked, you can use cURL to inspect the header of the returned resource. For that, you will need to execute the command twice, and at the second request, the resource should be cached:
curl --head <URL>
should print the header x-cache-status: HIT.
If you want to enable caching only for a sub-path of your application (for example an endpoint /static), you will need to create two separate ingress resources. To further configure caching, you can use the proxy_cache options that are valid for location blocks. See the nginx proxy_cache module documentation for available options.
Custom default backend
The default backend is responsible for showing a 404 error page if a request arrives on the nginx ingress controller for which no ingress rule was specified. You can create an own custom default backend (+ kubernetes service) and refer to it on your ingress object.
The default backend only has 2 requirements:
- it needs to serve a 404 page/code on the path /
- it needs to serve a 200 HTTP code on the path /healthz
The implementation of the default nginx ingress controller backend can be found in the ingress-gce 404-server source.
Once you built and deployed your default backend service in the same namespace as your ingress resource you can refer to it via the following annotation on your ingress:
nginx.ingress.kubernetes.io/default-backend: <SERVICE NAME OF YOUR DEFAULT BACKEND>
Additional custom error pages
To be able to additionally display custom error pages on the default backend (for example if the service your ingress points to is not available) you can use the following annotation:
nginx.ingress.kubernetes.io/custom-http-errors: <ERROR CODES> # for example: "404,415,503"
The nginx ingress controller will forward error information via certain HTTP headers to your default backend, which then can return the best possible error representation. More information about this can be found in the official documentation. An example of a default backend which can display custom error pages can be found in the ingress-nginx custom-error-pages repository.
Migrating From Nginx to HAProxy
You can run the nginx and HAProxy ingress controllers side by side. This lets you migrate one ingress at a time and verify each application on HAProxy before sending production traffic to it, so no maintenance window is required.
The migration is a per-ingress process: for each nginx Ingress resource you
add an equivalent HAProxy Ingress, verify it, switch DNS, and then remove the
old one.
Migrating Annotations From Nginx
If your ingress resources use nginx-specific annotations, you need to update them to their HAProxy equivalents. Here are the most common ones:
| Nginx annotation | HAProxy annotation |
|---|---|
nginx.ingress.kubernetes.io/ssl-redirect | haproxy-ingress.github.io/ssl-redirect |
nginx.ingress.kubernetes.io/auth-secret | haproxy-ingress.github.io/auth-secret |
nginx.ingress.kubernetes.io/auth-realm | haproxy-ingress.github.io/auth-realm |
nginx.ingress.kubernetes.io/whitelist-source-range | haproxy-ingress.github.io/allowlist-source-range |
nginx.ingress.kubernetes.io/temporal-redirect | haproxy-ingress.github.io/redirect-to (defaults to status code 302) |
nginx.ingress.kubernetes.io/permanent-redirect | haproxy-ingress.github.io/redirect-to + haproxy-ingress.github.io/redirect-to-code: "301" |
nginx.ingress.kubernetes.io/limit-rps | haproxy-ingress.github.io/limit-rps |
If the ingress terminates TLS with a cert-manager certificate, also add
acme.cert-manager.io/http01-ingress-ingressclassname: haproxy so the ACME
HTTP-01 challenge is solved through the HAProxy ingress instead of the cluster's
default ingress class. See Automatic TLS
Certificates.
-
Deploy the HAProxy ingress controller for your cluster in Cockpit. Note its ingress class name (defaults to
haproxy) and its ingress DNS name, both shown in the HAProxy ingress view. See Availability. -
Add a second
Ingressresource for your application that uses the HAProxy ingress class and the equivalent HAProxy annotations, and leave the existing nginx ingress in place for now. See Migrating Annotations From Nginx for the annotation mapping.To find the ingress resources you still need to migrate, list them across all namespaces. The
CLASScolumn shows which controller each one uses:kubectl get ingress --all-namespacesFor example, this nginx ingress:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata:name: helloannotations:nginx.ingress.kubernetes.io/ssl-redirect: "true"spec:ingressClassName: nginxrules:- host: app.example.orghttp:paths:- path: /pathType: Prefixbackend:service:name: helloport:number: 80gets an HAProxy counterpart with the translated
ingressClassNameand annotations. While testing, point it at a hostname under the HAProxy wildcard DNS name so you don't have to change your production DNS yet:apiVersion: networking.k8s.io/v1kind: Ingressmetadata:name: hello-haproxyannotations:haproxy-ingress.github.io/ssl-redirect: "true"spec:ingressClassName: haproxyrules:# a test hostname under the HAProxy wildcard DNS name- host: hello.<haproxy-ingress-dns-name>http:paths:- path: /pathType: Prefixbackend:service:name: helloport:number: 80 -
Verify that your application is reachable through the HAProxy ingress using the test hostname:
curl http://hello.<haproxy-ingress-dns-name>/ -
Switch your DNS records to the HAProxy ingress. Update the CNAME or Alias record for your domain so it points to the HAProxy ingress DNS name instead of the nginx one. You can then replace the test hostname in the HAProxy ingress with your production hostname. See Ingress DNS Name.
-
Remove the old nginx
Ingressresource once all traffic is served through HAProxy:kubectl --namespace=<namespace> delete ingress <nginx-ingress-name> -
Remove the old nginx ingress controller in Cockpit once no
Ingressresources reference its ingress class anymore. If the nginx controller was your cluster's default ingress class, set the HAProxy controller as the new default in Cockpit so ingresses without an explicitingressClassNamekeep working.