Reverse Proxy vs Load Balancer
debt(d7/e7/b7/t5)
Closest to 'only careful code review or runtime testing' (d7). No automated linter flags missing reverse proxy or load balancing; infrastructure tools like nginx/haproxy configs require human review. Production issues (slow client attacks, no health checks) surface under load testing.
Closest to 'cross-cutting refactor across the codebase' (e7). Introducing a reverse proxy or load balancer requires infrastructure changes, SSL cert handling, session storage migration to Redis, health check configuration — touches deployment topology, not just code.
Closest to 'strong gravitational pull' (b7). The proxy/LB layer shapes session handling, SSL termination, static asset routing, header forwarding (X-Forwarded-For), and deployment patterns across all web/api contexts.
Closest to 'notable trap most devs eventually learn' (t5). The misconception cited — that a load balancer is required for production, conflating it with reverse proxy duties — is a common confusion; round-robin without sticky sessions and unbuffered slow-client attacks are documented gotchas.
Also Known As
TL;DR
Explanation
A reverse proxy (nginx, Caddy, Traefik) sits in front of one or more application servers: it terminates SSL, compresses responses, caches static content, handles slow clients, and hides backend topology. A load balancer (HAProxy, AWS ALB, nginx upstream) specifically distributes requests across multiple backend instances. In practice these overlap — nginx does both. PHP-FPM sits behind nginx as a reverse proxy that forwards PHP requests via FastCGI, serving static files directly.
Diagram
flowchart TD
subgraph Reverse Proxy
C1[Client] --> RP[nginx]
RP -->|SSL termination| APP1[PHP-FPM]
RP -->|Serves static| STATIC[/assets/]
RP -->|Buffers slow clients| APP1
end
subgraph Load Balancer
C2[Client] & C3[Client] & C4[Client] --> LB[HAProxy / ALB]
LB --> S1[Server 1]
LB --> S2[Server 2]
LB --> S3[Server 3]
end
style RP fill:#1f6feb,color:#fff
style LB fill:#238636,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Exposing PHP-FPM port (9000) directly — always put nginx in front; FPM has no authentication or rate limiting.
- Not buffering slow client responses in nginx — PHP workers are held until the client downloads the full response.
- Load balancing without health checks — unhealthy backends receive traffic without them.
- Round-robin balancing for stateful sessions without Redis — sticky sessions or centralised session storage is needed.
Code Examples
# PHP-FPM exposed directly to internet:
# docker-compose.yml:
services:
php:
image: php:8.3-fpm
ports:
- '9000:9000' # NEVER expose FPM directly — no auth, protocol not HTTP
# nginx reverse proxy in front of PHP-FPM:
# nginx.conf:
server {
listen 443 ssl http2;
ssl_certificate /etc/ssl/cert.pem;
location / {
fastcgi_pass php:9000; # Internal only
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
include fastcgi_params;
}
location ~* \.(css|js|png|jpg)$ {
expires 1y;
add_header Cache-Control 'public, immutable';
}
}