Moving from Ingress NGINX to Traefik¶
Caution
This page is being continuously updated as more edge cases of the NGINX Ingress Controller to Traefik migration are being discovered.
Due to the March 2026 retirement of Ingress NGINX, Welkin is adopting Traefik instead as the standard Ingress Controller. We are making the transition as smooth as possible for both Application Developers and Platform Administrators.
Welkin Clusters will run both Ingress NGINX and Traefik during a transitional period to make sure environments have the opportunity to safely migrate. As an application developer, you can start testing on Traefik today by stepwise adapting your services with just a few changes at a time.
Prerequisites¶
Confirm both controllers are operational.
kubectl get pods -n ingress-nginx
kubectl get pods -n traefik
You should be able to resolve the *.traefik.$DOMAIN to the Traefik LoadBalancer IP using for example dig.
export TRAEFIK_EXTERNAL_LB=$(dig "*.traefik.$DOMAIN" +short)
The Migration Strategy¶
To guarantee service continuity, we utilize a side-by-side validation strategy, where Traefik is configured to shadow your existing NGINX Ingress resources. By running both controllers simultaneously, you are able to verify your services in isolation before redirecting any production traffic.
Clone and Patch¶
For all Ingress resources, follow these steps to migrate from NGINX to Traefik:
- Create a copy of the existing Ingress resource.
- Update the
ingressClassNametotraefik. - Replace NGINX-specific annotations or configuration snippets with the corresponding Traefik Middlewares.
Note
Traefik natively supports all standard Kubernetes Ingress fields.
If your Ingress uses only standard fields like paths and hosts without custom annotations, simply changing the ingressClassName is often sufficient.
Please read "Converting Annotations to Traefik Middlewares" for information about annotation support and instructions on how to create appropriate Traefik Middlewares.
Validation¶
You can verify that your services are running correctly on Traefik by sending a request directly to the Traefik LoadBalancer IP. This allows you to test the migration in isolation while your production DNS continues to point safely to NGINX.
Option 1: Client-Side Resolution¶
You can test routing without changing DNS by running:
curl --resolve example.base-domain.com:443:$TRAEFIK_EXTERNAL_LB https://example.base-domain.com
Alternatively, by updating your local /etc/hosts file to point your domain to the $TRAEFIK_EXTERNAL_LB IP.
This allows you to test the application in a browser on standard ports.
Option 2: Separate DNS¶
You can create separate Ingress objects and DNS pointing to the $TRAEFIK_EXTERNAL_LB IP.
The *.traefik.$DOMAIN DNS record pointing to the Traefik LoadBalancer can also be used directly.
The Permanent Switch¶
When validation is successful update your public DNS records to point to the Traefik LoadBalancer IP. Once propagation is complete, delete the old NGINX Ingress.
Converting Annotations to Traefik Middlewares¶
Traefik provides support for translating many common Ingress-NGINX annotations when using the nginx Ingress class name, though some may behave slightly differently.
However, not all annotations are supported. Some require additional steps and need to be converted to Traefik Middlewares. For more details on which annotations are supported or unsupported, see Traefik & Ingresses with NGINX annotations.
Caution
Due to a limitation in how Traefik's translation between Ingress NGINX and its own configuration format works: if you continue to use Ingress-NGINX annotations, you cannot use Traefik Middlewares on the same resource. We therefore recommend a full conversion to Traefik Middlewares to avoid configuration issues.
In the following sections below are step-by-step guides to help you convert various types of annotations into their Traefik Middleware equivalents.
For rate-limiting and allowlisting -- which need a conversion from NGINX annotation to Traefik Middleware -- please find an example in our documentation on Network Model.
Here the allowlist annotation nginx.ingress.kubernetes.io/whitelist-source-range: 98.128.193.2/32 is currently not a supported annotation.
To keep the allow-list feature a Middleware resource is created:
# Blocklisted IP's will get HTTP 403.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: demo-allowlist
namespace: <namespace>
spec:
ipAllowList:
sourceRange:
- 98.128.193.2/32
And the Traefik-specific Middleware annotation is added to the Ingress resource:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-app
annotations:
# This Traefik-specific annotation "plugs the gap"
traefik.ingress.kubernetes.io/router.middlewares: <namespace>-demo-allowlist@kubernetescrd
spec:
# Remember to also update the Ingress class name to Traefik
ingressClassName: traefik
...
Metrics¶
| Ingress-NGINX Metric | Traefik equivalent | Notes |
|---|---|---|
nginx_ingress_controller_requests |
traefik_service_requests_total |
Filter by code instead of status. |
nginx_ingress_controller_request_duration_seconds |
traefik_service_request_duration_seconds |
Warning: Histogram buckets differ by default. (See longer explanation below) |
nginx_ingress_controller_response_duration_seconds |
traefik_service_request_duration_seconds |
Partial: Traefik bundles "wait + processing" into a single metric. It does not separate "upstream time" from "total time" in metrics. (See longer explanation below) |
nginx_ingress_controller_header_duration_seconds |
MISSING | |
nginx_ingress_controller_connect_duration_seconds |
MISSING | |
nginx_ingress_controller_response_size |
MISSING (Histogram) | Traefik provides a Counter (traefik_service_responses_bytes_total) but no Histogram. You can track bandwidth, but not "size distribution." |
nginx_ingress_controller_request_size |
MISSING (Histogram) | Traefik provides a Counter (traefik_service_requests_bytes_total) but no Histogram. |
nginx_ingress_controller_bytes_sent |
traefik_service_responses_bytes_total |
|
nginx_ingress_controller_nginx_process_connections |
traefik_open_connections |
|
nginx_ingress_controller_nginx_process_connections_total |
traefik_entrypoint_requests_total |
|
nginx_ingress_controller_nginx_process_cpu_seconds_total |
process_cpu_seconds_total |
|
nginx_ingress_controller_nginx_process_num_procs |
go_goroutines |
NGINX is multi-process (workers), Traefik is single-process (Goroutines). |
nginx_ingress_controller_nginx_process_oldest_start_time_seconds |
process_start_time_seconds |
|
nginx_ingress_controller_nginx_process_read_bytes_total |
traefik_entrypoint_requests_bytes_total |
|
nginx_ingress_controller_nginx_process_requests_total |
traefik_entrypoint_requests_total |
|
nginx_ingress_controller_nginx_process_resident_memory_bytes |
process_resident_memory_bytes |
|
nginx_ingress_controller_nginx_process_virtual_memory_bytes |
process_virtual_memory_bytes |
|
nginx_ingress_controller_nginx_process_write_bytes_total |
traefik_entrypoint_responses_bytes_total |
|
nginx_ingress_controller_build_info |
MISSING | Traefik does not output a specific metric for this. Could maybe use kube_pod_container_info instead. |
nginx_ingress_controller_check_success |
MISSING | Traefik lacks the this metric because it updates routing rules dynamically in memory, whereas NGINX must validate a static configuration file before every reload. |
nginx_ingress_controller_config_hash |
MISSING | Traefik does not expose a config hash. Use traefik_config_last_reload_success to verify sync timestamp. |
nginx_ingress_controller_config_last_reload_successful |
MISSING | |
nginx_ingress_controller_config_last_reload_successful_timestamp_seconds |
traefik_config_last_reload_success |
Timestamp of last successful config update. |
nginx_ingress_controller_ssl_certificate_info |
traefik_tls_certs_not_after |
Like NGINX, Traefik puts the cert details (CN, Issuer) in the labels. However, the value Differs, Traefik is the expiry timestamp and NGINX is 1. |
nginx_ingress_controller_success |
MISSING | traefik_config_reloads_total could possibly be used to prove the controller is active but they are not counting the same thing. |
nginx_ingress_controller_orphan_ingress |
MISSING | |
nginx_ingress_controller_admission_config_size |
MISSING | |
nginx_ingress_controller_admission_render_duration |
MISSING | |
nginx_ingress_controller_admission_render_ingresses |
MISSING | |
nginx_ingress_controller_admission_roundtrip_duration |
MISSING | |
nginx_ingress_controller_admission_tested_duration |
MISSING | |
nginx_ingress_controller_admission_tested_ingresses |
MISSING |
Troubleshooting¶
See Traefik's official migration docs for more troubleshooting advice.