Network & delivery

5 min readUpdated

What is a reverse proxy?

Quick answer

A reverse proxy is a server that receives requests intended for your application, then decides what to do with them — serve from cache, block, challenge, or forward to one of your origin servers. To the visitor it looks like the website itself; your actual servers stay unpublished behind it. Nearly every CDN, WAF and load balancer is a reverse proxy with a particular emphasis, which is why one system can be all three.

01Forward proxy vs. reverse proxy

Both sit in the middle of a connection, but they act for opposite parties. A forward proxy acts for the client: your browser sends requests through it, and the destination server sees the proxy's address instead of yours. A VPN or a corporate web filter is a forward proxy.

A reverse proxy acts for the server: visitors address it directly, believing it to be the site, and it decides what reaches the real infrastructure behind. The client usually has no idea it exists, although response headers often give it away; the HTTP headers checker shows which proxy or CDN answers for any site.

Forward proxyReverse proxy
Acts forThe clientThe server
HidesThe visitor from the siteThe infrastructure from the visitor
Configured byThe user or their networkThe site operator
Typical useVPN, corporate filteringCDN, WAF, load balancer
Visitorsthe internetGuardX edgeWAF + cacheYour originunpublishedattacksdropped hereonlyclean
A reverse proxy answers on behalf of your origin: it inspects, may serve from cache, and forwards only what is left.

02What a reverse proxy does with a request

  • Terminates TLS, so certificates are managed in one place rather than on every backend
  • Inspects the request and may block, challenge or rate limit it before your application is involved
  • Serves it from cache if a fresh copy exists, without contacting the origin at all
  • Chooses a backend and forwards it, balancing load and skipping unhealthy servers
  • Rewrites headers, compresses responses and normalises anything the origin should not have to handle

03A minimal reverse proxy configuration

In nginx, a working reverse proxy is a couple of dozen lines. The details that matter are the ones people skip. nginx sends the upstream's own name as the Host header unless told otherwise, so name-based virtual hosts on the origin see the wrong site. And it does not pass the WebSocket Upgrade and Connection headers on by default, because they are hop-by-hop, so WebSocket routes need them set explicitly.

Two lines in the example exist for older versions. nginx 1.29.7 made HTTP/1.1 to the upstream the default; earlier releases need proxy_http_version 1.1 and an empty Connection header before upstream keepalive connections work.

Reverse proxy with keepalive, forwarded headers and WebSockets (nginx)
upstream app {
    server 10.0.0.10:8080;
    server 10.0.0.11:8080;
    keepalive 16;
}

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate     /etc/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/example.com/privkey.pem;

    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /ws/ {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}

04Why hiding the origin matters

Once traffic goes through a reverse proxy, your origin only ever needs to accept connections from the proxy. Everything else can be firewalled off entirely, which means an attacker who wants to reach your application has to go through the layer designed to stop them.

The protection collapses if the origin address leaks, and it leaks more often than people expect: old DNS records that were never deleted, certificate transparency logs, mail headers sent by the same host, error pages that print an internal hostname, or a subdomain that was never proxied. The guide to hiding your origin IP works through each of them.

05Locking the origin down

On a Linux origin the lock is a small firewall table: accept ports 80 and 443 only from your proxy provider's published ranges, for IPv4 and IPv6, drop them from everywhere else, and leave SSH and other ports to your existing rules. A tested nftables ruleset that also covers UDP 443 for HTTP/3 is in the origin lock-down guide; refresh its ranges whenever your provider's list changes.

Then test from an outside address that is not on the list. The connection should time out rather than reach your application, while requests through the proxy keep working.

06Keeping the visitor's real address

Because the proxy makes the connection to your origin, your application sees the proxy's address as the client; the visitor's address arrives in a header instead, usually X-Forwarded-For or the standardised Forwarded header from RFC 7239. That header must only be trusted on connections that genuinely came from your proxy, or anyone can claim any address and slip past every rate limit and geo rule you have. Which address in the list to believe is explained in the X-Forwarded-For guide, and a tested origin setup in nginx real IP.

07Troubleshooting a reverse proxy

SymptomUsual causeFix
502 Bad GatewayThe origin is down, refused the connection, or failed the TLS handshake with the proxyTest the origin from the proxy's address; see what causes a 502
504 Gateway TimeoutThe origin accepted the request but did not answer in timeFind the slow endpoint before raising timeouts; see how to fix a 504
Redirect loop after moving TLS to the proxyThe origin sees plain HTTP and keeps redirecting to HTTPSSend X-Forwarded-Proto and have the application trust it from the proxy; see redirect loops behind a proxy
Every visitor has the same IP in the logsThe real client address is not restoredConfigure the realip module or your framework's trusted-proxy setting
The wrong site or a default page appearsThe Host header carries the upstream name, not the visitor'sproxy_set_header Host $host
WebSockets disconnect or never connectUpgrade and Connection not forwarded, or the idle connection timed outPass both headers explicitly; nginx closes a proxied connection after 60 seconds without data unless proxy_read_timeout is raised or the application sends pings

Questions

Frequently asked questions

Usually yes. A load balancer is a reverse proxy whose main job is choosing which backend gets the request. Most also do TLS termination and health checking.

It adds a small amount for the extra hop, and usually removes more than it adds, through cached responses, reused origin connections and TLS terminated closer to the visitor.

Yes, nginx, HAProxy and Caddy all do it well. What you cannot self-host is capacity: a volumetric attack saturates your uplink before your own proxy ever sees the traffic.

X-Forwarded-For is the older de facto header: a comma-separated list of addresses, with the original client on the left and the most recent proxy on the right. Forwarded, standardised in RFC 7239, carries the same information plus the protocol and host in named parameters. Both are trustworthy only for the parts your own proxies added.

It can, as long as its firewall accepts web traffic only from the proxy. Better still, keep the address out of DNS history, mail headers and unproxied subdomains, so attackers have nothing to aim at even if the firewall is ever misconfigured.

Sources

Sources and verification

nginx configuration checked with nginx -t (nginx 1.22.1, self-signed test certificates), Debian 12, October 2026.

  1. 01RFC 7239: Forwarded HTTP Extension · IETF
  2. 02X-Forwarded-For header · MDN Web Docs
  3. 03Module ngx_http_realip_module · nginx.org
  4. 04Module ngx_http_proxy_module · nginx.org
  5. 05Module ngx_http_upstream_module (keepalive) · nginx.org
  6. 06WebSocket proxying · nginx.org

Last updated