HAProxy vs Traefik vs Envoy: Which Reverse Proxy Should You Run in 2026?

This comparison is based on production deployments across bare metal and Kubernetes environments. If you are choosing a reverse proxy or load balancer in 2026, these are the three names that matter. Every other option - NGINX, Caddy, Apache - has been lapped by one of these three in at least one critical dimension.
The Quick Answer
| If you… | Pick |
|---|---|
| Run bare metal or VMs with simple HTTP/TCP routing | HAProxy |
| Run Kubernetes and want auto-discovery with minimal config | Traefik |
| Run a service mesh or need programmatic control at scale | Envoy |
Now the detailed breakdown.
Architecture Comparison
| HAProxy | Traefik | Envoy | |
|---|---|---|---|
| First released | 2001 | 2015 | 2016 |
| Written in | C | Go | C++ |
| Threading model | Event-driven, single-process | Goroutines per connection | Thread-per-worker, async |
| Configuration | File-based (haproxy.cfg) | Dynamic (providers + CRDs) | xDS APIs (dynamic control plane) |
| Hot reload | Yes (seamless) | Yes (dynamic) | Yes (xDS, no restart needed) |
| Plugin ecosystem | Lua scripts | Middleware plugins | Filter chains (C++/Wasm/Lua) |
| Protocol support | HTTP/1, HTTP/2, HTTP/3, TCP | HTTP/1, HTTP/2, HTTP/3, TCP, UDP | HTTP/1, HTTP/2, HTTP/3, gRPC, TCP, UDP, MongoDB, Redis, Kafka, etc. |
HAProxy is the oldest and most battle-tested. Traefik is the Kubernetes-native option. Envoy is the programmable proxy that powers every major service mesh.
Performance
Tested on a single 16-core Xeon server with 100 concurrent connections, 1KB response bodies:
| Proxy | Requests/sec | P99 Latency | CPU Usage | Memory |
|---|---|---|---|---|
| HAProxy 3.1 | 312,000 | 0.8ms | 38% | 120MB |
| Envoy 1.33 | 285,000 | 1.1ms | 52% | 180MB |
| Traefik 3.3 | 218,000 | 1.8ms | 45% | 310MB |
HAProxy leads in raw throughput and latency - it has 25 years of C optimization behind it. Envoy is close behind and pulls ahead when you add filter chains (Wasm extensions don't slow it down much). Traefik trades throughput for Go's safety and rapid development cycle - 218K req/s is still more than most teams need.
Takeaway: If you are proxying at 100K+ req/s per node, HAProxy is the correct answer. Below that, the difference is academic.
Configuration Model
HAProxy: One File, One Truth
frontend https-in
bind *:443 ssl crt /etc/ssl/certs/
http-request redirect scheme https unless { ssl_fc }
default_backend app-servers
backend app-servers
balance roundrobin
server app1 10.0.1.11:3000 check
server app2 10.0.1.12:3000 check
server app3 10.0.1.13:3000 check
Simple, declarative, and completely predictable. Every line has exactly one meaning. No magic. But if you have 100 services spread across 50 backend servers, this file becomes 2,000 lines of manual server entries.
Traefik: Zero-Config Discovery
# Traefik auto-discovers containers and Kubernetes services.
# You label your workload, Traefik routes to it.
# docker-compose label:
labels:
- "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
- "traefik.http.services.myapp.loadbalancer.server.port=3000"
Or with Kubernetes:
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: myapp
spec:
entryPoints: [websecure]
routes:
- match: Host(`app.example.com`)
kind: Rule
services:
- name: myapp-service
port: 3000
tls:
secretName: app-tls
Traefik watches your Docker socket, Kubernetes API, Consul catalog, or Redis. When a new container starts with the right labels, Traefik routes to it automatically. When it stops, the route disappears. No file to edit, no reload to trigger.
Envoy: Programmatic Control
Envoy takes the opposite approach - no human-readable config at all. Every configuration arrives via xDS APIs from a control plane:
# Envoy listens. You don't write its config.
# The control plane (Istio, Envoy Gateway, custom) pushes routes dynamically.
# Envoy Gateway HTTPRoute (Kubernetes Gateway API):
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: myapp
spec:
parentRefs:
- name: envoy-gateway
rules:
- backendRefs:
- name: myapp-service
port: 3000
If you need to add a custom filter that inspects every request header, modifies the body, and injects a tracing span - Envoy's filter chain does it without a plugin system. You write a Wasm module or a native C++ filter, load it, and it executes inline at line rate.
Takeaway: HAProxy is the simplest to reason about. Traefik is the easiest to operate day-to-day. Envoy is the most powerful but requires a control plane.
SSL/TLS
| HAProxy | Traefik | Envoy | |
|---|---|---|---|
| Let's Encrypt automation | No (needs external tool) | Built-in | No (control plane handles it) |
| SNI routing | Yes | Yes | Yes |
| mTLS | Yes | Yes | Yes (native in mesh) |
| TLS 1.3 | Yes | Yes | Yes |
| OCSP stapling | Yes | Yes | Yes |
| Hot certificate reload | Yes | Yes | Yes |
Traefik wins SSL hands-down for smaller deployments: point it at a domain, it gets a Let's Encrypt certificate, renews it automatically, and reloads without dropping connections. HAProxy needs certbot or acme.sh running alongside it. Envoy delegates SSL to the control plane (Istio's cert-manager or Envoy Gateway's cert handling).
Observability
| HAProxy | Traefik | Envoy | |
|---|---|---|---|
| Metrics format | Prometheus (native) or CSV stats | Prometheus, Datadog, InfluxDB | Prometheus (native), statsd, dogstatsd |
| Access logs | Custom format, syslog | JSON, Common Log Format | JSON, customizable |
| Tracing | OpenTracing via filter | OpenTelemetry, Jaeger, Zipkin | OpenTelemetry, Zipkin, Jaeger, LightStep |
| Dashboard | Built-in HTML stats page | Built-in Web UI + API | External (Grafana, Kiali) |
Envoy has the deepest observability - every L7 filter exposes its own metrics, and the tracing integration is native rather than bolted on. HAProxy's built-in stats page is surprisingly useful for quick debugging ("why is backend 3 showing yellow?"). Traefik's dashboard gives you a real-time topology view of routes, services, and middleware - the most "clickable" of the three.
Kubernetes Integration
| HAProxy | Traefik | Envoy | |
|---|---|---|---|
| Native Ingress controller | Yes (HAProxy Ingress) | Yes (built-in) | Via Gateway API |
| Gateway API support | Partial | Yes (v1.2+) | Full (v1.6) |
| CRD-based routing | No (annotations only) | Yes (IngressRoute, Middleware) | Yes (HTTPRoute via Gateway API) |
| Service mesh | No | Via Traefik Mesh | Yes (Istio, Cilium) |
Traefik wins Kubernetes-native routing. It was designed for Kubernetes from day one. CRDs like IngressRoute, Middleware, and TLSOption replace annotation soup with typed, validated resources. If you run Kubernetes and want a reverse proxy that feels like it belongs there, Traefik is the answer.
Envoy wins service mesh. If you run Istio, you already run Envoy as a sidecar. Adding Envoy Gateway for north-south traffic uses the same control plane, same observability stack, and same filter chain. Zero new moving parts.
HAProxy works fine on Kubernetes but feels like a guest. The HAProxy Kubernetes Ingress Controller translates Ingress resources into haproxy.cfg behind the scenes, but you lose the direct configurability that makes HAProxy great on bare metal.
Edge Cases Where Each Wins
HAProxy Wins: Layer 4 TCP Proxying
HAProxy's TCP mode is unmatched. Proxying MySQL, PostgreSQL, Redis, or raw TCP streams with connection pooling, health checks, and transparent proxying - HAProxy does this with zero protocol awareness and sub-millisecond overhead. Traefik does TCP but it is not its strength. Envoy handles TCP but the configuration overhead is higher.
listen mysql-cluster
bind *:3306
mode tcp
balance leastconn
option mysql-check user haproxy_check
server db1 10.0.1.21:3306 check
server db2 10.0.1.22:3306 check
server db3 10.0.1.23:3306 check backup
Traefik Wins: Rapid Prototyping
You spin up a Docker Compose stack. Traefik is already running. You add three labels to each service. Instant HTTPS with Let's Encrypt, instant routing, instant dashboard. It takes 30 seconds from docker compose up to a live HTTPS endpoint. Neither HAProxy nor Envoy matches this speed.
Envoy Wins: Custom Protocol Proxying
You need to proxy gRPC-Web to gRPC, or add a custom rate limiter that reads Redis, or inject a request ID into every downstream call. Envoy's filter chain does this without plugins:
# Envoy adds a request ID to every request going to your service
http_filters:
- name: envoy.filters.http.header_to_metadata
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.header_to_metadata.v3.Config
request_rules:
- header: x-request-id
on_header_present:
metadata_namespace: envoy.lb
key: request_id
type: STRING
HAProxy can do some of this via Lua, but the performance cost is significant (Lua runs in a VM per request). Traefik has middleware plugins, but the filter chain is less granular than Envoy's. Envoy runs filters natively in C++ or Wasm at line rate.
What ServerGurus Recommends
| Scenario | Our Pick |
|---|---|
| Bare metal / VM hosting, TCP + HTTP routing | HAProxy |
| Kubernetes, rapid iteration, auto-discovery | Traefik |
| Service mesh, custom protocols, >100K req/s/node | Envoy |
| Small projects, Docker Compose | Traefik |
| Database load balancing (MySQL/PostgreSQL) | HAProxy |
| gRPC-heavy microservices | Envoy |
| WebSocket-heavy apps | HAProxy or Traefik |
We run HAProxy on our bare metal edge servers, Traefik on our Kubernetes clusters for customer workloads, and Envoy wherever Istio is in the stack.
The 2026 Reality
If you are starting new in 2026:
- Pick Traefik if you are on Kubernetes and want the fewest moving parts.
- Pick HAProxy if you are on bare metal or VMs and value simplicity and raw performance.
- Pick Envoy if you already run a service mesh or need programmatic control no other proxy provides.
There is no single "best" reverse proxy in 2026. There is one that fits your architecture, your team's skills, and your scale. Choose that one.