Kubernetes Ingress Controller Comparison for Production
Head-to-head comparison of NGINX, Traefik, HAProxy, Istio, and Envoy Gateway ingress controllers with benchmarks on throughput, latency, and resource usage

Introduction
Choosing a Kubernetes ingress controller is one of the most impactful infrastructure decisions for any cluster. The ingress controller handles every external request, making it the performance bottleneck, the security boundary, and the observability chokepoint all at once. With over a dozen production-grade options available, the decision requires data-driven comparison.
This article presents benchmark results from testing five popular ingress controllers under identical load conditions, comparing throughput, latency, resource consumption, and operational complexity.
Controller Overview
| Controller | Proxy Engine | License | Gateway API | Config Reload | Market Share |
|---|---|---|---|---|---|
| NGINX Ingress | NGINX | Apache 2.0 | Partial | Config reload (brief drop) | ~45% |
| Traefik | Go (native) | MIT | Full | Dynamic (zero downtime) | ~20% |
| HAProxy Ingress | HAProxy | Apache 2.0 | Partial | Reload or dynamic | ~10% |
| Istio (Envoy) | Envoy | Apache 2.0 | Full | xDS (zero downtime) | ~15% |
| Envoy Gateway | Envoy | Apache 2.0 | Full (native) | xDS (zero downtime) | ~5% |
Benchmark Setup
Test Environment
| Component | Specification |
|---|---|
| Cluster | EKS 1.29, 6x m6i.2xlarge worker nodes |
| Load generator | 3x c6i.4xlarge running k6 |
| Backend | 10 replicas of echo server (Go, ~0.1ms response) |
| Test duration | 5 minutes per test after 30s warmup |
| TLS | TLS 1.3, RSA 2048 certificate |
| Protocol | HTTPS with keep-alive |
k6 Load Script
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 100 }, // warmup
{ duration: '5m', target: 1000 }, // sustained load
],
thresholds: {
http_req_duration: ['p(99)<100'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('https://test-ingress.example.com/echo');
check(res, {
'status is 200': (r) => r.status === 200,
});
}
Throughput Benchmarks (RPS)
Requests Per Second at Various Concurrency Levels
| Concurrency | NGINX | Traefik | HAProxy | Istio (Envoy) | Envoy Gateway |
|---|---|---|---|---|---|
| 100 | 45,000 | 38,000 | 52,000 | 42,000 | 44,000 |
| 500 | 82,000 | 65,000 | 95,000 | 78,000 | 80,000 |
| 1,000 | 105,000 | 78,000 | 120,000 | 98,000 | 102,000 |
| 2,000 | 115,000 | 82,000 | 130,000 | 108,000 | 112,000 |
| 5,000 | 118,000 | 85,000 | 135,000 | 112,000 | 115,000 |
Winner: HAProxy consistently delivers 15-25% higher throughput than alternatives due to its highly optimized event-driven architecture.
Latency Benchmarks
Latency at 1,000 Concurrent Connections (HTTPS)
| Percentile | NGINX | Traefik | HAProxy | Istio | Envoy Gateway |
|---|---|---|---|---|---|
| p50 | 2.1ms | 3.5ms | 1.8ms | 2.4ms | 2.2ms |
| p90 | 5.2ms | 8.1ms | 4.5ms | 5.8ms | 5.4ms |
| p95 | 8.4ms | 12.3ms | 7.1ms | 9.2ms | 8.8ms |
| p99 | 15.2ms | 22.5ms | 12.8ms | 16.5ms | 15.8ms |
| p99.9 | 35ms | 55ms | 28ms | 38ms | 36ms |
Analysis: HAProxy leads on latency across all percentiles. Traefik has consistently higher latency due to Go's garbage collection pauses affecting tail latency. NGINX and Envoy-based solutions cluster together.
Resource Consumption
Idle Resource Usage (No Traffic)
| Controller | CPU (idle) | Memory (idle) | Pods | Notes |
|---|---|---|---|---|
| NGINX | 10m | 90 MB | 1 | Single pod default |
| Traefik | 15m | 120 MB | 1 | Includes dashboard |
| HAProxy | 8m | 80 MB | 1 | Minimal idle footprint |
| Istio | 150m | 600 MB | 4 | istiod + gateways |
| Envoy Gateway | 50m | 200 MB | 3 | Controller + Envoy |
Under Load (50,000 RPS)
| Controller | CPU (loaded) | Memory (loaded) | Scaling Model |
|---|---|---|---|
| NGINX | 2.5 cores | 350 MB | Horizontal (replicas) |
| Traefik | 3.2 cores | 450 MB | Horizontal (replicas) |
| HAProxy | 2.0 cores | 280 MB | Horizontal + vertical |
| Istio | 4.5 cores | 1.2 GB | Complex (data + control plane) |
| Envoy Gateway | 3.0 cores | 500 MB | Horizontal (Envoy fleet) |
Feature Comparison
| Feature | NGINX | Traefik | HAProxy | Istio | Envoy Gateway |
|---|---|---|---|---|---|
| Rate limiting | Annotation | Middleware | ACL | EnvoyFilter | Policy |
| mTLS | Manual | Yes | Manual | Automatic | Policy |
| Canary/traffic split | Annotation | Weighted | ACL | VirtualService | HTTPRoute |
| Circuit breaking | No | Yes | Backend check | Yes | Yes |
| WASM extensions | No | Plugin (Go) | No | Yes | Yes |
| OpenTelemetry | Partial | Yes | Partial | Yes | Yes |
| HTTP/3 (QUIC) | Experimental | Yes | No | Yes | Yes |
| Web Application Firewall | ModSecurity | Plugin | No | External | External |
| Auto TLS (Let's Encrypt) | cert-manager | Built-in | cert-manager | cert-manager | cert-manager |
| Config complexity | Low | Low | Medium | High | Medium |
Configuration Comparison
NGINX Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
ingressClassName: nginx
tls:
- hosts: ["api.example.com"]
secretName: api-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
Gateway API (Envoy Gateway / Istio / Traefik)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
spec:
parentRefs:
- name: production-gateway
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v2
backendRefs:
- name: api-v2
port: 80
weight: 90
- name: api-v2-canary
port: 80
weight: 10
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Request-Start
value: "%START_TIME%"
- matches:
- path:
type: PathPrefix
value: /api/v1
backendRefs:
- name: api-v1
port: 80
Decision Framework
| If You Need... | Choose | Reason |
|---|---|---|
| Maximum throughput | HAProxy | Best raw performance |
| Simplest setup | NGINX | Most documentation, lowest learning curve |
| Gateway API (future-proof) | Envoy Gateway | Native Gateway API implementation |
| Full service mesh | Istio | mTLS, observability, traffic management |
| Zero-downtime config | Traefik | Dynamic config without reloads |
| Cost efficiency (small cluster) | NGINX or HAProxy | Lowest resource usage |
| Enterprise features (WAF, OIDC) | NGINX Plus or Istio | Built-in security features |
By Cluster Size
| Cluster Size | Recommended | Reasoning |
|---|---|---|
| < 10 services | NGINX Ingress | Simple, well-documented, low overhead |
| 10-50 services | Traefik or Envoy Gateway | Gateway API, dynamic config, good observability |
| 50-200 services | Envoy Gateway or Istio | Advanced routing, traffic management |
| 200+ services (mesh) | Istio | Full mesh features, mTLS everywhere |
Migration Path
For teams currently on NGINX wanting to migrate to Gateway API:
# Step 1: Install Gateway API CRDs
# Step 2: Deploy Envoy Gateway alongside NGINX
# Step 3: Gradually move routes from Ingress to HTTPRoute
# Step 4: Decommission NGINX once migration complete
# Both can coexist using different ingressClassNames/gateway names
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: legacy-routes
spec:
ingressClassName: nginx # Old routes stay on NGINX
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: new-routes
spec:
parentRefs:
- name: envoy-gateway # New routes on Envoy Gateway
Key Takeaways
- HAProxy delivers the highest throughput and lowest latency at 130K+ RPS with sub-2ms p50 latency, making it ideal for performance-critical workloads.
- NGINX remains the safe default with the largest community, most documentation, and lowest learning curve for teams new to Kubernetes ingress.
- Gateway API is the future and should be adopted for new clusters; Envoy Gateway and Traefik have the most complete implementations.
- Istio is justified only when you need full service mesh including automatic mTLS, distributed tracing, and complex traffic policies; its resource overhead (600MB+ idle) is significant.
- Traefik trades performance for operational simplicity with zero-downtime configuration changes and built-in Let's Encrypt, suitable for teams prioritizing developer experience.
- Resource cost varies 10x between options from 80MB (HAProxy) to 1.2GB (Istio under load), impacting node sizing and cluster costs.
- Plan for Gateway API migration even if using Ingress today, as all major controllers are converging on this standard.
Recommended reading

Per-Team Cost Allocation in Shared Kubernetes Clusters: From Chaos to Clarity
Implementing accurate per-namespace cost allocation in multi-tenant Kubernetes clusters, covering request vs. usage attribution, shared resource amortization, and building showback dashboards that drive accountability.

Measuring and Eliminating Toil: From 40% to 12% of Engineering Time
A systematic approach to identifying, measuring, and automating toil—the repetitive operational work that scales linearly with service growth and prevents engineers from doing creative work.

Serverless Postgres in Production: Branching, Scale-to-Zero, and the End of Database Provisioning
Running Neon serverless Postgres in production for 8 months — covering database branching workflows, scale-to-zero economics, connection pooling, and migration from RDS.

Comments
No comments yet. Be the first to share your thoughts.