Kubernetes Ingress Controllers Compared: nginx vs Traefik vs Azure Application Gateway


Executive Snapshot

Category NGINX Ingress Controller Traefik Azure Application Gateway Ingress Controller (AGIC)
Best For General-purpose, high-traffic workloads Cloud-native, dynamic microservices Azure-native AKS workloads needing WAF
Configuration Model Annotations + ConfigMaps CRDs + Labels + TOML/YAML ARM/Bicep + Kubernetes Annotations
Auto Service Discovery Manual reload required Native, zero-downtime Via Azure Resource Manager
TLS Termination Yes (cert-manager integration) Yes (built-in ACME/Let's Encrypt) Yes (Azure Key Vault integration)
WAF Support Via ModSecurity module Via Traefik Enterprise Native (Azure WAF Policy)
Observability Prometheus metrics, access logs Built-in dashboard + Prometheus Azure Monitor + App Gateway metrics
Pricing Model Free (OSS) / NGINX Plus (commercial) Free (OSS) / Enterprise Pay-per-hour (Azure resource cost)
Horizontal Scalability Excellent Excellent Managed by Azure (limited control)
Multi-cluster Support With additional tooling Native (Traefik Enterprise) Azure-native via Traffic Manager
Learning Curve Moderate Low–Moderate Moderate–High (Azure knowledge required)

TL;DR

  • NGINX Ingress Controller is the battle-tested default — ideal for teams that need maximum configurability, high throughput, and broad community support, but it requires manual ConfigMap tuning and reload cycles.
  • Traefik wins on developer experience and dynamic service discovery, making it the strongest choice for rapidly-changing microservices environments with built-in Let's Encrypt automation.
  • Azure Application Gateway Ingress Controller (AGIC) is the right call for AKS-first shops that need enterprise WAF, SSL offloading via Key Vault, and deep Azure PaaS integration — but you pay for the gateway resource hourly.
  • No single controller is universally best — your choice should be driven by traffic patterns, team Azure/Linux expertise, WAF requirements, and cost tolerance.
  • All three controllers support the standard Kubernetes Ingress resource, but their full power is unlocked only via controller-specific CRDs or annotations.

Introduction

Every production Kubernetes cluster eventually needs a front door — a layer-7 traffic router that can terminate TLS, route by hostname and path, enforce rate limits, and expose dozens of services through a single load balancer IP. That front door is your Ingress controller, and your choice of controller will shape your cluster's reliability, security posture, and operational complexity for years.

flowchart TD
  Client["Client"] --> LB["Load balancer"]
  LB --> Ingress["Ingress controller"]
  Ingress -->|host / path rule| SvcA["Service A"]
  Ingress -->|host / path rule| SvcB["Service B"]
  SvcA --> PodA["Pods"]
  SvcB --> PodB["Pods"]
Figure: How an ingress controller routes external traffic to Kubernetes services.

The market has consolidated around three dominant players for most enterprise environments: NGINX Ingress Controller (the Kubernetes community's long-standing workhorse), Traefik (the cloud-native challenger built for dynamic environments), and Azure Application Gateway Ingress Controller (Microsoft's managed gateway deeply wired into AKS). Each reflects a fundamentally different philosophy about where routing intelligence should live, how configuration should be expressed, and who should own the operational burden.

This kubernetes ingress controller comparison cuts through the marketing noise. We'll install all three, configure identical routing scenarios, stress-test their operational characteristics, and give you a clear decision framework — backed by real commands you can run today.


Prerequisites

  • A running Kubernetes cluster (v1.27+) — AKS, EKS, GKE, or local (kind/k3s) for NGINX and Traefik sections
  • An AKS cluster (v1.27+) with a dedicated node pool for the AGIC section
  • kubectl configured and pointing at your target cluster
  • helm v3.12+ installed locally
  • Azure CLI (az) v2.50+ for the AGIC section
  • A domain name with DNS you can control (for TLS examples)
  • Basic familiarity with Kubernetes Ingress, Service, and Deployment resources
  • curl and k6 or wrk for load testing (optional but recommended)

1. The Kubernetes Ingress Landscape in 2026

Before diving into the kubernetes ingress controller comparison, it's worth understanding what you're actually comparing. The Kubernetes Ingress API (networking.k8s.io/v1) defines the intent — "route traffic for api.example.com/v2 to the api-service on port 8080" — but it deliberately says nothing about implementation. That gap is where Ingress controllers live.

The critical nuance: the standard Ingress spec is intentionally limited. Features like circuit breaking, sticky sessions, header manipulation, gRPC routing, and middleware chains are all controller-specific extensions, expressed through:

  • Annotations (all controllers, but brittle at scale)
  • Custom Resource Definitions (CRDs) — NGINX's VirtualServer, Traefik's IngressRoute, AGIC's integration with AzureApplicationGateway
  • Helm values that configure global controller behavior

The Gateway API (gateway.networking.k8s.io), introduced as GA in Kubernetes 1.28, is the long-term successor to Ingress. All three controllers now have Gateway API support at varying maturity levels — we'll cover this at the end.


2. NGINX Ingress Controller

2.1 Architecture Overview

The NGINX Ingress Controller (maintained by the Kubernetes community at kubernetes/ingress-nginx, distinct from NGINX Inc.'s commercial nginx/kubernetes-ingress) runs as a Deployment that watches the Kubernetes API for Ingress resources and dynamically rewrites an nginx.conf file, reloading NGINX worker processes when configuration changes. This reload cycle is the controller's most discussed limitation — more on that in the pitfalls section.

[Internet] → [LoadBalancer Service] → [NGINX Pod(s)] → [Backend Services]
                                            ↑
                             [nginx.conf regenerated on Ingress change]

2.2 Installation via Helm

# Add the ingress-nginx Helm repo
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

# Install with production-grade settings
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.replicaCount=3 \
  --set controller.metrics.enabled=true \
  --set controller.metrics.serviceMonitor.enabled=true \
  --set controller.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey=kubernetes.io/hostname \
  --set controller.resources.requests.cpu=200m \
  --set controller.resources.requests.memory=256Mi \
  --set controller.resources.limits.cpu=1000m \
  --set controller.resources.limits.memory=512Mi \
  --version 4.10.1

# Verify rollout
kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx
kubectl get svc -n ingress-nginx

2.3 Basic Ingress with TLS

# nginx-ingress-example.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  namespace: production
  annotations:
    kubernetes.io/ingress.class: "nginx"
    # Rate limiting: 100 req/min per IP
    nginx.ingress.kubernetes.io/limit-rps: "100"
    # Enable CORS
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.example.com"
    # Upstream keepalive
    nginx.ingress.kubernetes.io/upstream-keepalive-connections: "32"
    # Custom timeouts
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    # Force HTTPS redirect
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls-secret
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /v1
            pathType: Prefix
            backend:
              service:
                name: api-v1-service
                port:
                  number: 8080
          - path: /v2
            pathType: Prefix
            backend:
              service:
                name: api-v2-service
                port:
                  number: 8080

2.4 Advanced: VirtualServer CRD for Canary Deployments

The VirtualServer CRD (part of the nginx/kubernetes-ingress commercial variant but also available in extended OSS builds) unlocks proper traffic splitting without annotation soup:

# nginx-virtualserver-canary.yaml
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
  name: api-canary
  namespace: production
spec:
  host: api.example.com
  tls:
    secret: api-tls-secret
  upstreams:
    - name: api-stable
      service: api-stable-service
      port: 8080
      healthCheck:
        enable: true
        path: /healthz
        interval: 10s
        fails: 3
        passes: 2
    - name: api-canary
      service: api-canary-service
      port: 8080
  routes:
    - path: /
      splits:
        - weight: 90
          action:
            pass: api-stable
        - weight: 10
          action:
            pass: api-canary

2.5 NGINX Tuning: ConfigMap for High-Traffic Production

# nginx-configmap-tuning.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  # Worker processes (match to CPU cores)
  worker-processes: "auto"
  # Max connections per worker
  max-worker-connections: "65536"
  # Keepalive timeout
  keep-alive: "75"
  keep-alive-requests: "10000"
  # Enable Brotli compression
  enable-brotli: "true"
  brotli-level: "6"
  brotli-types: "text/plain text/css application/json application/javascript"
  # Log format with request ID for tracing
  log-format-upstream: >
    {"time":"$time_iso8601","remote_addr":"$proxy_protocol_addr",
    "x_forwarded_for":"$proxy_add_x_forwarded_for","request_id":"$req_id",
    "remote_user":"$remote_user","bytes_sent":"$bytes_sent",
    "request_time":"$request_time","status":"$status",
    "host":"$host","request_uri":"$request_uri","method":"$request_method",
    "http_referrer":"$http_referer","http_user_agent":"$http_user_agent",
    "upstream_addr":"$upstream_addr","upstream_status":"$upstream_status",
    "upstream_response_time":"$upstream_response_time"}
  # Proxy buffer settings for large headers (common with JWT tokens)
  proxy-buffer-size: "16k"
  proxy-buffers-number: "4"
  # HSTS
  hsts: "true"
  hsts-max-age: "31536000"
  hsts-include-subdomains: "true"

3. Traefik Ingress Controller

3.1 Architecture Overview

Traefik takes a fundamentally different approach. Instead of periodically regenerating a static config file, Traefik maintains a live routing table that updates in real-time as Kubernetes resources change — no reload, no dropped connections. Its architecture centers on Providers (Kubernetes, Docker, Consul, etc.), Routers (match rules), Middlewares (transform requests), and Services (upstream targets).

[Internet] → [LoadBalancer] → [Traefik Pod(s)] → [Backend Services]
                                    ↑
                    [Live routing table — zero reload]
                    Kubernetes Provider watches:
                    - Ingress resources
                    - IngressRoute CRDs
                    - Middleware CRDs

3.2 Installation via Helm

# Add Traefik Helm repo
helm repo add traefik https://traefik.github.io/charts
helm repo update

# Install Traefik with production settings
helm install traefik traefik/traefik \
  --namespace traefik \
  --create-namespace \
  --set deployment.replicas=3 \
  --set ports.websecure.tls.enabled=true \
  --set ingressClass.enabled=true \
  --set ingressClass.isDefaultClass=true \
  --set metrics.prometheus.enabled=true \
  --set metrics.prometheus.serviceMonitor.enabled=true \
  --set logs.general.level=INFO \
  --set logs.access.enabled=true \
  --set logs.access.format=json \
  --set resources.requests.cpu=200m \
  --set resources.requests.memory=256Mi \
  --set resources.limits.cpu=1000m \
  --set resources.limits.memory=512Mi \
  --set autoscaling.enabled=true \
  --set autoscaling.minReplicas=3 \
  --set autoscaling.maxReplicas=10 \
  --version 28.0.0

# Verify
kubectl rollout status deployment/traefik -n traefik
kubectl get svc -n traefik

3.3 IngressRoute CRD with Middleware Chain

Traefik's IngressRoute CRD is far more expressive than standard Ingress annotations:

# traefik-ingressroute.yaml
---
# Rate limiting middleware
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: rate-limit
  namespace: production
spec:
  rateLimit:
    average: 100
    burst: 50
    period: 1m
    sourceCriterion:
      ipStrategy:
        depth: 1

---
# Security headers middleware
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: secure-headers
  namespace: production
spec:
  headers:
    sslRedirect: true
    stsSeconds: 31536000
    stsIncludeSubdomains: true
    stsPreload: true
    forceSTSHeader: true
    contentTypeNosniff: true
    browserXssFilter: true
    referrerPolicy: "strict-origin-when-cross-origin"
    permissionsPolicy: "camera=(), microphone=(), geolocation=()"
    customRequestHeaders:
      X-Forwarded-Proto: https

---
# Strip prefix middleware for versioned APIs
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: strip-api-prefix
  namespace: production
spec:
  stripPrefix:
    prefixes:
      - /api/v1
      - /api/v2

---
# The actual IngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: api-ingressroute
  namespace: production
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`api.example.com`) && PathPrefix(`/api/v1`)
      kind: Rule
      middlewares:
        - name: rate-limit
        - name: secure-headers
        - name: strip-api-prefix
      services:
        - name: api-v1-service
          port: 8080
          weight: 90
        - name: api-v1-canary-service
          port: 8080
          weight: 10
    - match: Host(`api.example.com`) && PathPrefix(`/api/v2`)
      kind: Rule
      middlewares:
        - name: rate-limit
        - name: secure-headers
      services:
        - name: api-v2-service
          port: 8080
  tls:
    certResolver: letsencrypt

3.4 Built-in Let's Encrypt with DNS Challenge

Traefik's native ACME support is a genuine differentiator, especially for internal/wildcard certificates:

# traefik-values-acme.yaml — append to Helm values
additionalArguments:
  - "--certificatesresolvers.letsencrypt.acme.email=ops@example.com"
  - "--certificatesresolvers.letsencrypt.acme.storage=/data/acme.json"
  - "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
  - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=azure"
  - "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=30"

env:
  - name: AZURE_CLIENT_ID
    valueFrom:
      secretKeyRef:
        name: traefik-azure-dns
        key: client-id
  - name: AZURE_CLIENT_SECRET
    valueFrom:
      secretKeyRef:
        name: traefik-azure-dns
        key: client-secret
  - name: AZURE_SUBSCRIPTION_ID
    valueFrom:
      secretKeyRef:
        name: traefik-azure-dns
        key: subscription-id
  - name: AZURE_TENANT_ID
    valueFrom:
      secretKeyRef:
        name: traefik-azure-dns
        key: tenant-id
  - name: AZURE_RESOURCE_GROUP
    value: "dns-resource-group"

persistence:
  enabled: true
  size: 1Gi
  storageClass: "managed-premium"

4. Azure Application Gateway Ingress Controller (AGIC)

4.1 Architecture Overview

AGIC is architecturally different from the other two. Rather than running a proxy inside the cluster, AGIC runs as a Kubernetes controller pod that translates Kubernetes Ingress resources into Azure Application Gateway configuration via the Azure Resource Manager API. The actual traffic proxy is Azure Application Gateway — a fully managed PaaS resource running outside your cluster.

[Internet] → [Azure Application Gateway (PaaS)] → [AKS Node IPs]
                          ↑
              [AGIC Pod (in-cluster)]
              Watches Ingress resources
              → Calls ARM API to update
                Application Gateway config

This means:

  • No in-cluster proxy overhead — but you pay for the Application Gateway SKU
  • WAF is native via Azure WAF Policy (OWASP 3.2 ruleset out of the box)
  • Configuration changes are slower — ARM API calls take 3–8 seconds vs milliseconds for in-cluster controllers
  • Requires WAF_v2 or Standard_v2 SKU for production use

4.2 Installation: AGIC with AKS Add-on (Recommended)

# Variables
RESOURCE_GROUP="rg-aks-prod"
LOCATION="eastus2"
CLUSTER_NAME="aks-prod-01"
APPGW_NAME="appgw-aks-prod"
APPGW_SUBNET="10.1.0.0/24"
VNET_NAME="vnet-prod"

# Create Application Gateway subnet
az network vnet subnet create \
  --resource-group $RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name AppGatewaySubnet \
  --address-prefix $APPGW_SUBNET

# Create Application Gateway (WAF_v2 for production WAF support)
az network application-gateway create \
  --name $APPGW_NAME \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --sku WAF_v2 \
  --capacity 2 \
  --vnet-name $VNET_NAME \
  --subnet AppGatewaySubnet \
  --public-ip-address appgw-pip \
  --priority 100

# Enable AGIC add-on on existing AKS cluster
APPGW_ID=$(az network application-gateway show \
  --name $APPGW_NAME \
  --resource-group $RESOURCE_GROUP \
  --query id -o tsv)

az aks enable-addons \
  --name $CLUSTER_NAME \
  --resource-group $RESOURCE_GROUP \
  --addons ingress-appgw \
  --appgw-id $APPGW_ID

# Verify AGIC pod is running
kubectl get pods -n kube-system -l app=ingress-appgw

4.3 Ingress with WAF Policy

# agic-ingress-waf.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress-agic
  namespace: production
  annotations:
    kubernetes.io/ingress.class: azure/application-gateway
    # WAF policy (created separately in Azure)
    appgw.ingress.kubernetes.io/waf-policy-for-path: /subscriptions/<sub-id>/resourceGroups/rg-aks-prod/providers/Microsoft.Network/applicationGatewayWebApplicationFirewallPolicies/waf-policy-prod
    # SSL certificate from Key Vault
    appgw.ingress.kubernetes.io/appgw-ssl-certificate: keyvault-ssl-cert
    # Connection draining for zero-downtime deployments
    appgw.ingress.kubernetes.io/connection-draining: "true"
    appgw.ingress.kubernetes.io/connection-draining-timeout: "30"
    # Cookie-based session affinity
    appgw.ingress.kubernetes.io/cookie-based-affinity: "true"
    # Custom health probe
    appgw.ingress.kubernetes.io/health-probe-path: /healthz
    appgw.ingress.kubernetes.io/health-probe-interval: "15"
    appgw.ingress.kubernetes.io/health-probe-threshold: "3"
    # Request timeout
    appgw.ingress.kubernetes.io/request-timeout: "60"
spec:
  ingressClassName: azure-application-gateway
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls-secret
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /v1/*
            pathType: ImplementationSpecific
            backend:
              service:
                name: api-v1-service
                port:
                  number: 8080
          - path: /v2/*
            pathType: ImplementationSpecific
            backend:
              service:
                name: api-v2-service
                port:
                  number: 8080

4.4 WAF Policy via Azure CLI

# Create WAF policy with OWASP 3.2 ruleset
az network application-gateway waf-policy create \
  --name waf-policy-prod \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION

# Set policy to Prevention mode (Detection for initial rollout)
az network application-gateway waf-policy policy-setting update \
  --policy-name waf-policy-prod \
  --resource-group $RESOURCE_GROUP \
  --mode Prevention \
  --state Enabled \
  --request-body-check true \
  --max-request-body-size-kb 128

# Associate OWASP 3.2 managed ruleset
az network application-gateway waf-policy managed-rule ruleset add \
  --policy-name waf-policy-prod \
  --resource-group $RESOURCE_GROUP \
  --type OWASP \
  --version 3.2

# Add custom rule: block requests from known bad ASNs
az network application-gateway waf-policy custom-rule create \
  --policy-name waf-policy-prod \
  --resource-group $RESOURCE_GROUP \
  --name BlockBadUserAgents \
  --priority 10 \
  --rule-type MatchRule \
  --action Block

# Key Vault integration for SSL certificates
az keyvault certificate import \
  --vault-name kv-aks-prod \
  --name api-ssl-cert \
  --file api.example.com.pfx

# Reference Key Vault cert in Application Gateway
az network application-gateway ssl-cert create \
  --gateway-name $APPGW_NAME \
  --resource-group $RESOURCE_GROUP \
  --name keyvault-ssl-cert \
  --key-vault-secret-id $(az keyvault certificate show \
    --vault-name kv-aks-prod \
    --name api-ssl-cert \
    --query sid -o tsv)

5. Head-to-Head Performance Comparison

5.1 Benchmarking Methodology

The figures in this section are indicative — drawn from commonly reported community and vendor benchmarks, not a controlled test run for this article — so treat them as rough relative characteristics rather than measurements. A representative comparison setup is AKS 1.29 with Standard_D4s_v3 nodes (4 vCPU, 16 GB RAM), 3 controller replicas each, routing to an identical echo-server backend, load-generated with k6.

# k6 load test script (save as load-test.js)
# Run with: k6 run --vus 1000 --duration 5m load-test.js

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 1000,
  duration: '5m',
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  const res = http.get('https://api.example.com/v1/echo', {
    headers: { 'Accept': 'application/json' },
  });
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 200ms': (r) => r.timings.duration < 200,
  });
  sleep(0.1);
}

5.2 Results Summary

Metric NGINX Traefik AGIC
Throughput (RPS) ~48,000 ~45,000 ~38,000
P95 Latency 8ms 11ms 24ms
P99 Latency 22ms 28ms 67ms
Config Reload Latency 500ms–2s 0ms (live) 3–8s (ARM)
Memory per Pod 180–220 MB 140–170 MB 95 MB (AGIC pod)
CPU per Pod (sustained) 0.4–0.6 cores 0.3–0.5 cores 0.1 cores

⚠️ Note: AGIC CPU/memory figures reflect only the controller pod. Azure Application Gateway resource costs are external to the cluster and not reflected here.


6. Gateway API Support (The Future of Kubernetes Ingress)

The Kubernetes Gateway API graduated to GA in 1.28 and represents the long-term evolution beyond Ingress. Here's where each controller stands:

Feature NGINX Traefik AGIC
Gateway API Version v1 (GA) v1 (GA) v1 (Preview)
HTTPRoute ✅ Stable ✅ Stable ✅ Preview
GRPCRoute ✅ Stable ✅ Stable ❌ Not yet
TCPRoute ✅ Beta ✅ Stable ❌ Not yet
TLSRoute ✅ Beta ✅ Stable ❌ Not yet
ReferenceGrant
# Gateway API HTTPRoute example (works with NGINX and Traefik)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-httproute
  namespace: production
spec:
  parentRefs:
    - name: prod-gateway
      namespace: gateway-system
  hostnames:
    - "api.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
        headers:
          - name: "x-canary"
            value: "true"
      backendRefs:
        - name: api-v1-canary-service
          port: 8080
          weight: 1
    - matches:
        - path:
            type: PathPrefix
            value: /v1
      backendRefs:
        - name: api-v1-service
          port: 8080
          weight: 99

7. Pitfalls to Avoid

NGINX Pitfalls

  1. Reload storms under high churn: Each Ingress change triggers an NGINX reload. In clusters with hundreds of frequently-updated services, reloads can queue up and cause 500–2,000ms latency spikes. Fix: Use nginx.ingress.kubernetes.io/load-balance: ewma and batch your Ingress changes. Consider the VirtualServer CRD which supports dynamic upstream updates without reload in NGINX Plus.

  2. Annotation sprawl: Teams accumulate dozens of per-Ingress annotations that are impossible to audit centrally. Fix: Move global settings to the ConfigMap and use naming conventions. Evaluate migrating to VirtualServer/HTTPRoute CRDs.

  3. Not setting resource limits on the controller: An unthrottled NGINX controller will consume all node CPU under DDoS. Fix: Always set limits.cpu and limits.memory; pair with HPA targeting CPU at 70%.

  4. Missing --ingress-class flag with multiple controllers: Running two NGINX controllers without distinct ingressClassName values causes both to pick up all Ingress resources. Fix: Always set controller.ingressClass explicitly in Helm values.

Traefik Pitfalls

  1. ACME storage on ephemeral volumes: Storing acme.json on a non-persistent volume means Let's Encrypt state is lost on pod restart, triggering certificate re-issuance and potentially hitting rate limits (5 certs per domain per week). Fix: Always use a PersistentVolumeClaim for Traefik's data directory.
Thorsteinn Halldorsson Senior Cloud Engineer

Senior Cloud Engineer with 25+ years of hands-on experience across the datacenter-to-cloud stack: fiber SAN and disk storage, IBM/Lenovo blade and Dell/HP/Lenovo servers, Hyper-V and VMware clusters, and SQL and Remote Desktop Services (RDS) clusters. Deep in the Microsoft platform — Active Directory, PKI/certificate services, SQL, Power BI, Dynamics 365 Business Central (NAV) and AX (Axapta), Microsoft 365, Entra, and Intune — with a focus on Azure operations, FinOps, and applying AI tools like GitHub Copilot and Claude in real workflows. Writes practical, no-nonsense guides for IT professionals who need to ship real solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *