FR
live

Your app is slow because your load balancer is misconfigured — not because it is slow

HAProxy 3.4 brings dynamic backends with zero-downtime reloads, Traefik 3.7 auto-discovers your containers, and Envoy 1.39 powers Google’s service mesh. Here’s which one to pick and how to configure it for speed instead of drag.

Load balancers HAProxy Traefik Envoy — ETTAYEB illustration

In September 2025, a French SaaS company lost 40% of its traffic during a major marketing campaign. The root cause was not a bug in the application code, not a cloud outage, not a DDoS attack. It was a single balance roundrobin directive in an HAProxy configuration that had not been touched in three years — four out of five backends were burning CPU while the fifth, the most powerful one, idled at 12% utilization.

June 2026 sees HAProxy shipping version 3.4.0 with dynamic backends that can be provisioned without interrupting live traffic. Traefik sits at v3.7.9, discovering your Docker containers with zero manual configuration. Envoy releases v1.39.0, extends its xDS protocol, and hardens HTTP/2 filtering. Three excellent load balancers, hundreds of thousands of production deployments — and an overwhelming majority of them misconfigured in ways that silently degrade performance.

A misconfigured load balancer does not just fail to help: it injects latency, wastes resources, and obscures the real bottleneck. Fixing the configuration usually delivers more throughput than upgrading to a larger server tier.

What a load balancer actually does — and what it does not

A load balancer distributes incoming traffic across multiple backend servers. That textbook definition omits three critical functions that determine whether your infrastructure holds up or collapses under load.

Distribution algorithms. Round-robin is the most common and most misunderstood algorithm. It sends requests to backends sequentially without considering actual load. If backend A has twice the CPU of backend B, round-robin sends them equal traffic — and B collapses while A yawns. Algorithms like least connections (HAProxy leastconn, Envoy LEAST_REQUEST) or least loaded (Traefik, driven by circuit breaker metrics) correct this imbalance, but only if you explicitly enable them.

Health checks. Without them, the load balancer keeps forwarding traffic to a dead backend. A TCP health check (port open) is insufficient: an NGINX process can accept TCP connections while returning HTTP 500 errors. An HTTP health check (GET /health with status code and response body validation) is the bare minimum. HAProxy and Envoy also support gRPC, MySQL, and Redis health checks — protocols where a TCP check says « all good » while the connection pool is saturated.

SSL termination. Decrypting TLS at the load balancer saves backends from burning CPU on cryptography — a measurable 15 to 30% throughput gain. But it is a trade-off: traffic between the load balancer and backends flows in the clear unless you re-encrypt it. TLS termination without re-encryption is the default in 80% of Docker Compose tutorials — and the first thing a penetration tester flags.

HAProxy: the diesel engine that starts instantly

HAProxy is written in C by Willy Tarreau and has been maintained since 2001. It does one thing — TCP/HTTP proxying — and does it with an efficiency that newer implementations rarely match. A single HAProxy process on modern hardware can handle 2 million concurrent connections using under 100 MB of RAM. It sits behind Cloudflare, GitHub, Reddit, Twitter, and a large chunk of the European web.

Version 3.4.0, released on June 3, 2026, delivers a feature the community has requested for fifteen years: dynamic backends. Before 3.4, any change to the backend server list required a reload — an operation that restarts the HAProxy process and breaks established connections. HAProxy’s reload is famously graceful (the parent process hands the listening socket to the new process without packet loss), but it remains a risky operation under high load. With 3.4, you create, populate, publish, and delete backends via the CLI socket without ever reloading. Version 3.4 is an LTS release supported until Q2 2031.

HAProxy’s strengths are mechanical: a single configuration file (haproxy.cfg), an expressive ACL rule language, exhaustive documentation, and a stability record that borders on legend — data corruption bugs? Zero in twenty-five years. Its limitations are equally mechanical: no native auto-discovery (you need scripts or consul-template to inject backends dynamically), no built-in TLS automation (no Let’s Encrypt integration — certificate management is on you), and a learning curve that rewards people who read the docs rather than those who copy-paste a tutorial.

Traefik: the config that writes itself

Traefik was built in Go for a container-first world. Its radical promise: you do not configure Traefik — you annotate your services, and Traefik discovers the rest. A Docker container labeled traefik.http.routers.myapp.rule=Host("app.example.com") appears automatically in the routing table — no separate YAML file, no reload, no forgotten entry.

Version v3.7.9 (July 2026) supports Docker, Kubernetes (Ingress, Gateway API, CRD), Consul, Etcd, Redis, ZooKeeper, and Nomad providers. TLS certificates are handled automatically through Let’s Encrypt with hands-off renewal. The middleware pipeline is extensible: rate limiting, circuit breaking, forward auth, compression, header customization — all activated via annotations.

This model has a cost. Traefik is a single binary using 50 to 200 MB of RAM depending on routing complexity, and its discovery engine actively polls each provider. On a 50-node Docker Swarm cluster with 300 containers, the Docker API polling generates non-trivial API load. Advanced users also hit the routing syntax wall: what is trivial with a Docker label becomes esoteric in static YAML, and debugging a mismatched routing rule is less tooled than haproxy -c -f which tells you exactly where the error is.

The community verdict is clear: Traefik is unbeatable for HTTP/HTTPS routing in containerized environments where the developer controls annotations. As soon as you step outside HTTP (raw TCP, databases, message brokers), HAProxy takes the lead back.

Envoy: the load balancer that does way more than load balancing

Envoy is a CNCF graduated project created by Lyft in 2016. Unlike HAProxy and Traefik, Envoy was never meant to be hand-configured by a human. Its configuration is expressed in YAML or JSON (several hundred lines for a simple reverse proxy), and its dynamic configuration model — the xDS protocol — assumes an external control plane pushes updates continuously. It is the data plane behind Istio, the service mesh powering Google Cloud, and a growing share of production Kubernetes architectures.

