Free Ebook cover Web Servers 101: How HTTP, DNS, TLS, and Reverse Proxies Work

Web Servers 101: How HTTP, DNS, TLS, and Reverse Proxies Work

New course

13 pages

Reverse Proxies: Front Doors for Web Servers and Applications

Capítulo 10

Estimated reading time: 12 minutes

+ Exercise

What a Reverse Proxy Is (and What It Is Not)

A reverse proxy is a server that sits in front of one or more “origin” servers (your application servers, API servers, or static file servers). Clients connect to the reverse proxy, and the reverse proxy forwards each request to an appropriate upstream server, then returns the upstream’s response back to the client.

From the client’s perspective, the reverse proxy is the website. From the upstream’s perspective, the reverse proxy is the client. This “front door” position lets you centralize cross-cutting concerns—routing, load balancing, caching, compression, request filtering, and observability—without forcing every application to implement them.

A reverse proxy is different from a forward proxy. A forward proxy represents the client side (often used inside companies or by privacy tools) and proxies outbound traffic to the internet. A reverse proxy represents the server side and proxies inbound traffic to your services.

Common reverse proxy placements

  • Edge reverse proxy: public-facing, receives internet traffic and routes to internal services.
  • Internal reverse proxy: used inside a private network to route between microservices or to standardize auth and logging.
  • Ingress controller: in container orchestration environments, a reverse proxy that routes external traffic into services.

Why Put a Reverse Proxy in Front of Your Apps?

1) One public entry point for many services

Instead of exposing multiple servers and ports to the internet, you expose one (or a small set) of reverse proxies. The proxy decides where each request goes based on hostname, path, headers, or other attributes.

Example: route api.example.com to an API service, and www.example.com to a web frontend, or route /images/ to a static server while / goes to an app server.

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

2) Load balancing and high availability

A reverse proxy can distribute traffic across multiple upstream instances. If one instance is unhealthy, the proxy can stop sending traffic to it. This is a key building block for scaling and resilience.

3) Centralized security controls

Because every request passes through the proxy, it’s a natural place to enforce policies such as IP allow/deny lists, rate limiting, request size limits, and basic bot filtering. You can also standardize authentication/authorization at the edge for multiple apps (for example, requiring an auth token before forwarding to internal services).

4) Performance features

  • Caching: store responses for static assets or cacheable API results to reduce load on upstreams.
  • Compression: gzip/brotli responses to reduce bandwidth.
  • Connection management: keep a small number of long-lived connections to upstreams while handling many client connections.

5) Operational simplicity

Reverse proxies provide a single place to configure timeouts, request limits, headers, and logging. They can also enable “blue/green” or canary deployments by routing a percentage of traffic to a new version.

Core Mechanics: How a Reverse Proxy Handles a Request

At a high level, the reverse proxy performs these steps for each request:

  • Accept the client connection and read the incoming HTTP request.
  • Match the request against routing rules (host, path, method, headers).
  • Select an upstream target (one server or one instance from a pool).
  • Transform the request if needed (add headers, rewrite path, normalize).
  • Forward the request to the upstream and wait for a response.
  • Transform the response if needed (add/remove headers, compress, cache).
  • Return the response to the client.

Routing dimensions you’ll see in real configs

  • Host-based routing: api.example.com vs admin.example.com.
  • Path-based routing: /api/ vs /static/.
  • Header-based routing: route based on Accept, User-Agent, or a custom header (useful for canaries).
  • Method-based routing: treat GET differently from POST for caching or access control.

Headers You Must Understand When Using a Reverse Proxy

Once a proxy sits in the middle, the upstream server no longer sees the client directly. To preserve critical context, reverse proxies commonly add or modify these headers:

X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host

  • X-Forwarded-For carries the original client IP (and possibly a chain of proxy IPs).
  • X-Forwarded-Proto indicates the original scheme as seen by the client (often https).
  • X-Forwarded-Host indicates the original host requested by the client.

Upstreams use these to generate correct absolute URLs, enforce security rules, and log real client IPs. A common pitfall is trusting these headers from untrusted sources. Only trust them if they are set by your own proxy, and configure your app/framework to treat the proxy as a trusted hop.

Forwarded (standardized alternative)

Some setups use the standardized Forwarded header (for example, Forwarded: for=203.0.113.10;proto=https;host=example.com). Many systems still rely on the X-Forwarded-* family for compatibility.

Host header and upstream expectations

