When designing high-traffic web infrastructure, I've encountered numerous debates about combining multiple reverse proxies. Let's examine why engineers might layer these components despite feature overlaps:
# Typical multi-layer configuration example
client → HAProxy (TCP load balancing)
→ Varnish (HTTP caching)
→ nginx (SSL termination/static content)
→ application servers
HAProxy shines in these scenarios:
- Layer 4 load balancing with minimal overhead
- Advanced health checking mechanisms
- TCP connection multiplexing
# HAProxy configuration snippet for TLS passthrough
frontend https-in
bind *:443
mode tcp
default_backend varnish_servers
backend varnish_servers
mode tcp
server varnish1 192.168.1.10:80 check
server varnish2 192.168.1.11:80 check backup
While nginx can cache, Varnish offers:
- Millisecond-level cache purging via VCL
- Edge Side Includes (ESI) support
- Built-in support for grace/stale content
# Varnish VCL example for cache variation
sub vcl_backend_response {
if (bereq.url ~ "\.(jpg|png|gif)$") {
set beresp.ttl = 1h;
set beresp.http.Cache-Control = "public, max-age=3600";
}
elseif (bereq.url ~ "\.(css|js)$") {
set beresp.ttl = 7d;
}
}
In our stack, nginx handles:
- TLS termination with OCSP stapling
- Broti/Gzip compression
- Static file serving with zero-copy sendfile
# nginx configuration for optimized TLS
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
For many deployments, nginx alone suffices with:
- ngx_http_proxy_module for load balancing
- ngx_http_cache_purge module
- Lua scripting via OpenResty
The decision to combine these tools depends on:
if (traffic > 10K RPS && need_subsecond_purges) {
use Varnish;
} else if (need_L4_load_balancing) {
use HAProxy;
} else {
nginx_alone_may_suffice();
}
In modern web deployments, it's common to see complex stacks with multiple reverse proxies and caching layers. While this provides feature flexibility, it introduces operational complexity that needs justification.
Each technology shines in specific scenarios:
# Nginx configuration snippet for SSL termination
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Compression and caching directives...
}
Nginx excels at SSL termination and static content serving, while Varnish provides advanced caching logic:
# Varnish VCL for cache control
sub vcl_backend_response {
if (bereq.url ~ "\.(jpg|png|gif|webp)$") {
set beresp.ttl = 1w;
}
if (bereq.url ~ "\.(css|js)$") {
set beresp.ttl = 3d;
}
}
Common production architectures include:
- HAProxy → Varnish → Nginx → Application (for high-availability needs)
- Cloudflare → Nginx → Varnish → Application (CDN-enhanced stack)
- AWS ALB → HAProxy → Application (cloud-native simplified stack)
Each additional layer introduces latency. Benchmarks show:
# Sample wrk benchmark results
Single Nginx: 45,000 req/s
Nginx+Varnish: 38,000 req/s (but better cache hit ratio)
Full stack: 32,000 req/s (with HAProxy health checks)
Consider these operational factors:
- Configuration synchronization across layers
- Debugging request flow through multiple components
- Security patch management for each component
Newer solutions like Envoy and Traefik combine multiple functionalities:
# Envoy configuration combining LB and caching
resources:
- "@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: backend_service
cache:
disabled: false
max_bytes: 52428800
Ultimately, the right architecture depends on your specific traffic patterns, performance requirements, and team expertise.