Short answer: a reverse proxy is a server that sits in front of one or more web servers and receives every request meant for them. It decides which backend handles the request, forwards it, and passes the answer back to the visitor, who only ever talks to the proxy. On the way it usually terminates HTTPS, spreads load across backends, caches, compresses and filters. Nginx, HAProxy, Caddy and Traefik are common reverse proxies; Cloudflare is one you rent.
“Reverse” is relative to the ordinary proxy server, the forward proxy, which works for clients and hides them. A reverse proxy works for servers and hides them: the visitor sees one address and one certificate, whatever runs behind it.
How does a reverse proxy work?
Follow one request to https://example.com/api/orders:
- DNS points
example.comat the reverse proxy’s IP address, not at the application. - The browser opens a TLS connection to the proxy, which presents the certificate and decrypts the request.
- The proxy matches the host name and path against its rules —
/api/goes to the API service on127.0.0.1:8000, everything else to the frontend on port 3000. - It opens its own connection to that backend, usually plain HTTP on localhost or a private network, and sends the request with headers that record the original client and scheme.
- The backend answers the proxy; the proxy may compress or cache the answer, then sends it to the browser over the TLS connection.
There are two separate connections, and the backend never sees the visitor directly. That one fact explains both the benefits and almost every reverse-proxy bug.
What is a reverse proxy used for?
| Job | What it does | Why it helps |
|---|---|---|
| TLS termination | Holds the certificates and speaks HTTPS to visitors | One place for certificates and renewals; apps stay plain HTTP |
| Routing | Sends requests to backends by host name or path | Many sites and services on one IP address and port 443 |
| Load balancing | Spreads requests across several backends, skips failed ones | More capacity, and restarts without downtime |
| Caching | Stores responses and answers repeats itself | Less work for the app, faster answers |
| Compression | gzip or Brotli for text responses | Fewer bytes on the wire |
| Protection | Rate limits, request size limits, IP rules, a WAF | Abuse is stopped before it reaches the app |
| Hiding the backend | Only the proxy is reachable from the internet | Smaller attack surface; backends can move freely |
On a single server the most common reason is the second one. A VPS has one public IP address and one port 443, and you want a blog, an API, a dashboard and a self-hosted app on it. Each app listens on its own local port; the reverse proxy is the only thing on 443 and routes by host name.
Which headers does a reverse proxy add, and why do they matter?
Because the backend’s connection comes from the proxy, it sees the proxy’s address as the client and plain HTTP as the scheme. The proxy has to pass the truth along in headers, and the app has to read them:
Host— the host name the visitor asked for. Nginx replaces it with the upstream’s name unless you pass$hoston.X-Forwarded-For— the client’s IP address, with any earlier proxies appended.X-Forwarded-Proto—httpsorhttp, the scheme the visitor used.Forwarded— the standard form of the same information (RFC 7239), less widely read by apps than the X- headers.
server {
listen 443 ssl;
server_name app.example.com;
# ssl_certificate and ssl_certificate_key lines here
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
}
}The same in Caddy, which also obtains and renews the certificate on its own and sets the forwarding headers by default:
app.example.com {
reverse_proxy 127.0.0.1:3000
}Nginx, HAProxy, Caddy or Traefik?
| Software | Strength | Configuration | Good fit |
|---|---|---|---|
| Nginx | Web server and proxy in one; static files, caching | Text config, reload without dropping connections | Most single-server setups |
| HAProxy | Load balancing, TCP and HTTP, detailed health checks and stats | Text config | Several backends, high connection counts |
| Caddy | Automatic HTTPS, short configs | Caddyfile or JSON API | Small servers, quick setups |
| Traefik | Discovers Docker and Kubernetes services from labels | Labels on containers, dynamic | Container-heavy hosts |
| Apache httpd | mod_proxy alongside existing Apache sites | Text config | Servers already running Apache |
All of them do the core job well. Pick the one whose configuration model fits how you deploy: hand-edited files (Nginx, HAProxy), as little config as possible (Caddy), or containers that announce themselves (Traefik). The step-by-step setup with Nginx and a free certificate is in our guide to an Nginx reverse proxy with SSL.
Reverse proxy vs load balancer, API gateway and CDN
- Load balancer: a reverse proxy whose main job is spreading load. Every reverse proxy that has more than one backend balances; dedicated balancers also work at the TCP level (layer 4) without reading HTTP at all.
- API gateway: a reverse proxy for APIs with authentication, quotas, request transformation and per-client keys on top.
- CDN: a reverse proxy distributed across many locations, caching close to visitors. Putting a site behind a CDN makes the CDN the first reverse proxy and your own one the second.
- Web server: Nginx and Apache are both. The same process can serve static files itself and proxy dynamic requests to an application server. Web server vs application server explains where one ends and the other begins.
What goes wrong with reverse proxies?
- 502 Bad Gateway — the proxy could not get a valid answer: the backend is down, listens on another port, or crashed mid-request. Our nginx 502 guide works through the causes.
- 504 Gateway Timeout — the backend took longer than the proxy’s read timeout, 60 seconds by default in nginx.
- Redirect loops after enabling HTTPS — the app sees plain HTTP from the proxy and redirects to HTTPS, forever. Pass
X-Forwarded-Protoand tell the framework to trust the proxy. - Every visitor has the same IP in the logs —
127.0.0.1or the proxy’s address. Same cause, same fix: forwarding headers plus a trusted-proxy setting. - 413 Request Entity Too Large — nginx caps request bodies at 1 MB by default; raise
client_max_body_sizefor uploads. - WebSockets disconnect at once — the upgrade needs
proxy_http_version 1.1plus theUpgradeandConnectionheaders. Nginx used HTTP/1.0 towards backends by default until version 1.29.7, and older distribution packages still do.
Do not let the backend bypass the proxy
A reverse proxy only protects what cannot be reached around it. Bind apps to 127.0.0.1 rather than 0.0.0.0, and check with ss -tlnp what listens on public addresses. Docker deserves a second look: a port published with -p 3000:3000 is opened in iptables by Docker itself, past UFW’s rules — publish it as -p 127.0.0.1:3000:3000 when only the proxy should reach it.
Running a reverse proxy on a VMHeaven server
A reverse proxy needs little: an Nginx or Caddy instance in front of a few apps runs comfortably on the smallest Standard KVM plan, with the apps deciding how much more you need. Every server has its own IPv4 address and root access, so ports 80 and 443 are yours, and always-on DDoS mitigation sits in front of it at the network level. A fresh Ubuntu VPS or Debian VPS is the shortest path to the setup above.
Frequently asked
What is the difference between a proxy and a reverse proxy?
A forward proxy works for clients: they send their requests through it, and websites see the proxy instead of them. A reverse proxy works for servers: visitors connect to it, and it forwards their requests to the servers behind it.
Is Nginx a reverse proxy or a web server?
Both. Nginx serves static files itself and forwards other requests to application servers with proxy_pass, often in the same configuration.
Why do my application logs show 127.0.0.1 for every visitor?
Behind a reverse proxy the application sees the proxy's connection. Pass X-Forwarded-For and X-Real-IP from the proxy and configure the application to trust those headers only from the proxy's address.