The three reverse proxies engineers reach for most often — HAProxy, Nginx, Traefik — solve overlapping problems with different design centers. Pick the wrong one and you fight the tool for years. This article is the decision matrix we apply when standing up a client environment: what each is genuinely best at, where they overlap, and the criteria that decide which one fits a workload.
How to verify
If you inherited a proxy you didn’t choose, identify it and its role:
ss -ltnp | grep -E ':(80|443)\b'
ps auxf | grep -E 'haproxy|nginx|traefik'
which haproxy nginx traefik 2>/dev/null
cat /etc/haproxy/haproxy.cfg /etc/nginx/nginx.conf /etc/traefik/traefik.yml 2>/dev/null | head -50
The presence of traefik.yml and a process named traefik is unambiguous. Both HAProxy and Nginx live in their own conf files; the directory structure is the giveaway (/etc/haproxy/ vs /etc/nginx/).
Decision
The decision turns on five questions: traffic profile, configuration style, ecosystem integration, observability needs, and operator skillset.
-
HAProxy — best when:
- Pure performance matters at the L4/L7 edge. HAProxy is the fastest of the three for raw TCP and HTTP/1.1 forwarding.
- You need real load balancing — weighted, dynamic, with stick-tables and peer replication.
- mTLS, advanced ACLs, content switching, rate limiting via stick-tables are first-class.
- Hot reload with zero dropped connections is required (master-worker model).
- The runtime API gives you live operational control (disable servers, swap certs, change weights) without reload.
- Stick tables provide stateful in-process counters for sessions, rate limits, and gpc.
- You are willing to maintain a config file under change control.
Less ideal when: you want auto-discovery from Docker labels or Kubernetes annotations (HAProxy has the Kubernetes Ingress Controller and the Data Plane API, but they are heavier than Traefik’s native discovery).
-
Nginx — best when:
- You are serving static content, handling many small files, doing fastcgi to PHP-FPM, or proxying to one or two upstreams.
- The reverse-proxy role is secondary to web-serving.
- You need rich URL rewriting, sophisticated cache control with
proxy_cache, or many small per-location overrides. - The team already knows Nginx config syntax (Nginx is ubiquitous; most ops engineers have written
server { location { } }blocks). - You want a single binary that handles TLS, static serving, FastCGI, and reverse proxy in one config.
Less ideal when: you need true load balancing with health checks, dynamic backends, rate limiting beyond the basic
limit_reqmodule, or in-process state across requests. Nginx Plus has these, but it is commercial. -
Traefik — best when:
- Containers are first-class. Traefik discovers services from Docker labels, Kubernetes CRDs, Consul, Nomad, AWS ECS — zero config file rewrites for routing changes.
- Let’s Encrypt auto-provisioning is a non-negotiable.
- The team wants a single binary with a dashboard, metrics endpoint, and dynamic config out of the box.
- You operate a microservice topology where service identity changes frequently (deploys, autoscaling).
Less ideal when: you need raw L4 performance, deterministic config under Git, complex ACLs, or stick-table state. Traefik improved a lot but still trails HAProxy at the high-throughput edge.
Side-by-side
| Concern | HAProxy | Nginx | Traefik |
|---|---|---|---|
| L4 (TCP) load balancing | First-class | stream module | Yes, but limited |
| L7 (HTTP) routing | First-class | First-class | First-class |
| Hot reload, no dropped connections | Yes (master-worker) | Yes (workers) | Yes |
| Dynamic backend discovery | Static + Data Plane API | Static + commercial Plus | Native (Docker, K8s, Consul) |
| Let’s Encrypt built-in | No (use certbot) | No (use certbot) | Yes |
| Runtime API for ops | Yes (admin socket) | Commercial Plus only | Dashboard + API |
| Rate limiting | Stick-tables (rich) | limit_req (basic) | Middleware |
| Stick sessions | Yes (cookie + tables) | Yes (sticky cookie) | Yes (sticky cookie) |
| mTLS client cert | Yes | Yes | Yes |
| Static file serving | No | First-class | No |
| Performance at edge | Highest | High | Good |
| Config complexity | Medium-high | Medium | Low (auto-discovery) |
| Observability | Stats page + Prometheus | Status module + commercial | Dashboard + Prometheus |
| Learning curve | Higher | Lower | Lowest |
How we choose for clients
The decision tree we use:
- Edge of a multi-region or clustered platform, public APIs, strict performance budget? HAProxy.
- Public website that mixes static assets, PHP-FPM, and a few proxied paths? Nginx (with HAProxy in front if multi-region).
- Container platform (Docker Compose, Kubernetes) where services come and go? Traefik (or HAProxy with the K8s Ingress Controller if performance dominates).
- Mixed: an Nginx for the website + HAProxy in front for HA and routing. Common in our deployments; the two coexist gracefully.
We rarely run only Traefik for high-traffic public APIs. We rarely run only Nginx as a load balancer for clustered apps. HAProxy is the default at the public edge for clients with non-trivial traffic.
Operational notes
- All three support graceful reload, but only HAProxy makes runtime operations (drain a server, swap a cert) trivial without reload.
- Nginx’s
proxy_passis the most flexible at URL rewriting; HAProxy’shttp-request set-pathis less expressive but covers 95% of cases. - Traefik’s dashboard is convenient but should never be public-facing without authentication; the same caution applies to HAProxy’s stats page and Nginx’s stub_status.
- Migrating from one to another is painful — config syntax, observability tooling, runbooks all change. Get the choice right at the start.
- Hybrid deployments (HAProxy → Nginx, or Traefik → HAProxy) add a hop but let each tool play to its strengths. This is common in production.
Stack Harbor picks the proxy that matches the workload. For clients running clustered applications where performance and operational control matter most, HAProxy sits at the edge. For containerized platforms where services come and go, Traefik often goes in. Nginx handles website-shaped workloads. We document the choice and its rationale in the operating playbook — this is what we ship as part of Managed Operations so the next engineer who inherits the environment knows why, not just what.