← Backend← Backend
BackendBackend19 Th7, 2026Jul 19, 202615 phút đọc13 min read

Web ServerWeb Servers

Thuộc bộ kiến thức Backend Roadmap.

Tổng quan

Một web server là chương trình lắng nghe các request HTTP(S) qua network và trả về response HTTP. Theo nghĩa cổ điển, nó phục vụ static content (HTML, CSS, JavaScript, hình ảnh, font) trực tiếp từ disk. Trong kiến trúc hiện đại, nó gần như luôn đảm nhận thêm một vai trò quan trọng hơn: đứng phía trước application của bạn như một reverse proxy — làm TLS termination, routing, load balancing, caching, compression, và che chắn app khỏi lưu lượng internet thô.

Hiểu về web server là quan trọng vì nó là chặng đầu tiên của mọi request đến backend. Cấu hình đúng tầng web server mang lại lợi ích “miễn phí” về performance (caching, compression, tái sử dụng connection), security (TLS, rate limiting, lọc request), và khả năng vận hành (graceful reload, access log, health check) — trước khi một dòng code application nào chạy.

Note này bổ trợ cho hai phần liên quan trong bộ kiến thức:

Ở đây ta tập trung vào góc nhìn của backend developer: web server nằm ở đâu phía trước app, một reverse proxy thực sự làm gì, và cấu hình nó thế nào cho đúng.

Note liên quan: Internet and Web Fundamentals · Caching · Scalability and Reliability.

Kiến thức nền tảng

Static content vs dynamic content

Khía cạnhStatic contentDynamic content
Định nghĩaFile không đổi theo từng request (HTML, CSS, JS, hình ảnh)Response tạo ra theo từng request (HTML render từ DB, JSON từ API)
Tạo bởiWeb server đọc trực tiếp từ diskCode application (application server) tính toán
Ví dụ/logo.png, /app.css, /index.html/api/orders/42, /dashboard sau khi đăng nhập
Khả năng cacheRất cao — thường được cache mạnh ở CDN và browserTùy — phụ thuộc mức độ cá nhân hóa và độ tươi (freshness)
Ai phục vụ tốt nhấtWeb server (Nginx/Apache) hoặc CDNApplication server phía sau web server

Nguyên tắc tốt: để web server phục vụ static asset (nó được tối ưu cho sendfile, byte range, caching header) và để application server xử lý logic động. Web server sẽ proxy các request động tới app.

Đường đi của request phía trước application server

Luồng request điển hình của một web app hiện đại:

Đường đi của request qua tầng phục vụ
  1. 01Client (browser / mobile)
  2. HTTPS
    02CDNtùy chọn — edge cache cho static asset
  3. 03Load balancer (L4/L7)
  4. 04Web server / reverse proxy (Nginx)
  5. phục vụ static, proxy request động
    05Application serverGunicorn / Node / Puma / Kestrel
  6. 06Database, cache, queue

Web server / reverse proxy là ranh giới giữa “public edge” và “phần ruột application”. Nó nói HTTP với thế giới bên ngoài và forward tới app qua một upstream connection riêng tư, thường là plaintext (HTTP hoặc Unix socket).

Vì sao không expose application server trực tiếp?

Đa số application server có thể nói HTTP trực tiếp, nhưng nhìn chung bạn không nên expose chúng ra internet:

Khái niệm chính

Web server vs application server vs reverse proxy vs API gateway vs load balancer

Các thuật ngữ này chồng lấn nhau, và một sản phẩm (như Nginx) có thể đóng nhiều vai trò cùng lúc. Sự phân biệt là về trách nhiệm, không nhất thiết là các máy riêng biệt.

Thành phầnNhiệm vụ chínhHoạt động ở tầngVí dụ điển hình
Web serverNhận request HTTP; phục vụ static file; thường kiêm reverse proxyL7 (HTTP)Nginx, Apache httpd, Caddy, IIS
Application serverChạy code application; tạo ra response độngL7 (HTTP/app protocol)Gunicorn/uWSGI (Python), Puma (Ruby), Kestrel (.NET), Node.js, Tomcat (Java)
Reverse proxyĐứng trước một hoặc nhiều server; forward request thay cho chúngL7Nginx, HAProxy, Envoy, Traefik, Caddy
API gatewayReverse proxy chuyên cho API: auth, rate limiting, quota, routing, transform request/response, versioningL7Kong, Amazon API Gateway, Apigee, gateway dựa trên Envoy
Load balancerPhân phối lưu lượng qua nhiều backend để scale và tăng tính sẵn sàngL4 (TCP/UDP) hoặc L7 (HTTP)HAProxy, Nginx, AWS ELB/ALB/NLB, Envoy

Một vài điểm cần làm rõ:

Trách nhiệm của reverse proxy

Reverse proxy chứng minh giá trị của mình bằng cách xử lý các mối quan tâm xuyên suốt ở một nơi:

Trách nhiệmLàm gìVì sao quan trọng
TLS terminationGiải mã HTTPS ở edge; nói plaintext/HTTP với backendTập trung quản lý certificate; giảm tải crypto khỏi app
RoutingChọn backend dựa trên host, path, method, hoặc headerMột entry point phục vụ được nhiều app/microservice
Load balancingTrải request qua các upstream instance; bỏ qua instance không khỏeScale ngang và tính sẵn sàng
CachingLưu response và phục vụ mà không cần chạm backendGiảm mạnh latency và tải cho content cache được
CompressionNén gzip/brotli response ngay lúc trả vềPayload nhỏ hơn, tải trang nhanh hơn
Rate limitingGiới hạn tốc độ request theo client/keyBảo vệ backend khỏi lạm dụng và đỉnh lưu lượng
BufferingĐọc trọn vẹn request/response của slow client trước/sau khi chạm appGiải phóng app worker khỏi slow client
Chỉnh sửa headerThêm/bớt header (X-Forwarded-For, HSTS, security header)Thông tin client đúng, tăng cường bảo mật
Phục vụ staticPhục vụ file trực tiếp, không qua appGiao asset nhanh

TLS termination

Proxy giữ certificate và private key, terminate TLS, rồi forward plaintext HTTP tới upstream (qua network riêng đáng tin) — hoặc mã hóa lại (TLS passthrough / re-encryption) khi chặng tới backend cũng cần được mã hóa. Terminate ở edge nghĩa là bạn gia hạn và quản lý certificate ở một chỗ. Caddy còn tiến xa hơn với automatic HTTPS qua ACME/Let’s Encrypt ngay từ mặc định.

Thuật toán load balancing

Thuật toánHành viPhù hợp cho
Round robinRequest phân phối theo thứ tựBackend đồng nhất, stateless (mặc định)
Least connectionsGửi tới backend có ít active connection nhấtRequest không đều hoặc kéo dài
IP hashCùng client IP → cùng backendSticky session mà không cần shared session store
WeightedWeight lớn hơn → nhiều lưu lượng hơnBackend có năng lực khác nhau

Health check (passive: đánh dấu backend down sau N lần lỗi; active: probe một endpoint) giữ lưu lượng tránh xa các instance đã chết.

Mô hình process và concurrency

Cách một web server xử lý nhiều connection đồng thời quyết định profile performance của nó.

Mô hìnhCách hoạt độngDùng bởi
Event-driven / asyncVài worker process, mỗi cái là một event loop xử lý hàng nghìn connection qua epoll/kqueueNginx, Caddy, Envoy
Process/thread mỗi connectionMỗi connection do một process hoặc thread xử lýApache MPM prefork/worker (cổ điển)
HybridWorker process, mỗi cái có một pool thread/eventApache MPM event

Server event-driven (Nginx, Caddy) scale được lên mức concurrency rất cao với bộ nhớ thấp vì không cấp phát một thread/process cho mỗi connection. Đây là lý do Nginx trở thành reverse proxy chuẩn cho các site lưu lượng cao.

Keep-alive, timeout và buffering

Best Practices

Khảo sát các web server

ServerMô hìnhKiểu configĐiểm mạnh nổi bậtĐiểm cần lưu ý
NginxEvent-drivenBlock khai báo trong nginx.confReverse proxy nhanh, load balancing, caching; ecosystem lớnTLS phải cấu hình thủ công; một số tính năng là bản thương mại (Nginx Plus)
Apache httpdProcess/thread (MPM)Directive, .htaccessTrưởng thành, module linh hoạt, override .htaccess theo thư mụcTốn bộ nhớ hơn khi concurrency cao ở chế độ prefork
CaddyEvent-driven (Go)Caddyfile đơn giản / JSONAutomatic HTTPS qua ACME; mặc định hợp lý; HTTP/3Ecosystem nhỏ hơn; ít “bí kíp” tuning
Microsoft IISNative trên WindowsGUI + web.configTích hợp sâu Windows/.NET, auth qua Active DirectoryChỉ chạy trên Windows
App-embedded serverTùy (async/threaded)Config trong code/frameworkKhông thêm chặng; setup dev đơn giản (Express, Kestrel, Gunicorn, Uvicorn)Trong production thường đặt reverse proxy phía trước để lo TLS, static, buffering

Kinh nghiệm chung: Nginx hoặc Caddy làm reverse proxy phía trước; embedded/app server của framework nằm phía sau. Chọn Caddy nếu muốn HTTPS zero-config; chọn Nginx nếu muốn kiểm soát tối đa và ecosystem lớn.

Một config reverse-proxy Nginx thực tế

Reverse proxy tới upstream app, có TLS, gzip và caching response cơ bản:

# /etc/nginx/nginx.conf (http context — chỉ hiển thị các directive chính)

# Định nghĩa proxy cache zone: 10 MB cho key, tối đa 1 GB trên disk.
proxy_cache_path /var/cache/nginx/app
                 levels=1:2
                 keys_zone=app_cache:10m
                 max_size=1g
                 inactive=60m
                 use_temp_path=off;

# Rate limit: 10 request/giây mỗi client IP, cho phép burst nhỏ.
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;

# Pool upstream gồm các application instance.
upstream app_backend {
    least_conn;                       # gửi tới instance ít bận nhất
    server 127.0.0.1:8000 max_fails=3 fail_timeout=15s;
    server 127.0.0.1:8001 max_fails=3 fail_timeout=15s;
    keepalive 32;                     # tái sử dụng connection tới upstream
}

# Redirect toàn bộ HTTP plaintext sang HTTPS.
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name example.com www.example.com;

    # --- TLS termination ---
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;

    # --- Security header ---
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;

    # --- Nén gzip ---
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml image/svg+xml;
    gzip_vary on;

    # --- Phục vụ static asset trực tiếp, cache mạnh ở browser ---
    location /static/ {
        root /var/www/example;
        expires 30d;
        access_log off;
        add_header Cache-Control "public, immutable";
    }

    # --- Proxy request động tới app, có caching response ---
    location / {
        limit_req zone=req_per_ip burst=20 nodelay;

        proxy_pass http://app_backend;

        # Giữ lại thông tin client cho app.
        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;

        # Dùng HTTP/1.1 + keep-alive tới upstream.
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        # Timeout.
        proxy_connect_timeout 5s;
        proxy_send_timeout    30s;
        proxy_read_timeout    30s;

        # Caching response cho các response GET/HEAD cache được.
        proxy_cache            app_cache;
        proxy_cache_valid      200 301 302 10m;
        proxy_cache_valid      404 1m;
        proxy_cache_use_stale  error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_lock       on;   # gộp các miss đồng thời thành một lần fetch upstream
        add_header X-Cache-Status $upstream_cache_status;  # HIT / MISS / EXPIRED — rất tiện để debug
    }
}

Ghi chú về config:

Reload mà không rớt connection sau khi sửa config:

nginx -t            # validate config trước — đừng bao giờ reload một config lỗi
nginx -s reload     # graceful reload: worker mới khởi động, worker cũ drain

Cơ bản về tuning

Thiết lậpHướng dẫn
worker_processesĐặt auto (một worker mỗi CPU core) cho công việc TLS/compression thiên về CPU
worker_connectionsTăng lên (ví dụ 1024–65535) đồng thời với giới hạn file-descriptor của OS (ulimit -n)
keepalive_timeout60–75s cho client; giữ một pool keepalive tới upstream để giảm reconnect
TimeoutĐặt timeout read/send cho client và proxy để giới hạn dùng tài nguyên
sendfile / tcp_nopushBật để giao static file hiệu quả
LoggingDùng access log có cấu trúc/buffer; tắt access log cho các path static lưu lượng cao

Best practices vận hành

Tài liệu tham khảo

Part of the Backend Roadmap knowledge base.

Overview

A web server is a program that listens for HTTP(S) requests over the network and returns HTTP responses. In the classic sense it serves static content (HTML, CSS, JavaScript, images, fonts) directly from disk. In modern architectures it almost always plays a second, more important role: sitting in front of your application as a reverse proxy — terminating TLS, routing requests, load balancing, caching, compressing, and shielding the app from raw internet traffic.

Understanding web servers matters because they are the first hop of every request that reaches your backend. Getting the web server layer right gives you free wins in performance (caching, compression, connection reuse), security (TLS, rate limiting, request filtering), and operability (graceful reloads, access logs, health checks) — before a single line of your application code runs.

This note complements two related sections in this knowledge base:

Here we focus on the backend developer’s view: how a web server fits in front of your app, what a reverse proxy actually does, and how to configure one correctly.

Related notes: Internet and Web Fundamentals · Caching · Scalability and Reliability.

Fundamentals

Static vs dynamic content

AspectStatic contentDynamic content
DefinitionFiles that don’t change per request (HTML, CSS, JS, images)Responses generated per request (HTML rendered from a DB, JSON from an API)
Produced byRead directly from disk by the web serverComputed by application code (app server)
Examples/logo.png, /app.css, /index.html/api/orders/42, /dashboard after login
CacheabilityVery high — often cached aggressively at CDN and browserVaries — depends on personalization and freshness
Who serves it bestWeb server (Nginx/Apache) or CDNApplication server behind the web server

A good rule: let the web server serve static assets (it is optimized for sendfile, byte ranges, caching headers) and let the app server handle dynamic logic. The web server proxies dynamic requests to the app.

The request path in front of an app server

A typical request flow for a modern web app:

Request path through the serving stack
  1. 01Client (browser / mobile)
  2. HTTPS
    02CDNoptional — edge cache for static assets
  3. 03Load balancer (L4/L7)
  4. 04Web server / reverse proxy (Nginx)
  5. serves static, proxies dynamic
    05Application serverGunicorn / Node / Puma / Kestrel
  6. 06Database, cache, queues

The web server / reverse proxy is the boundary between the “public edge” and your “application internals”. It speaks HTTP to the world and forwards to the app over a private, often plaintext, upstream connection (HTTP or a Unix socket).

Why not expose the app server directly?

Most application servers can speak HTTP directly, but you generally shouldn’t expose them to the internet:

Key Concepts

Web server vs application server vs reverse proxy vs API gateway vs load balancer

These terms overlap and a single product (like Nginx) can play several roles at once. The distinction is about responsibility, not necessarily separate machines.

ComponentPrimary jobOperates atTypical examples
Web serverAccept HTTP requests; serve static files; often also reverse-proxyL7 (HTTP)Nginx, Apache httpd, Caddy, IIS
Application serverRun your application code; produce dynamic responsesL7 (HTTP/app protocol)Gunicorn/uWSGI (Python), Puma (Ruby), Kestrel (.NET), Node.js, Tomcat (Java)
Reverse proxySit in front of one or more servers; forward requests on their behalfL7Nginx, HAProxy, Envoy, Traefik, Caddy
API gatewayReverse proxy specialized for APIs: auth, rate limiting, quotas, routing, request/response transformation, versioningL7Kong, Amazon API Gateway, Apigee, Envoy-based gateways
Load balancerDistribute traffic across many backends for scale and availabilityL4 (TCP/UDP) or L7 (HTTP)HAProxy, Nginx, AWS ELB/ALB/NLB, Envoy

Key clarifications:

Reverse proxy responsibilities

A reverse proxy earns its place by handling cross-cutting concerns in one place:

ResponsibilityWhat it doesWhy it matters
TLS terminationDecrypts HTTPS at the edge; talks plaintext/HTTP to the backendCentralizes certificate management; offloads crypto from the app
RoutingChooses a backend based on host, path, method, or headersOne entry point can serve many apps/microservices
Load balancingSpreads requests across upstream instances; skips unhealthy onesHorizontal scale and availability
CachingStores responses and serves them without hitting the backendMassive latency and load reduction for cacheable content
Compressiongzip/brotli responses on the flySmaller payloads, faster page loads
Rate limitingCaps request rate per client/keyProtects backends from abuse and traffic spikes
BufferingReads full slow-client requests/responses before/after touching the appFrees app workers from slow clients
Header manipulationAdds/removes headers (X-Forwarded-For, HSTS, security headers)Correct client info, hardening
Static servingServes files directly, bypassing the appFast asset delivery

TLS termination

The proxy holds the certificate and private key, terminates TLS, and forwards plaintext HTTP to the upstream (over a trusted private network) — or re-encrypts (TLS passthrough / re-encryption) when the backend hop must also be encrypted. Terminating at the edge means you renew and manage certificates in one place. Caddy takes this further with automatic HTTPS via ACME/Let’s Encrypt out of the box.

Load balancing algorithms

AlgorithmBehaviorGood for
Round robinRequests distributed in orderHomogeneous, stateless backends (default)
Least connectionsSend to the backend with fewest active connectionsUneven or long-lived requests
IP hashSame client IP → same backendSticky sessions without shared session store
WeightedBigger weight → more trafficMixed-capacity backends

Health checks (passive: mark a backend down after N failures; active: probe an endpoint) keep traffic away from dead instances.

Process and concurrency models

How a web server handles many simultaneous connections shapes its performance profile.

ModelHow it worksUsed by
Event-driven / asyncA few worker processes, each an event loop handling thousands of connections via epoll/kqueueNginx, Caddy, Envoy
Process/thread per connectionEach connection handled by a process or threadApache prefork/worker MPMs (classic)
HybridWorker processes each with a thread/event poolApache event MPM

Event-driven servers (Nginx, Caddy) scale to very high concurrency with low memory because they don’t allocate a thread/process per connection. This is why Nginx became the standard reverse proxy for high-traffic sites.

Keep-alive, timeouts, and buffering

Best Practices

Survey of web servers

ServerModelConfig styleStandout strengthsWatch-outs
NginxEvent-drivenDeclarative nginx.conf blocksFast reverse proxy, load balancing, caching; huge ecosystemManual TLS setup; some features are commercial (Nginx Plus)
Apache httpdProcess/thread (MPMs)Directives, .htaccessMature, flexible modules, per-directory .htaccess overridesHigher memory under high concurrency in prefork mode
CaddyEvent-driven (Go)Simple Caddyfile / JSONAutomatic HTTPS via ACME; sane defaults; HTTP/3Smaller ecosystem; less tuning lore
Microsoft IISWindows-nativeGUI + web.configDeep Windows/.NET integration, Active Directory authWindows-only
App-embedded serversVaries (async/threaded)Code/framework configNo extra hop; simple dev setup (Express, Kestrel, Gunicorn, Uvicorn)Usually put a reverse proxy in front for TLS, static, buffering in prod

Rule of thumb: Nginx or Caddy as the reverse proxy in front; your app framework’s embedded/app server behind it. Caddy if you want zero-config HTTPS; Nginx if you want maximum control and ecosystem.

A real Nginx reverse-proxy config

Reverse proxy to an upstream app, with TLS, gzip, and basic response caching:

# /etc/nginx/nginx.conf (http context — key directives shown)

# Define a proxy cache zone: 10 MB of keys, up to 1 GB on disk.
proxy_cache_path /var/cache/nginx/app
                 levels=1:2
                 keys_zone=app_cache:10m
                 max_size=1g
                 inactive=60m
                 use_temp_path=off;

# Rate limit: 10 requests/sec per client IP, with a small burst allowance.
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;

# Upstream pool of application instances.
upstream app_backend {
    least_conn;                       # send to the least-busy instance
    server 127.0.0.1:8000 max_fails=3 fail_timeout=15s;
    server 127.0.0.1:8001 max_fails=3 fail_timeout=15s;
    keepalive 32;                     # reuse upstream connections
}

# Redirect all plaintext HTTP to HTTPS.
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name example.com www.example.com;

    # --- TLS termination ---
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;

    # --- Security headers ---
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;

    # --- gzip compression ---
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml image/svg+xml;
    gzip_vary on;

    # --- Serve static assets directly, cache hard in the browser ---
    location /static/ {
        root /var/www/example;
        expires 30d;
        access_log off;
        add_header Cache-Control "public, immutable";
    }

    # --- Proxy dynamic requests to the app, with response caching ---
    location / {
        limit_req zone=req_per_ip burst=20 nodelay;

        proxy_pass http://app_backend;

        # Preserve client info for the app.
        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;

        # Use HTTP/1.1 + keep-alive to the upstream.
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        # Timeouts.
        proxy_connect_timeout 5s;
        proxy_send_timeout    30s;
        proxy_read_timeout    30s;

        # Response caching for cacheable GET/HEAD responses.
        proxy_cache            app_cache;
        proxy_cache_valid      200 301 302 10m;
        proxy_cache_valid      404 1m;
        proxy_cache_use_stale  error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_lock       on;   # collapse concurrent misses into one upstream fetch
        add_header X-Cache-Status $upstream_cache_status;  # HIT / MISS / EXPIRED — great for debugging
    }
}

Notes on the config:

Reload without dropping connections after editing config:

nginx -t            # validate config first — never reload a broken config
nginx -s reload     # graceful reload: new workers start, old ones drain

Tuning basics

SettingGuidance
worker_processesSet to auto (one per CPU core) for CPU-bound TLS/compression work
worker_connectionsRaise (e.g. 1024–65535) alongside the OS file-descriptor limit (ulimit -n)
keepalive_timeout60–75s for clients; keep an upstream keepalive pool to reduce reconnects
TimeoutsSet client and proxy read/send timeouts to bound resource usage
sendfile / tcp_nopushEnable for efficient static file delivery
LoggingUse structured/buffered access logs; disable access log for high-volume static paths

Operational best practices

References