Skip to content

Web Servers and Proxies

A web server accepts HTTP requests and serves content or application responses. A forward proxy is chosen by a client; a reverse proxy—called a gateway by HTTP specifications—acts as the origin-facing endpoint and forwards requests to upstream services. Load balancers, caches, API gateways, and ingress controllers often combine these roles.

Request Path

client → DNS → edge/CDN → load balancer or reverse proxy → application → dependencies

Each hop can terminate TLS, enforce policy, transform HTTP, retry, cache, buffer, and add latency. Document the actual path and trust boundary. RFC 9110 defines current HTTP intermediary semantics.

Reverse Proxy Responsibilities

Use a reverse proxy for a concrete need:

  • TLS termination and certificate lifecycle;
  • virtual-host and path routing;
  • load balancing and health-aware endpoint selection;
  • bounded request and response buffering;
  • compression and static content;
  • caching with correct HTTP semantics;
  • connection limits, timeouts, and overload protection;
  • consistent access telemetry;
  • controlled header normalization.

Keep business authorization and domain validation in the application. A proxy policy can add defense in depth but may be bypassed by alternate paths or misconfiguration.

TLS

Automate certificate issuance, renewal, deployment, and expiry monitoring. Use current protocol and cipher guidance from the platform or security standard rather than copying a permanent cipher list from a tutorial.

Decide whether TLS ends at the edge or continues upstream based on network trust and identity requirements. Upstream encryption without certificate validation does not authenticate the destination. For mutual TLS, define issuer trust, workload identity, rotation, revocation behavior, and failure mode.

Preserve the original scheme and host only through trusted proxy metadata. Applications must accept forwarded headers from known proxies, not arbitrary clients.

Forwarded Identity and Addresses

Headers such as Forwarded and X-Forwarded-For form a chain. The trusted edge should remove or overwrite untrusted client-supplied values, then append known hop information. Applications need an explicit trusted-proxy count or range.

Client IP addresses are weak identity, may be shared or privacy-sensitive, and should not be the sole authorization or abuse-control key.

Routing

Route by stable host, path, method, or protocol requirements. Normalize paths consistently across proxy and application to avoid authorization and cache discrepancies. Be cautious with rewrites, encoded separators, case rules, and trailing slashes.

A route change is an API change. Test normal, missing, malformed, large, and ambiguous requests plus upgrade and streaming behavior.

Load Balancing and Health

Common policies include round robin, least connections, weighted routing, and consistent hashing. Choose based on request cost, connection lifetime, state, and endpoint capacity.

Health checks should answer whether an endpoint can serve traffic. Keep them cheap and distinguish:

  • liveness: the process cannot recover without restart;
  • readiness: the endpoint should receive new traffic;
  • startup: initialization is still allowed to continue.

Drain endpoints before shutdown and budget enough time for long requests. Session affinity hides state coupling and fails when the selected endpoint disappears; externalize session state or define that failure explicitly.

Timeouts and Retries

Set an end-to-end deadline, then allocate shorter connection, header, idle, and upstream timeouts within it. Defaults are rarely correct for every route.

Proxy retries can duplicate non-idempotent effects and multiply application retries. Retry only transient failures with safe semantics, bounded attempts, backoff, and remaining deadline. Prefer one retrying layer.

Streaming, WebSockets, server-sent events, uploads, and long polling need different idle and buffering behavior from short HTTP requests.

Buffering and Limits

Bound request headers, body size, response buffering, connections, concurrent requests, and per-client or tenant work. Reject oversized input early with a clear status. Buffering protects slow upstreams or clients but consumes memory or disk and can defeat streaming.

Apply back-pressure and load shedding before every worker and connection pool is saturated. A proxy cannot create downstream capacity.

Caching

Cache only responses whose semantics allow it. Respect Cache-Control, validators, Vary, authorization, and status behavior. Define:

  • cache key, including all representation-varying inputs;
  • freshness and stale-serving policy;
  • invalidation or versioned URLs;
  • private versus shared eligibility;
  • behavior on upstream failure;
  • stampede protection and storage limit.

Never cache personalized content in a shared cache unless identity is safely isolated in both policy and key. Cookies and authorization headers require deliberate handling.

Minimal Nginx Example

upstream app {
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    keepalive 32;
}

server {
    listen 443 ssl;
    server_name api.example.com;

    client_max_body_size 2m;

    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Request-ID $request_id;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 2s;
        proxy_read_timeout 15s;
    }
}

This is a structural example, not production TLS policy. Validate with the installed server version, configure certificates and trusted client-address handling, and test reload before activation.

Security

  • expose only intended listeners and administrative endpoints;
  • run with least privilege and protect configuration and private keys;
  • remove ambiguous or hop-by-hop headers correctly;
  • limit methods, sizes, rates, and connection use by risk;
  • keep server versions patched without relying on banner hiding;
  • prevent request smuggling through consistent HTTP parsing across hops;
  • protect management and metrics endpoints separately;
  • treat a WAF as detection and defense in depth, not a code fix.

Rate limits should use meaningful identity and cost when possible. IP-only rules harm shared users and are easy to distribute around.

Deployment and Observability

Validate configuration before atomic reload. Preserve existing connections, observe error and latency changes, and keep a known-good configuration. A successful reload does not prove routing or certificates work externally.

Measure request rate, status, latency, active and queued connections, rejected work, upstream connect and response time, retries, cache results, TLS failures, and endpoint health. Keep high-cardinality paths and identifiers out of metric labels; retain sampled or structured detail in logs and traces.

Checklist

  • Is every hop and TLS boundary documented?
  • Are forwarded headers trusted only from known proxies?
  • Do path normalization and authorization agree?
  • Are deadlines, retries, body sizes, buffers, and connections bounded?
  • Are health checks and graceful draining correct?
  • Can caching ever mix users or representations?
  • Are reload, rollback, and certificate renewal tested?
  • Can telemetry distinguish proxy, upstream, and client delay?

A proxy should make traffic safer and more operable without changing application semantics unexpectedly.