When proxying, you can either pass the original Host header through or set it to the upstream’s host. Passing the original host is common when upstreams serve multiple virtual hosts or need to generate correct links. Setting it to the upstream can be useful when upstreams expect a specific host value. The key is consistency: your routing rules, upstream server configuration, and application logic must agree on what Host means.

Practical Setup: Reverse Proxy in Front of Two Applications

This section uses NGINX as a concrete example because it’s widely deployed, but the concepts map to other reverse proxies.

Scenario

  • Public: one domain example.com
  • Upstream A: web app on 127.0.0.1:3000
  • Upstream B: API on 127.0.0.1:4000
  • Routing: /api/ goes to the API; everything else goes to the web app

Step 1: Define upstream pools

Even if you start with a single instance, define an upstream block so you can add more instances later.

upstream web_app {    server 127.0.0.1:3000;}upstream api_app {    server 127.0.0.1:4000;}

Step 2: Create a server block and route by path

server {    listen 80;    server_name example.com;    location /api/ {        proxy_pass http://api_app/;        proxy_set_header Host $host;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header X-Forwarded-Proto $scheme;        proxy_set_header X-Forwarded-Host $host;    }    location / {        proxy_pass http://web_app;        proxy_set_header Host $host;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header X-Forwarded-Proto $scheme;        proxy_set_header X-Forwarded-Host $host;    }}

Notice the subtle difference in proxy_pass usage:

  • proxy_pass http://api_app/; with a trailing slash rewrites the matched prefix (/api/) away when forwarding, so upstream receives /, /users, etc.
  • proxy_pass http://web_app; without a trailing slash forwards the full original URI as-is.

This is a frequent source of “404 behind proxy” bugs. Decide whether your upstream expects to see /api/... or just /..., then configure accordingly.

Step 3: Add timeouts and request size limits

Reverse proxies protect upstreams from slow clients and oversized uploads. Start with explicit values rather than defaults.

server {    listen 80;    server_name example.com;    client_max_body_size 10m;    proxy_connect_timeout 5s;    proxy_send_timeout 30s;    proxy_read_timeout 30s;    location /api/ {        proxy_pass http://api_app/;        proxy_set_header Host $host;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header X-Forwarded-Proto $scheme;    }    location / {        proxy_pass http://web_app;        proxy_set_header Host $host;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header X-Forwarded-Proto $scheme;    }}

Timeout tuning is workload-specific. APIs that stream responses or handle long-running jobs may need longer proxy_read_timeout. For uploads, increase client_max_body_size and ensure your upstream also accepts the size.

Load Balancing: Adding Multiple Upstream Instances

To scale horizontally, add more server entries to an upstream pool. The proxy will distribute requests across them.

upstream api_app {    server 10.0.0.11:4000;    server 10.0.0.12:4000;    server 10.0.0.13:4000;}

Load balancing strategies you’ll encounter

  • Round robin: default in many proxies; cycles through instances.
  • Least connections: sends new requests to the instance with the fewest active connections; useful when requests have variable duration.
  • IP hash / sticky sessions: attempts to keep a client routed to the same upstream instance; useful when the app stores session state in memory (though a better long-term approach is shared session storage).

Health checks and failover behavior

Reverse proxies can mark upstreams as failed after connection errors or timeouts and temporarily stop sending traffic. Some systems support active health checks (periodic probes). Even without active checks, you should define reasonable failure thresholds so a broken instance doesn’t keep receiving traffic.

Caching at the Reverse Proxy (When It Helps and When It Hurts)

Proxy caching can dramatically reduce upstream load for cacheable content. It’s most effective for:

  • Static assets (images, CSS, JS)
  • Public, cacheable API responses (for example, product catalogs)
  • Pages that are identical for many users

It can be harmful or tricky for:

  • Personalized pages (risk of serving one user’s content to another if misconfigured)
  • Responses that depend on cookies, auth headers, or other per-user signals
  • Rapidly changing data where staleness is unacceptable

Practical caching example (NGINX)

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:100m inactive=60m max_size=2g;server {    listen 80;    server_name example.com;    location /static/ {        proxy_pass http://web_app;        proxy_cache STATIC;        proxy_cache_valid 200 10m;        add_header X-Cache-Status $upstream_cache_status;        proxy_set_header Host $host;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;    }}

This caches successful (200) responses under /static/ for 10 minutes. The X-Cache-Status header helps you debug whether a response was a cache HIT or MISS.

When enabling caching, ensure your upstream sets appropriate cache headers (like Cache-Control) and that you vary cache keys correctly when responses differ by important headers.

Request and Response Rewriting

Path rewriting

Path rewriting is common when you want a clean public URL structure but your upstream expects a different base path.

Example: public requests to /api/ should be forwarded to upstream as /. The earlier proxy_pass http://api_app/; pattern achieves this. If you need more complex rewriting, you can rewrite explicitly before proxying.

Redirect rewriting

Upstreams sometimes return redirects with a Location header pointing to an internal hostname or scheme. Reverse proxies can rewrite these so clients see correct public URLs. If you see redirects to http://127.0.0.1:3000 or to an internal DNS name, you likely need to adjust upstream base URL settings or enable redirect rewriting in the proxy.

Response header normalization

You may add security headers (for example, Strict-Transport-Security, X-Content-Type-Options) at the proxy so all apps get consistent defaults. Be careful: some headers are best set by the app (for example, content-specific CSP). Decide which layer owns which headers and document it.

WebSockets and Streaming: Special Proxy Considerations

Some applications use WebSockets or other upgrade mechanisms. Reverse proxies must forward the upgrade headers and keep connections open longer.

location /ws/ {    proxy_pass http://web_app;    proxy_http_version 1.1;    proxy_set_header Upgrade $http_upgrade;    proxy_set_header Connection "upgrade";    proxy_read_timeout 3600s;}

If WebSockets disconnect quickly behind a proxy, the cause is often missing upgrade headers or an overly short read timeout.

Observability: Logging and Tracing Through a Proxy

Access logs with upstream timing

A reverse proxy can log both client-facing details and upstream performance. This helps you distinguish “slow client,” “slow proxy,” and “slow upstream.” Many proxies can log upstream response time, status, and which upstream server handled the request.

Correlation IDs

A practical pattern is to generate or propagate a request ID:

  • If the client sends X-Request-Id, pass it through.
  • If not, generate one at the proxy and add it to the request forwarded upstream.

Then ensure your upstream logs include that ID. This makes debugging multi-service requests far easier.

Common Failure Modes and How to Diagnose Them

1) 502 Bad Gateway

This usually means the proxy could not get a valid response from the upstream. Common causes:

  • Upstream process is down or not listening on the expected port
  • Firewall rules block the proxy from reaching upstream
  • Upstream crashes mid-request
  • Protocol mismatch (proxy expects HTTP but upstream speaks something else)

Check proxy error logs, then test upstream directly from the proxy host (for example, curl http://127.0.0.1:3000/).

2) 504 Gateway Timeout

The proxy connected to upstream but did not receive a response in time. Common causes:

  • Upstream is overloaded or stuck
  • Timeouts are too low for the endpoint’s normal behavior
  • Network issues between proxy and upstream

Compare proxy timeout settings with upstream processing times. If only one endpoint times out, optimize that endpoint or increase timeouts selectively for that location.

3) Wrong client IP in logs

If your app logs show the proxy IP instead of the real client IP, configure your app/framework to trust proxy headers from your reverse proxy and read X-Forwarded-For (or Forwarded). Also ensure clients cannot spoof these headers by reaching the app directly; keep upstreams private.

4) Infinite redirects or wrong scheme

A classic symptom is redirect loops between HTTP and HTTPS, or redirects to the wrong scheme. This happens when the upstream believes the original request was HTTP while the client used HTTPS at the edge. Ensure the proxy sets X-Forwarded-Proto correctly and that the upstream is configured to respect it.

5) 404s due to path rewriting

If an endpoint works when hitting the upstream directly but returns 404 through the proxy, inspect how the proxy forwards the URI. Pay close attention to trailing slashes and whether the /api prefix is preserved or removed.

Reverse Proxy Patterns You’ll Use in Real Systems

Serving static files directly, proxying dynamic requests

Reverse proxies can serve static assets from disk efficiently and only proxy dynamic requests to the app. This reduces load and simplifies scaling.

Blue/green and canary routing

You can run two versions of a service and route traffic based on a header, cookie, or percentage split. A simple approach is to route internal testers by a header (for example, X-Canary: 1) to the new upstream pool while everyone else stays on stable.

API gateway-like behavior

When a reverse proxy starts handling auth, rate limiting, request validation, and routing across many backend services, it begins to resemble an API gateway. The difference is often product scope and features, but the underlying “front door” concept is the same.

Now answer the exercise about the content:

In a reverse proxy setup, what is a key benefit of having one public entry point in front of multiple upstream services?

You are right! Congratulations, now go to the next page

You missed! Try again.

A reverse proxy provides a single public front door and then uses routing rules (for example, host or path) to forward requests to the appropriate upstream, avoiding exposing many internal services directly.

Next chapter

Load Balancing Patterns: Distributing Traffic and Handling Failures

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.