For AI agents: the complete documentation index is available at https://docs.ovhcloud.com/pt/llms.txt, the full documentation bundle is available at https://docs.ovhcloud.com/pt/llms-full.txt, and this page is available as Markdown at https://docs.ovhcloud.com/pt/guides/public-cloud/containers-orchestration/managed-kubernetes/migrating-nginx-to-traefik.md.

Migrating from NGINX Ingress Controller to Traefik on OVHcloud Managed Kubernetes

Ver como Markdown

Find out how to migrate from NGINX Ingress Controller to Traefik on your OVHcloud MKS cluster with zero downtime

Objective

The Kubernetes NGINX Ingress Controller project has announced its retirement in March 2026. This guide helps OVHcloud Managed Kubernetes Service (MKS) users migrate from NGINX Ingress Controller to Traefik with zero downtime, covering OVHcloud-specific considerations.

Traefik v3.6.2+ includes a Kubernetes Ingress NGINX provider that automatically translates NGINX annotations into native Traefik configuration. Your existing Ingress manifests with ingressClassName: nginx therefore work immediately, without modification.

Info

This guide focuses on OVHcloud-specific configurations. For the complete migration procedure, refer to the official Traefik nginx-to-traefik migration guide.

For a broader perspective on the transition from Ingress to Gateway API, read the OVHcloud blog post: Moving beyond Ingress: Why should OVHcloud MKS users start looking at the Gateway API.

Warning

This guide applies to MKS clusters running Kubernetes version 1.31 or later, which use the Public Cloud Load Balancer (OpenStack Octavia) by default.

Before you begin

This tutorial assumes that you already have:

  • a working OVHcloud Managed Kubernetes Service cluster (version >= 1.31)
  • the NGINX Ingress Controller deployed and serving traffic
  • kubectl and helm CLI tools installed with cluster admin permissions
  • DNS access to update records for your services

We recommend creating backups before starting:

# Export all Ingress resources
kubectl get ingress --all-namespaces -o yaml > ingress-backup.yaml

# Export NGINX ConfigMaps
kubectl get configmap --all-namespaces -l app.kubernetes.io/name=ingress-nginx -o yaml > nginx-configmaps.yaml

You should also be familiar with:

OVHcloud-specific considerations

Load Balancer (OpenStack Octavia)

On MKS clusters >= 1.31, the Public Cloud Load Balancer (based on OpenStack Octavia) is the default Load Balancer. Octavia operates at Layer 4 (TCP/UDP), meaning:

  • TLS termination is handled by Traefik, not by the Load Balancer.
  • Layer 7 routing (host-based, path-based) is handled by Traefik.
  • Octavia provides health checking, Floating IP management, and traffic distribution.

You can optionally choose the Load Balancer size using the annotation loadbalancer.openstack.org/flavor-id (MKS Standard only; MKS Free uses loadbalancer.ovhcloud.com/flavor). If not specified, the default size is small. To list available flavors, install and configure the OVHcloud CLI and run ovhcloud cloud reference loadbalancer list-flavors <REGION>. For more details, see the Expose your applications using OVHcloud Public Cloud Load Balancer guide.

Proxy Protocol and source IP preservation

To preserve the client's source IP behind the Load Balancer, you must enable Proxy Protocol v2 between Octavia and Traefik. This requires configuration on both sides:

On the Load Balancer side (Service annotation):

annotations:
  loadbalancer.openstack.org/proxy-protocol: "v2"

On the Traefik side (Helm values), you must configure Traefik to accept Proxy Protocol and trust the Load Balancer IPs:

  • Public network clusters: use the egress IPs from the Load Balancer annotation:
kubectl get svc ingress-nginx-controller -n ingress-nginx \
  -o jsonpath="{.metadata.annotations.lb\.k8s\.ovh\.net/egress-ips}"
  • Private network clusters: use your subnet CIDR range (e.g., 10.0.0.0/20).

You must also set externalTrafficPolicy: Local on the Traefik Service to prevent source IP masquerading through inter-node SNAT.

CNI considerations

MKS PlanCNINotes
FreeCanal (Flannel + Calico)Standard Kubernetes NetworkPolicies
StandardCilium (eBPF)CiliumNetworkPolicy available, kube-proxy replaced

If you have NetworkPolicies targeting NGINX Ingress pods (labels, ports), you will need to update them to match Traefik's labels (app.kubernetes.io/name: traefik) and ports.

Step 1 - Install Traefik alongside NGINX

Add the Traefik Helm repository:

helm repo add traefik https://traefik.github.io/charts
helm repo update

Create a traefik-values.yaml file adapted for OVHcloud MKS:

a. Public network clusters

providers:
  kubernetesIngressNginx:
    enabled: true

service:
  externalTrafficPolicy: Local
  annotations:
    loadbalancer.openstack.org/proxy-protocol: "v2"
    loadbalancer.openstack.org/keep-floatingip: "true"

ports:
  web:
    proxyProtocol:
      trustedIPs:
        - "aaa.aaa.aaa.aaa/32"  # Replace with your egress IPs
        - "bbb.bbb.bbb.bbb/32"
  websecure:
    proxyProtocol:
      trustedIPs:
        - "aaa.aaa.aaa.aaa/32"  # Replace with your egress IPs
        - "bbb.bbb.bbb.bbb/32"

deployment:
  replicas: 2

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app.kubernetes.io/name: traefik
        topologyKey: kubernetes.io/hostname

podDisruptionBudget:
  enabled: true
  minAvailable: 1

b. Private network clusters

providers:
  kubernetesIngressNginx:
    enabled: true

service:
  externalTrafficPolicy: Local
  annotations:
    loadbalancer.openstack.org/proxy-protocol: "v2"
    loadbalancer.openstack.org/keep-floatingip: "true"

ports:
  web:
    proxyProtocol:
      trustedIPs:
        - "10.0.0.0/20"  # Replace with your subnet CIDR range
  websecure:
    proxyProtocol:
      trustedIPs:
        - "10.0.0.0/20"  # Replace with your subnet CIDR range

deployment:
  replicas: 2

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app.kubernetes.io/name: traefik
        topologyKey: kubernetes.io/hostname

podDisruptionBudget:
  enabled: true
  minAvailable: 1

Install Traefik:

helm upgrade --install traefik traefik/traefik \
  --namespace traefik --create-namespace \
  --values traefik-values.yaml

Verify both controllers are running:

kubectl get pods -n ingress-nginx
kubectl get pods -n traefik
kubectl get svc -n ingress-nginx ingress-nginx-controller
kubectl get svc -n traefik traefik
Warning

The key configuration providers.kubernetesIngressNginx.enabled: true tells Traefik to watch for Ingress resources with ingressClassName: nginx and automatically translate NGINX annotations into native Traefik configuration. This requires Traefik v3.6.2 or later.

Step 2 - Verify Traefik handles traffic

Get the LoadBalancer IPs for both controllers:

NGINX_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
TRAEFIK_IP=$(kubectl get svc -n traefik traefik \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "NGINX IP: $NGINX_IP"
echo "Traefik IP: $TRAEFIK_IP"

Test your application through both controllers using curl --connect-to to bypass DNS:

FQDN=myapp.example.com

# Test via NGINX
curl --connect-to "${FQDN}:80:${NGINX_IP}:80" "http://${FQDN}"

# Test via Traefik
curl --connect-to "${FQDN}:80:${TRAEFIK_IP}:80" "http://${FQDN}"

Both commands should return the same response. Also verify that the source IP is correctly preserved in the X-Real-IP or X-Forwarded-For headers.

Check Traefik logs for Ingress discovery:

kubectl logs -n traefik deployment/traefik | grep -i "ingress"

Step 3 - Shift traffic to Traefik (DNS-based)

The recommended approach is a DNS-based migration:

  1. Add the Traefik IP to your DNS records alongside the NGINX IP (round-robin).
  2. Monitor traffic on both controllers to ensure Traefik handles requests correctly.
  3. Remove the NGINX IP from your DNS records.
  4. Wait 24-48 hours for DNS caches to expire before proceeding to the next step.
Warning

Some ISPs ignore DNS TTL values, caching records longer than specified. Keep NGINX running for at least 24-48 hours after removing it from DNS to avoid dropping traffic.

We recommend reducing your DNS TTL to 300 seconds (5 minutes) before starting the migration. For detailed DNS switch instructions, see How to perform a DNS switch.

Step 4 - Retain the LoadBalancer Floating IP (OVHcloud-specific)

If you want Traefik to use the same IP as NGINX (to avoid DNS changes), follow this procedure:

  1. Ensure the keep-floatingip annotation is set on both services:
kubectl annotate svc -n ingress-nginx ingress-nginx-controller \
  loadbalancer.openstack.org/keep-floatingip="true"
kubectl annotate svc -n traefik traefik \
  loadbalancer.openstack.org/keep-floatingip="true"
  1. Ensure Traefik is receiving traffic (via its own IP or DNS round-robin).

  2. Delete the NGINX LoadBalancer service to release the Floating IP:

kubectl delete svc -n ingress-nginx ingress-nginx-controller
  1. Update traefik-values.yaml to claim the released Floating IP:
service:
  spec:
    loadBalancerIP: "<nginx-floating-ip>"
  1. Upgrade Traefik:
helm upgrade traefik traefik/traefik \
  --namespace traefik \
  --values traefik-values.yaml
  1. Verify Traefik claimed the IP:
kubectl get svc -n traefik traefik

Step 5 - Uninstall NGINX Ingress Controller

Preserve the IngressClass

The nginx IngressClass must survive the NGINX uninstallation for Traefik to continue discovering your Ingress resources.

If NGINX was installed via Helm, add the preservation annotation:

helm upgrade ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx \
  --reuse-values \
  --set-json 'controller.ingressClassResource.annotations={"helm.sh/resource-policy": "keep"}'
Warning

The --reuse-values flag is critical: it preserves all your existing NGINX configuration during this annotation update.

Delete admission webhooks

kubectl delete validatingwebhookconfiguration ingress-nginx-admission
kubectl delete mutatingwebhookconfiguration ingress-nginx-admission --ignore-not-found

Uninstall NGINX

helm uninstall ingress-nginx -n ingress-nginx

Verify the IngressClass is preserved

kubectl get ingressclass nginx

You should see the nginx IngressClass still present.

Clean up

kubectl delete namespace ingress-nginx

Next steps: Gateway API

While Traefik with the NGINX Ingress provider is a suitable immediate solution, we recommend migrating to the Kubernetes Gateway API in the long term. Traefik natively supports Gateway API resources (HTTPRoute, TLSRoute, GRPCRoute, TCPRoute).

The recommended migration path is:

NGINX Ingress (current) → Traefik with NGINX provider (this guide) → Traefik with Gateway API

For more details on Gateway API with OVHcloud MKS, read the blog post: Moving beyond Ingress: Why should OVHcloud MKS users start looking at the Gateway API.

Troubleshooting

Ingresses not discovered by Traefik

# Verify IngressClass exists
kubectl get ingressclass nginx

# Check Traefik provider configuration
kubectl logs -n traefik deployment/traefik | grep -i "nginx\|ingress"

# Verify Ingress has correct ingressClassName
kubectl get ingress <name> -o yaml | grep ingressClassName

Source IP not preserved

  • Verify Proxy Protocol is enabled on the Service: loadbalancer.openstack.org/proxy-protocol: "v2".
  • Verify externalTrafficPolicy: Local is set on the Traefik Service.
  • Verify Traefik's proxyProtocol.trustedIPs match your Load Balancer egress IPs (public clusters) or subnet CIDR (private clusters).
  • Check the X-Real-IP and X-Forwarded-For headers in your application responses.

LoadBalancer IP not assigned

# Check service status
kubectl describe svc -n traefik traefik

# Check for events
kubectl get events -n traefik --sort-by='.lastTimestamp'

Verify that the Floating IP you are trying to claim is not still allocated to another service.

TLS certificates not working

Traefik terminates TLS using the secrets referenced in your Ingress spec.tls entries. Ensure that:

  • TLS secrets exist in the same namespace as the Ingress.
  • secrets contain valid tls.crt and tls.key data.
kubectl get secrets -n <namespace>
kubectl get secret <tls-secret-name> -n <namespace> -o yaml

Go further

Official Traefik migration guide: Migrating from NGINX to Traefik

OVHcloud blog: Moving beyond Ingress

Expose your applications using OVHcloud Public Cloud Load Balancer

Getting the source IP behind the LoadBalancer

Traefik HTTP Middlewares (replacement for NGINX annotations)

Visit our dedicated Discord channel: https://discord.gg/ovhcloud. Ask questions, provide feedback and interact directly with the team that builds our Containers and Orchestration services.

For training or technical assistance implementing our solutions, contact your sales representative or visit our Professional Services page to request a quote and have your project analysed by our experts.

Join our community of users.

Esta página foi útil?