Version v1.39.0 (July 2026) adds streaming JSON parsing for MCP, A2A, and OpenAI protocols, extensible health checkers, per-worker CPU affinity, an eBPF SO_REUSEPORT connection balancer, and dynamically loadable modules with no recompilation. Envoy natively speaks HTTP/1.1, HTTP/2, HTTP/3 (QUIC), gRPC, MongoDB, MySQL, Redis, Kafka, Thrift, and essentially any protocol that runs over TCP.

This universality has weight. A minimal Envoy deployment uses ~50 MB of RAM per proxy, but a proxy configured with full HTTP filtering, OpenTelemetry observability, and active health checking can reach 150–200 MB. The static configuration file is inhumane — 500 to 1,000 lines of YAML for a reverse proxy with TLS and rate limiting — which explains why almost nobody runs Envoy without a control plane like Istio, Contour, or Envoy Gateway.

Envoy’s strength is not basic load balancing. It is observability: native distributed tracing, percentile metrics (P50, P95, P99), structured access logs, and the ability to inject faults or mirror traffic without touching application code. If your load balancer needs to double as a chaos engineering lab, Envoy is the only one that checks the box.

Four misconfigurations that kill your performance

Before you choose between HAProxy, Traefik, and Envoy, fix the configuration errors that cancel out their benefits. These four traps are universal — they apply to all three tools.

1. Default round-robin. In HAProxy, that is balance roundrobin. In Traefik, it is the default behavior for Docker Swarm services. In Envoy, it is ROUND_ROBIN. None of these modes account for backend capacity. Result: the slowest backend dictates global latency. The fix is straightforward: balance leastconn (HAProxy), LEAST_REQUEST (Envoy), or a weighted round-robin with weights proportional to each backend’s real capacity. But this fix requires that you have actually measured capacity per backend — something 90% of teams skip.

2. TCP health checks without application-level verification. A check port 8080 does not catch a backend returning HTTP 503 on every request. The cost is silent: the load balancer forwards traffic to a sick backend, which rejects it, and the client sees an error while healthy backends sit idle. The fix is an active HTTP health check (option httpchk GET /health in HAProxy, healthcheck in Traefik, a health_check filter in Envoy) that validates the status code and ideally a field in the response body.

3. TLS termination without re-encryption. Traffic between the load balancer and backends flows in the clear on the internal network. It is fast, it is convenient, and it is the default in most tutorials. It also violates every security framework (NIST, PCI-DSS, ANSSI in France) that mandates end-to-end encryption. The CPU cost of re-encryption is real — 5 to 15% depending on load — but the alternative is worse: an attacker who compromises a single container on the Docker bridge network can sniff all inter-service traffic.

4. Poorly sized timeouts. The default timeout between load balancer and backend is often left at its factory value: 30 seconds in HAProxy (timeout server 30s), 60 seconds in Traefik, 15 seconds in Envoy. If your backend handles long requests (file uploads, report generation, calls to an external API), the timeout kills the connection before processing completes — and the client gets a 504 Gateway Timeout. Conversely, a timeout that is too long on fast requests holds connections that could serve other clients. The rule: measure your backend’s P99 latency, double it, and use that as the timeout. Re-measure monthly.

Verdict: which load balancer for which profile

The perfect load balancer does not exist. The right load balancer is the one whose configuration you actually understand and whose deployment model matches your architecture — not the one recommended by the blog you read last week.

You manage bare-metal servers or VMs with heterogeneous backends. Pick HAProxy 3.4 LTS. The single haproxy.cfg file is version-controllable, reviewable, and understandable by an SRE who has not touched the config in six months. Dynamic backends in 3.4 eliminate the last major operational pain point. Support until 2031 gives you five years of peace. One caveat: budget for an external TLS certificate solution (certbot + cron, or a CI/CD pipeline that pushes certificates).

You deploy on Docker or Kubernetes with mostly HTTP/HTTPS traffic. Pick Traefik 3.7. Auto-discovery via Docker labels or Kubernetes annotations reduces configuration to the bare minimum, Let’s Encrypt handles your certificates, and the middleware pipeline covers 90% of request transformation needs. The API polling cost becomes noticeable above 200 containers — if you reach that scale, migrate the provider to a static configuration file or switch to a Kubernetes control plane (Ingress Controller or Gateway API).

You operate a Kubernetes service mesh with advanced observability requirements. Envoy 1.39 is the reference data plane, but do not hand-configure it. Use Istio (most complete, most demanding), Contour (simpler, Gateway API-based), or Envoy Gateway (the emerging CNCF standard, still young). The RAM and complexity overhead is real — only deploy Envoy if you already have a team comfortable with Kubernetes CRDs.

You have fewer than 5 backends and moderate traffic. You may not need a dedicated load balancer at all. NGINX as a reverse proxy with an upstream block and least_conn handles simple deployments perfectly. The difference between NGINX and HAProxy only matters above 50,000 concurrent connections or 10 backends — below that threshold, the simplicity of an NGINX config that your entire team already knows outweighs the 5% performance gain HAProxy delivers on HTTP traffic.

The load balancer is the first link in a request’s processing chain. It is also the most neglected one, because it « just works » with factory settings — until the day it does not. The three minutes you spend replacing roundrobin with leastconn pay back more than the three days you will spend debugging an application that was never the problem.

References

The cyber brief, every Tuesday

The flaws that matter and the patches to apply, in a ten-minute read.

No spam. One-click unsubscribe.
read next

On the same topic

← Back to the feed

Type at least two characters.

navigate open esc dismiss