Web Server, Proxy & Load BalancerWeb Servers, Proxies & Load Balancers
Thuộc bộ kiến thức DevOps Roadmap.
Tổng quan
Giữa internet và code ứng dụng của bạn là một lớp gồm web server, proxy và load balancer. Lớp này đảm nhiệm TLS termination, phục vụ file tĩnh, nén và cache response, định tuyến request đến đúng backend, hấp thụ các đợt tăng traffic đột biến, và che chắn ứng dụng khỏi traffic dị dạng hoặc độc hại. Gần như mọi HTTP request trên production đều đi qua ít nhất một trong các thành phần này — thường là nhiều lớp nối tiếp (CDN → cloud load balancer → Nginx ingress → app).
Với DevOps engineer, đây là công việc hằng ngày: viết config Nginx, cấu hình TLS termination, tinh chỉnh cache header, định nghĩa health check, và lựa chọn giữa L4 và L7 load balancing. Đây cũng là nơi đầu tiên cần nhìn vào khi có sự cố — lỗi 502/504, connection reset và lỗi TLS đều lộ ra ở lớp này, và một proxy có log thời gian upstream biến “site chậm” thành “backend số 3 chậm”.
Các khái niệm ở đây có tính chuyển đổi cao. Dù công cụ cụ thể là Nginx, HAProxy, Envoy, Traefik, AWS ALB hay Kubernetes ingress controller, những ý tưởng cốt lõi đều giống nhau: reverse proxy, upstream pool, thuật toán cân bằng tải, health check, xử lý kết nối và caching. Học mô hình một lần thì mọi công cụ chỉ còn là chuyện cú pháp.
Kiến thức nền tảng
Web server thực sự làm gì
Về cốt lõi, một web server (a) nhận kết nối TCP, (b) parse HTTP request, và (c) tạo response — từ file tĩnh trên đĩa hoặc bằng cách ủy thác cho code ứng dụng. Cơ chế ủy thác rất quan trọng:
- File tĩnh — đọc từ đĩa và stream, kèm
Content-Type, caching và hỗ trợ range. Web server làm việc này cực nhanh. - Reverse proxy tới app — chuyển tiếp request tới upstream qua HTTP (
proxy_pass) — mô hình chủ đạo cho app hiện đại (Node, Go, Python/Gunicorn, Java). - FastCGI / uWSGI — giao thức nhị phân tới runtime ngôn ngữ (PHP-FPM kinh điển, Python/uWSGI) — overhead thấp hơn proxy HTTP cho các stack này.
- Module nhúng — chạy ngôn ngữ ngay trong tiến trình server (Apache
mod_php) — đơn giản nhưng gắn chặt vòng đời server và app.
Trục còn lại là mô hình concurrency, quyết định server xử lý được bao nhiêu kết nối khi tải cao.
So sánh các web server
| Nginx | Apache HTTP Server | Caddy | IIS | |
|---|---|---|---|---|
| Kiến trúc | Event-driven, worker bất đồng bộ (ít process, mỗi cái nhiều kết nối) | Process/thread cho mỗi kết nối (MPM event cải thiện) | Event-driven (Go, goroutine) | Tích hợp Windows, I/O bất đồng bộ |
| Concurrency khi tải cao | Xuất sắc, ít bộ nhớ | Tốt với MPM event; nặng hơn với prefork | Rất tốt | Tốt trên Windows |
| Cấu hình | Các block trong nginx.conf (khai báo) | Directive + .htaccess theo thư mục (ghi đè lúc chạy) | Caddyfile (tối giản) hoặc JSON API | GUI / web.config |
| HTTPS tự động | Không (dùng certbot / acme.sh) | Không (dùng certbot / mod_md) | Có — ACME tích hợp, bật mặc định | Qua ACME client cho Windows |
| Vai trò điển hình | Reverse proxy, file tĩnh, LB, K8s ingress | Hosting truyền thống, app phụ thuộc .htaccess (PHP shared hosting) | Service vừa và nhỏ muốn TLS không cần cấu hình | Stack Windows/.NET |
| Nội dung động | Qua upstream (proxy/FastCGI) | Module nhúng (mod_php) hoặc proxy | Qua reverse proxy | ASP.NET in-process |
Nginx thống trị mảng reverse proxy/ingress nhờ tốn ít bộ nhớ và chịu được concurrency cao; Apache vẫn phổ biến ở nơi cần sự linh hoạt theo thư mục của .htaccess (shared hosting, PHP cũ); Caddy thắng ở sự đơn giản và là cách nhanh nhất để có HTTPS đúng, tự gia hạn; IIS là mặc định trong các shop Windows. Envoy và HAProxy cũng đáng nhắc tới: Envoy là proxy L7 hiện đại làm nền cho service mesh (Istio) và API gateway với observability phong phú và cấu hình động (xDS); HAProxy là load balancer hiệu năng cao đã được kiểm chứng, mạnh ở cả L4 và L7.
Forward proxy vs reverse proxy
- Forward proxy đứng trước client: server đích chỉ thấy proxy, không thấy từng client. Công dụng: kiểm soát và lọc egress, thực thi policy doanh nghiệp, ẩn danh, cache request đi ra, và tập trung hóa IP outbound (ví dụ Squid).
- Reverse proxy đứng trước server: client chỉ thấy proxy, không thấy backend. Công dụng: TLS termination, load balancing, caching, nén, rate limiting, định tuyến request, và che giấu topology nội bộ (ví dụ Nginx, HAProxy, Envoy, Traefik).
Mẹo nhớ: forward proxy giấu client; reverse proxy giấu server. API gateway là một reverse proxy chuyên biệt bổ sung auth, rate limiting, biến đổi request và policy theo từng route cho microservice.
Các lớp cache và CDN
Caching là công cụ hiệu năng có đòn bẩy cao nhất ở lớp này — request nhanh nhất là request không bao giờ chạm tới origin.
- Browser / client cache — điều khiển bằng
Cache-Control(max-age,no-cache,private/public,immutable),ETag/If-None-Match,Last-Modified/If-Modified-SincevàExpires. - Reverse-proxy cache —
proxy_cachecủa Nginx hoặc Varnish (HTTP accelerator chuyên dụng với ngôn ngữ cấu hình VCL mạnh mẽ) đặt trước origin chậm. - CDN — mạng reverse-proxy cache phân tán toàn cầu (Cloudflare, CloudFront, Fastly, Akamai — Fastly xây trên Varnish). Static asset và API response cache được sẽ phục vụ từ edge POP gần người dùng, giảm độ trễ và tải origin, hấp thụ đợt tăng đột biến và DDoS.
Khái niệm cache quan trọng:
- Cache key — thường là URL cộng một tập header được chọn; header
Varykhai báo request header nào (ví dụAccept-Encoding) tách cache thành nhiều biến thể. - TTL / độ tươi — một entry được phục vụ bao lâu trước khi revalidate.
stale-while-revalidate/stale-if-error— phục vụ ngay bản hơi cũ trong khi làm tươi ở nền, hoặc khi origin sập (thắng lớn về độ sẵn sàng).- Invalidation / purging — bài toán khó: purge tường minh (theo URL hoặc surrogate/cache tag), hoặc cache-busting bằng URL asset có version/hash (
app.9f2a.js). - Khả năng cache — chỉ cache response safe, idempotent; không bao giờ cache nội dung đã xác thực theo từng user nếu thiếu
privatevà một key theo user.
Khái niệm chính
Các thuật toán load balancing
| Thuật toán | Cách chọn backend | Khi nào dùng |
|---|---|---|
| Round robin | Lần lượt từng backend (có thể gắn trọng số) | Backend stateless đồng nhất — lựa chọn mặc định hợp lý |
| Weighted round robin | Tỉ lệ theo trọng số từng server | Instance kích thước khác nhau; rollout/canary dần theo trọng số |
| Least connections | Backend có ít kết nối đang hoạt động nhất | Request có thời lượng chênh lệch lớn |
| Least response time | Độ trễ thấp + ít kết nối nhất | Service nhạy độ trễ (Nginx Plus / HAProxy) |
| IP hash | Hash IP của client → luôn về cùng backend | Session affinity đơn giản không cần cookie |
| Consistent hashing | Hash theo một key (URI, user ID) với remap tối thiểu khi pool đổi | Tối ưu cache locality; pool sharded/stateful co giãn |
| Random + power of two choices | Chọn ngẫu nhiên hai backend, lấy bên ít tải hơn | Pool rất lớn; tránh hiệu ứng bầy đàn với ít state |
Health check khiến cân bằng tải trở nên an toàn: passive (đánh dấu backend chết sau N request thật thất bại, ví dụ Nginx max_fails/fail_timeout) và active (probe riêng gọi /healthz theo lịch). Không có health check, load balancer vẫn vô tư đẩy traffic vào node đã chết. Phân biệt liveness (tiến trình còn sống?) với readiness (có phục vụ được ngay bây giờ?) để node quá tải hoặc đang khởi động được rút ra thay vì chập chờn.
Session affinity (“sticky session”) ghim một client vào một backend (qua cookie hoặc IP hash) để state session trong bộ nhớ nhất quán — nhưng nó phá vỡ phân phối tải đều và làm phức tạp việc scale. Ưu tiên backend stateless với kho session dùng chung (Redis, JWT) và tránh affinity khi có thể.
L4 vs L7 load balancing
| L4 (transport) | L7 (application) | |
|---|---|---|
| Hoạt động trên | IP + port TCP/UDP | Toàn bộ HTTP request (method, path, host, header, cookie) |
| Tốc độ / overhead | Rất nhanh, ít CPU, throughput cao | Tốn CPU hơn (parse HTTP, thường terminate TLS) |
| Quyết định routing | Theo từng kết nối | Theo từng request (theo path, host, header/cookie, canary %) |
| TLS | Passthrough (hoặc terminate mà không inspect) | Terminate; có thể mã hóa lại về backend |
| Observability | Mức kết nối | Phong phú: status code, độ trễ, metric theo route |
| Tính năng | Chỉ chuyển tiếp | Retry, redirect, rewrite, WAF, rate limiting |
| Ví dụ | AWS NLB, HAProxy chế độ TCP, IPVS, MetalLB | AWS ALB, Nginx, HAProxy chế độ HTTP, Envoy, Traefik |
Quy tắc chung: L7 cho microservice HTTP (routing phong phú, retry, observability, TLS termination); L4 khi bạn mang giao thức không phải HTTP, cần throughput cực lớn với ít CPU, muốn không phá vỡ TLS end-to-end (passthrough), hoặc cần giữ IP nguồn của client mà không dùng thủ thuật header. Nhiều stack dùng cả hai: một NLB L4 phía trước để trải các kết nối thô, rồi các proxy L7 phía sau để routing.
Tầng proxy như một ranh giới bảo mật
Tầng proxy là điểm thực thi bảo mật tự nhiên:
- Cô lập mạng — security group trên cloud chỉ cho phép load balancer chạm tới instance app (tham chiếu security group của LB, không dùng CIDR). Backend không bao giờ được truy cập trực tiếp từ internet.
- WAF — ModSecurity (với OWASP Core Rule Set), AWS WAF, hoặc Cloudflare lọc tấn công L7 (SQLi, XSS, path traversal) trước hoặc ngay trên proxy.
- Rate limiting —
limit_req/limit_conntrong Nginx làm cùn brute force, scraping và DDoS tầng ứng dụng, rẻ hơn nhiều so với scale out. - TLS và security header — quản lý certificate tập trung, HSTS và siết header tại một điểm nghẽn.
- Vệ sinh request — giới hạn kích thước (
client_max_body_size), timeout, và chuẩn hóa header chặn nhiều tấn công request dị dạng.
Xử lý kết nối và timeout
Hai điểm tinh tế thường xuyên cắn người vận hành:
- Keepalive tới upstream — nếu thiếu directive
keepalivevàproxy_http_version 1.1, Nginx mở một kết nối TCP (và có thể TLS) mới cho mỗi request tới backend, thêm độ trễ và cạn kiệt port khi tải cao. Luôn cấu hình pool keepalive cho upstream. - Timeout — timeout connect, send và read phải đặt tường minh. Mặc định thường quá rộng; một backend chậm với
proxy_read_timeoutkhông giới hạn có thể ghim hết mọi worker connection và làm sập cả proxy (head-of-line tại proxy).
Cấu hình Nginx mẫu
Reverse proxy với upstream pool và keepalive:
upstream app_backend {
least_conn;
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 backup; # chỉ dùng khi các server kia chết
keepalive 32; # tái dùng kết nối upstream
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # bắt buộc cho keepalive upstream
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
proxy_next_upstream error timeout http_502 http_503; # thử backend kế tiếp
}
}
TLS termination (kèm redirect HTTP→HTTPS và cấu hình hiện đại):
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri; # ép HTTPS
}
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off; # để client TLS 1.3 tự chọn
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on; # OCSP stapling
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
server_tokens off; # ẩn phiên bản Nginx
client_max_body_size 10m;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Proxy caching kèm stale-on-error và chống stampede:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m
max_size=1g inactive=60m use_temp_path=off;
server {
location /api/ {
proxy_cache api_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_502 http_503 http_504;
proxy_cache_background_update on; # làm tươi ở nền
proxy_cache_lock on; # gộp các cache miss đồng thời (chống stampede)
add_header X-Cache-Status $upstream_cache_status; # HIT / MISS / STALE
proxy_pass http://app_backend;
}
}
Phục vụ file tĩnh với cache dài hạn, immutable:
location /static/ {
root /var/www/app;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
gzip_static on; # phục vụ file .gz nén sẵn nếu có
}
Rate limit một endpoint:
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
location /login {
limit_req zone=login burst=10 nodelay;
proxy_pass http://app_backend;
}
Lệnh vận hành:
nginx -t # kiểm tra config TRƯỚC khi áp dụng — không bao giờ reload mù
nginx -s reload # reload mượt: worker mới nhận config, worker cũ rút dần
nginx -T # in toàn bộ config hiệu lực (mọi include)
curl -I -H "Host: app.example.com" http://127.0.0.1/ # test routing tại chỗ không cần DNS
Tương đương với Caddy (HTTPS tự động trong ba dòng):
app.example.com {
reverse_proxy 10.0.1.10:8080 10.0.1.11:8080 {
lb_policy least_conn
health_uri /healthz
}
}
Caddy tự cấp và gia hạn certificate — không certbot, không cron — đó là lý do nó được ưa chuộng cho service nhỏ và công cụ nội bộ.
Best Practices
- Luôn chạy
nginx -tvà dùng graceful reload — kiểm tra config cộng vớinginx -s reloadcho phép thay đổi không downtime; lỗi cú pháp không bao giờ được chạm tới traffic đang chạy. Trong CI, chạynginx -tvới config đã render trong container. - Terminate TLS ở biên, nhưng cân nhắc mã hóa lại — TLS termination tập trung hóa việc quản lý cert; với môi trường nhạy cảm, bị quản chế hoặc zero-trust, mã hóa lại chặng proxy→backend (hoặc dùng service mesh với mTLS). Không bao giờ gửi credential qua chặng nội bộ plaintext trong mạng không tin cậy.
- Chuyển tiếp danh tính thật của client — set
X-Forwarded-For,X-Forwarded-ProtovàHost, đồng thời cấu hình app chỉ tin proxy của bạn (Nginxreal_ip/set_real_ip_from). Nếu không, log, rate limit, geolocation và quyết định auth đều dùng IP của proxy và đều sai (và có thể bị giả mạo). - Health check phải phản ánh mức sẵn sàng thật — probe một endpoint kiểm tra cả các dependency quan trọng, không chỉ “process còn sống”; tách liveness với readiness để node quá tải hoặc đang khởi động được rút ra thay vì chập chờn.
- Đặt timeout tường minh ở mọi nơi — connect/send/read trên proxy và upstream. Giá trị mặc định thường không giới hạn hoặc quá rộng, và một backend chậm có thể ngốn hết worker connection của proxy và lan thành sự cố toàn diện.
- Cấu hình keepalive cho upstream —
keepalivetrong block upstream cộngproxy_http_version 1.1vàConnection ""tránh handshake TCP/TLS mới cho mỗi request; nguồn gây độ trễ và cạn kiệt port thường xuyên và vô hình. - Phục vụ dữ liệu stale khi backend lỗi —
proxy_cache_use_stale(Nginx) hoặc grace mode (Varnish) giữ người dùng vẫn được phục vụ khi origin sập; kết hợpproxy_cache_lock/proxy_cache_background_updateđể tránh cache stampede khi một entry phổ biến hết hạn. - Không bao giờ để backend lộ trực tiếp — security group chỉ nhận traffic từ tầng load balancer/proxy (tham chiếu security group của nó); xác minh bằng port scan từ bên ngoài và audit định kỳ.
- Rate limit và giới hạn kích thước ở biên —
limit_req/limit_conntheo IP hoặc token vàclient_max_body_sizebảo vệ backend khỏi lạm dụng và tấn công cạn kiệt tài nguyên, rẻ hơn nhiều so với scale out. - Bật HTTP/2 (và HTTP/3 nếu hỗ trợ) về phía client — multiplexing cải thiện rõ rệt tốc độ tải trang; chặng proxy→backend có thể giữ HTTP/1.1 với keepalive, đơn giản hơn và thường là đủ.
- Cache mạnh tay nhưng đúng cách — cache dài hạn
immutablecho static asset có hash, TTL ngắn kèm revalidate cho API response,Varyđúng, và không bao giờ cache công khai nội dung đã xác thực theo user. PhátX-Cache-Statusđể thấy tỉ lệ hit. - Ẩn thông tin server và gửi security header —
server_tokens off;, cộng với HSTS,X-Content-Type-Options: nosniff,X-Frame-Options/frame-ancestors và Content-Security-Policy phù hợp với app. - Nén response — gzip hoặc Brotli cho text/JSON/HTML/CSS/JS (không cho ảnh/video đã nén sẵn); ưu tiên static asset nén sẵn (
gzip_static). Thắng lớn về băng thông và độ trễ với chi phí nhỏ. - Log dạng structured kèm các trường cache/upstream — thêm
$upstream_addr,$upstream_status,$upstream_response_timevà$upstream_cache_status; điều tra 5xx và độ trễ trở nên dễ dàng khi thấy rõ backend nào phục vụ request và cache có hit hay không. - Giữ config proxy trong version control và template hóa — config là code: review qua PR, lint trong CI (
nginx -ttrong container), render theo từng môi trường bằng công cụ templating, deploy bằng configuration management hoặc Kubernetes ingress controller, tuyệt đối không sửa tay trên server đang chạy. - Tinh chỉnh giới hạn worker và kết nối theo workload —
worker_processes auto;,worker_connectionsđủ lớn, và giới hạn file-descriptor ở mức OS; mặc định giả định một máy nhỏ và âm thầm chặn throughput trên máy lớn hơn.
Tài liệu tham khảo
- roadmap.sh DevOps roadmap: https://roadmap.sh/devops
- Tài liệu Nginx: https://nginx.org/en/docs/
- Nginx admin guide (load balancing, caching): https://docs.nginx.com/nginx/admin-guide/
- Tham chiếu Nginx
ngx_http_proxy_module: https://nginx.org/en/docs/http/ngx_http_proxy_module.html - Tài liệu Apache HTTP Server: https://httpd.apache.org/docs/
- Tài liệu Caddy: https://caddyserver.com/docs/
- Tài liệu HAProxy: https://docs.haproxy.org/
- Tài liệu Varnish Cache: https://varnish-cache.org/docs/
- Tài liệu Envoy Proxy: https://www.envoyproxy.io/docs
- Tài liệu Traefik: https://doc.traefik.io/traefik/
- Cloudflare Learning — What is a reverse proxy: https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/
- Cloudflare Learning — What is a CDN: https://www.cloudflare.com/learning/cdn/what-is-a-cdn/
- Mozilla SSL Configuration Generator: https://ssl-config.mozilla.org/
- MDN — HTTP caching: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
- OWASP ModSecurity Core Rule Set: https://coreruleset.org/
- RFC 9111 — HTTP Caching: https://www.rfc-editor.org/rfc/rfc9111
Part of the DevOps Roadmap knowledge base.
Overview
Between the internet and your application code sits a layer of web servers, proxies, and load balancers. This layer terminates TLS, serves static files, compresses and caches responses, routes requests to the right backend, absorbs traffic spikes, and shields applications from malformed or malicious traffic. Nearly every production HTTP request passes through at least one of these components — often several in sequence (CDN → cloud load balancer → Nginx ingress → app).
For DevOps engineers this layer is daily work: writing Nginx configs, setting up TLS termination, tuning cache headers, defining health checks, and choosing between L4 and L7 load balancing. It’s also the first place to look during incidents — 502/504 errors, connection resets, and TLS failures all surface here, and a proxy that logs upstream timing turns “the site is slow” into “backend #3 is slow.”
The concepts are portable. Whether the concrete tool is Nginx, HAProxy, Envoy, Traefik, an AWS ALB, or a Kubernetes ingress controller, the same ideas apply: reverse proxying, upstream pools, balancing algorithms, health checks, connection handling, and caching. Learn the model once and every tool becomes a matter of syntax.
Fundamentals
What a web server actually does
At its core a web server (a) accepts TCP connections, (b) parses HTTP requests, and (c) produces responses — either from static files on disk or by delegating to application code. The delegation mechanism matters:
- Static files — read from disk and stream, with
Content-Type, caching, and range support. Web servers are extremely fast at this. - Reverse proxy to an app — forward the request to an upstream over HTTP (
proxy_pass) — the dominant pattern for modern apps (Node, Go, Python/Gunicorn, Java). - FastCGI / uWSGI — a binary protocol to language runtimes (classic PHP-FPM, Python/uWSGI) — lower overhead than HTTP proxying for those stacks.
- Embedded module — run the language inside the server process (Apache
mod_php) — simple but couples server and app lifecycle.
The other axis is the concurrency model, which determines how many connections a server handles under load.
Web servers compared
| Nginx | Apache HTTP Server | Caddy | IIS | |
|---|---|---|---|---|
| Architecture | Event-driven, async workers (few processes, many connections each) | Process/thread per connection (MPM event improves this) | Event-driven (Go, goroutines) | Windows-integrated, async I/O |
| Concurrency under load | Excellent, low memory | Good with event MPM; heavier with prefork | Very good | Good on Windows |
| Configuration | nginx.conf blocks (declarative) | Directives + per-dir .htaccess (runtime override) | Caddyfile (minimal) or JSON API | GUI / web.config |
| Automatic HTTPS | No (use certbot / acme.sh) | No (use certbot / mod_md) | Yes — built-in ACME, on by default | Via Windows ACME clients |
| Typical role | Reverse proxy, static files, LB, K8s ingress | Legacy hosting, .htaccess-dependent apps (PHP shared hosting) | Small/medium services wanting zero-config TLS | Windows/.NET stacks |
| Dynamic content | Via upstream (proxy/FastCGI) | Embedded modules (mod_php) or proxy | Via reverse proxy | ASP.NET in-process |
Nginx dominates the reverse-proxy/ingress niche due to its low memory footprint and high concurrency; Apache remains common where .htaccess per-directory flexibility matters (shared hosting, legacy PHP); Caddy wins on simplicity and is the fastest way to get correct, auto-renewing HTTPS; IIS is the default in Windows shops. Envoy and HAProxy deserve mention too: Envoy is the modern L7 proxy powering service meshes (Istio) and API gateways with rich observability and dynamic (xDS) config; HAProxy is a battle-tested high-performance load balancer strong at both L4 and L7.
Forward proxy vs reverse proxy
- A forward proxy sits in front of clients: the destination server sees the proxy, not the individual client. Uses: egress control and filtering, corporate policy enforcement, anonymity, caching outbound requests, and centralizing outbound IPs (e.g. Squid).
- A reverse proxy sits in front of servers: the client sees the proxy, not the backends. Uses: TLS termination, load balancing, caching, compression, rate limiting, request routing, and hiding internal topology (e.g. Nginx, HAProxy, Envoy, Traefik).
Memory aid: forward proxy hides the client; reverse proxy hides the server. An API gateway is a specialized reverse proxy adding auth, rate limiting, request transformation, and per-route policy for microservices.
Caching layers and CDNs
Caching is the highest-leverage performance tool at this layer — the fastest request is the one that never reaches your origin.
- Browser / client cache — controlled by
Cache-Control(max-age,no-cache,private/public,immutable),ETag/If-None-Match,Last-Modified/If-Modified-Since, andExpires. - Reverse-proxy cache — Nginx
proxy_cacheor Varnish (a dedicated HTTP accelerator with the powerful VCL configuration language) placed in front of slow origins. - CDN — a globally distributed reverse-proxy cache (Cloudflare, CloudFront, Fastly, Akamai — Fastly is built on Varnish). Static assets and cacheable API responses are served from edge POPs near users, cutting latency and origin load and absorbing spikes and DDoS.
Key cache concepts:
- Cache key — usually the URL plus a selected set of headers; the
Varyheader declares which request headers (e.g.Accept-Encoding) split the cache into variants. - TTL / freshness — how long an entry is served before revalidation.
stale-while-revalidate/stale-if-error— serve a slightly stale copy instantly while refreshing in the background, or when the origin is down (huge availability win).- Invalidation / purging — the hard problem: explicit purge (by URL or surrogate/cache tag), or cache-busting via versioned/hashed asset URLs (
app.9f2a.js). - Cacheability — only cache safe, idempotent responses; never cache authenticated per-user content without
privateand a user-scoped key.
Key Concepts
Load balancing algorithms
| Algorithm | How it picks a backend | When to use |
|---|---|---|
| Round robin | Each in turn (optionally weighted) | Homogeneous stateless backends — the sensible default |
| Weighted round robin | Proportional to per-server weight | Mixed instance sizes; gradual rollout / canary by weight |
| Least connections | Fewest active connections | Requests with highly variable duration |
| Least response time | Lowest latency + fewest connections | Latency-sensitive services (Nginx Plus / HAProxy) |
| IP hash | Hash of client IP → same backend | Cheap session affinity without cookies |
| Consistent hashing | Hash of a key (URI, user ID) with minimal remapping on pool change | Cache locality; sharded/stateful pools that scale up and down |
| Random + power of two choices | Pick two at random, take the less loaded | Very large pools; avoids herd behavior with little state |
Health checks make balancing safe: passive (mark a backend down after N failed real requests, e.g. Nginx max_fails/fail_timeout) and active (a dedicated probe hitting /healthz on a schedule). Without health checks, a balancer happily routes traffic to a dead node. Distinguish liveness (is the process alive?) from readiness (can it serve traffic right now?) so an overloaded or warming-up node drains instead of flapping.
Session affinity (“sticky sessions”) pins a client to one backend (via cookie or IP hash) so in-memory session state stays consistent — but it defeats even load distribution and complicates scaling. Prefer stateless backends with shared session storage (Redis, JWT) and avoid affinity where you can.
L4 vs L7 load balancing
| L4 (transport) | L7 (application) | |
|---|---|---|
| Operates on | IP + TCP/UDP port | Full HTTP request (method, path, host, headers, cookies) |
| Speed / overhead | Very fast, low CPU, high throughput | More CPU (parses HTTP, often terminates TLS) |
| Routing decisions | Per connection | Per request (path-based, host-based, header/cookie, canary %) |
| TLS | Passthrough (or terminate without inspecting) | Terminates; can re-encrypt to backend |
| Observability | Connection-level | Rich: status codes, latency, per-route metrics |
| Features | Just forwarding | Retries, redirects, rewrites, WAF, rate limiting |
| Examples | AWS NLB, HAProxy TCP mode, IPVS, MetalLB | AWS ALB, Nginx, HAProxy HTTP mode, Envoy, Traefik |
Rule of thumb: L7 for HTTP microservices (rich routing, retries, observability, TLS termination); L4 when you carry non-HTTP protocols, need extreme throughput with minimal CPU, want end-to-end TLS not to be broken (passthrough), or need to preserve the client source IP without header tricks. Many stacks use both: an L4 NLB in front to spread raw connections, then L7 proxies behind it for routing.
The proxy tier as a security boundary
The proxy tier is the natural enforcement point:
- Network isolation — cloud security groups allow only the load balancer to reach app instances (reference the LB’s security group, not CIDRs). Backends should never be directly reachable from the internet.
- WAF — ModSecurity (with the OWASP Core Rule Set), AWS WAF, or Cloudflare filters L7 attacks (SQLi, XSS, path traversal) in front of or on the proxy.
- Rate limiting —
limit_req/limit_connin Nginx blunts brute force, scraping, and application-layer DDoS far more cheaply than scaling out. - TLS and security headers — centralized certificate management, HSTS, and header hardening at one choke point.
- Request hygiene — size limits (
client_max_body_size), timeouts, and header normalization stop many malformed-request attacks.
Connection handling and timeouts
Two subtleties bite operators constantly:
- Keepalive to upstreams — without a
keepalivedirective andproxy_http_version 1.1, Nginx opens a fresh TCP (and possibly TLS) connection per request to the backend, adding latency and exhausting ports under load. Always configure an upstream keepalive pool. - Timeouts — connect, send, and read timeouts must be set explicitly. Defaults are often too generous; one slow backend with unbounded
proxy_read_timeoutcan pin every worker connection and take the whole proxy down (head-of-line at the proxy).
Sample Nginx configurations
Reverse proxy with an upstream pool and keepalive:
upstream app_backend {
least_conn;
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 backup; # only used if the others are down
keepalive 32; # reuse upstream connections
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # required for upstream keepalive
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
proxy_next_upstream error timeout http_502 http_503; # retry the next backend
}
}
TLS termination (with HTTP→HTTPS redirect and modern settings):
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri; # force HTTPS
}
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off; # let TLS 1.3 clients choose
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on; # OCSP stapling
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
server_tokens off; # hide Nginx version
client_max_body_size 10m;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Proxy caching with stale-on-error and stampede protection:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m
max_size=1g inactive=60m use_temp_path=off;
server {
location /api/ {
proxy_cache api_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_502 http_503 http_504;
proxy_cache_background_update on; # refresh in the background
proxy_cache_lock on; # collapse concurrent misses (anti-stampede)
add_header X-Cache-Status $upstream_cache_status; # HIT / MISS / STALE
proxy_pass http://app_backend;
}
}
Serving static files with long-lived, immutable caching:
location /static/ {
root /var/www/app;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
gzip_static on; # serve pre-compressed .gz if present
}
Rate limiting an endpoint:
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
location /login {
limit_req zone=login burst=10 nodelay;
proxy_pass http://app_backend;
}
Operational commands:
nginx -t # validate config BEFORE applying — never reload blind
nginx -s reload # graceful reload: new workers pick up config, old ones drain
nginx -T # dump the full effective config (all includes)
curl -I -H "Host: app.example.com" http://127.0.0.1/ # test routing locally without DNS
Caddy equivalent (automatic HTTPS in three lines):
app.example.com {
reverse_proxy 10.0.1.10:8080 10.0.1.11:8080 {
lb_policy least_conn
health_uri /healthz
}
}
Caddy provisions and renews the certificate automatically — no certbot, no cron — which is why it’s a favourite for small services and internal tools.
Best Practices
- Always run
nginx -tand use graceful reloads — config validation plusnginx -s reloadmeans zero-downtime changes; a syntax error should never reach live traffic. In CI, runnginx -tagainst the rendered config in a container. - Terminate TLS at the edge, but consider re-encryption — TLS termination centralizes certificate management; for sensitive, regulated, or zero-trust environments, re-encrypt proxy→backend (or use a service mesh with mTLS). Never send credentials over plaintext internal hops in untrusted networks.
- Forward the real client identity — set
X-Forwarded-For,X-Forwarded-Proto, andHost, and configure the app to trust only your proxy (Nginxreal_ip/set_real_ip_from). Otherwise logs, rate limits, geolocation, and auth decisions all use the proxy’s IP and are wrong (and spoofable). - Health checks must reflect real readiness — probe an endpoint that verifies critical dependencies, not just “process is up”; separate liveness from readiness so overloaded or starting nodes drain rather than flap.
- Set explicit timeouts everywhere — connect/send/read on proxy and upstream. Defaults are often unbounded or too generous, and one slow backend can exhaust proxy worker connections and cascade into a full outage.
- Configure upstream keepalive —
keepalivein the upstream block plusproxy_http_version 1.1andConnection ""avoids a new TCP/TLS handshake per request; a frequent, invisible source of latency and port exhaustion. - Serve stale on backend failure —
proxy_cache_use_stale(Nginx) or grace mode (Varnish) keeps users served during origin outages; pair withproxy_cache_lock/proxy_cache_background_updateto prevent cache stampedes when a popular entry expires. - Never expose backends directly — security groups should accept traffic only from the load balancer/proxy tier (reference its security group); verify with an external port scan and periodic audits.
- Rate limit and size-limit at the edge —
limit_req/limit_connper IP or token andclient_max_body_sizeprotect backends from abuse and resource-exhaustion attacks far more cheaply than scaling out. - Enable HTTP/2 (and HTTP/3 where supported) toward clients — multiplexing meaningfully improves page load; the proxy→backend hop can stay HTTP/1.1 with keepalive, which is simpler and usually sufficient.
- Cache aggressively but correctly — long-lived
immutablecaching for hashed static assets, short TTLs with revalidation for API responses, correctVary, and never cache per-user authenticated content publicly. EmitX-Cache-Statusso you can see hit rates. - Hide server details and send security headers —
server_tokens off;, plus HSTS,X-Content-Type-Options: nosniff,X-Frame-Options/frame-ancestors, and a Content-Security-Policy appropriate to the app. - Compress responses — gzip or Brotli for text/JSON/HTML/CSS/JS (not for already-compressed images/video); prefer pre-compressed static assets (
gzip_static). Big bandwidth and latency wins for little cost. - Log in structured form with cache/upstream fields — include
$upstream_addr,$upstream_status,$upstream_response_time, and$upstream_cache_status; 5xx and latency triage becomes trivial when you can see which backend served the request and whether the cache was hit. - Keep proxy configs in version control and template them — configs are code: review them in PRs, lint in CI (
nginx -tin a container), render per-environment with a templating tool, deploy via configuration management or a Kubernetes ingress controller, and never hand-edit on live servers. - Tune worker and connection limits for the workload —
worker_processes auto;, sufficientworker_connections, and OS-level file-descriptor limits; the defaults assume a small box and silently cap throughput on larger ones.
References
- roadmap.sh DevOps roadmap: https://roadmap.sh/devops
- Nginx documentation: https://nginx.org/en/docs/
- Nginx admin guide (load balancing, caching): https://docs.nginx.com/nginx/admin-guide/
- Nginx
ngx_http_proxy_modulereference: https://nginx.org/en/docs/http/ngx_http_proxy_module.html - Apache HTTP Server documentation: https://httpd.apache.org/docs/
- Caddy documentation: https://caddyserver.com/docs/
- HAProxy documentation: https://docs.haproxy.org/
- Varnish Cache documentation: https://varnish-cache.org/docs/
- Envoy Proxy documentation: https://www.envoyproxy.io/docs
- Traefik documentation: https://doc.traefik.io/traefik/
- Cloudflare Learning — What is a reverse proxy: https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/
- Cloudflare Learning — What is a CDN: https://www.cloudflare.com/learning/cdn/what-is-a-cdn/
- Mozilla SSL Configuration Generator: https://ssl-config.mozilla.org/
- MDN — HTTP caching: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
- OWASP ModSecurity Core Rule Set: https://coreruleset.org/
- RFC 9111 — HTTP Caching: https://www.rfc-editor.org/rfc/rfc9111