
A reverse proxy is a server that sits in front of one or more backend servers, accepting every incoming client request on its own address and forwarding each one to whichever backend should actually handle it, then passing the response back to the client as if the reverse proxy had produced it itself. The client only ever connects to the reverse proxy — it never opens a direct connection to the application server, the PHP-FPM pool, or the database sitting behind it.
This is the pattern behind almost every production web stack. Nginx or HAProxy listens on port 80 and 443, terminates the incoming connection, and then makes a second, separate connection to a backend process running PHP-FPM, Node.js, Python, or another Nginx or Apache instance. The backend never needs its own public IP, its own TLS certificate, or even a port exposed to the internet — the reverse proxy is the only thing the outside world can reach.
A forward proxy sits in front of clients; a reverse proxy sits in front of servers — that single distinction explains every other difference between them. A forward proxy, like a corporate network proxy or a VPN exit node, forwards outbound requests on behalf of a client, hiding that client's identity from the servers it talks to. The server on the other end has no idea whether it's talking to the real client or the proxy relaying for it.
A reverse proxy inverts that relationship entirely: it hides the servers from the client instead. When a browser requests https://example.com, it has no way of knowing whether that request is served by one backend, five load-balanced backends, or a PHP process handed off through three internal hops — the reverse proxy is the only endpoint it can see. This is why the two are never interchangeable despite both technically "proxying" traffic.
Reverse proxies matter because they solve five practical problems that a backend server can't solve on its own: SSL termination, load balancing, hiding backend architecture, running multiple applications on a single IP, and caching. Each of these is a genuine operational need, not a theoretical benefit.
SSL termination means the reverse proxy handles the TLS handshake and certificate once, at a single point, instead of every backend process needing its own certificate and cipher configuration. Load balancing lets one reverse proxy distribute requests across several identical backend instances, so a single overloaded process doesn't take the whole application down. Hiding backend architecture means an attacker probing your public IP sees only Nginx — not the framework, the internal port, or the process manager actually serving the request. Running multiple applications on one IP means a single server with one public IP address can serve a dozen different domains or paths, each routed by the reverse proxy to a different backend on a different local port. And caching lets the proxy serve static assets and even whole pages directly, without ever bothering the backend for a request it already knows the answer to.
If two applications need to share the same domain — say a Next.js marketing site at `/` and a WordPress blog at `/blog` — a reverse proxy can route by path instead of by domain, sending each prefix to a different backend without either application knowing the other exists.
The `proxy_pass` directive is what actually turns an Nginx `server` block into a reverse proxy — everything else around it exists to preserve information the backend would otherwise lose. Without the accompanying `proxy_set_header` lines, the backend sees every request as if it originated from the reverse proxy itself, on the wrong protocol, with no idea what the client actually asked for.
`Host` tells the backend which domain the client actually requested, which matters the moment more than one site shares the same backend process. `X-Real-IP` and `X-Forwarded-For` carry the client's real IP address forward — without them every request in your application logs would show 127.0.0.1, since that's the address the backend actually sees the connection coming from. `X-Forwarded-Proto` tells the backend whether the original request was HTTP or HTTPS, which matters for frameworks that redirect based on scheme — get this wrong and you can end up in a redirect loop where the backend keeps trying to force HTTPS on a connection Nginx already terminated as TLS.
Every site on a CloudStick server is served through exactly this pattern. CloudStick's nginx-cs (Nginx 1.24.0) is the only process listening publicly on ports 80 and 443, and it decides per-request where the traffic actually goes based on the vhost configuration under `/etc/nginx-cs/vhosts.d/`.
For a PHP site, nginx-cs reverse-proxies to apache2-cs listening on `127.0.0.1:81`, which in turn hands the request off to PHP-FPM over a Unix socket — Apache is never reachable from outside the server, only from nginx-cs on localhost. For a Node.js, Next.js, or other custom application, nginx-cs skips Apache entirely and proxies straight to the app's own port with a block that looks exactly like the `proxy_pass http://127.0.0.1:3000;` example above. Either way, the public internet only ever talks to nginx-cs; the actual application process, whether Apache or Node, stays bound to localhost.
Because per-site customizations live in safe override files under `/etc/nginx-cs/extra.d/<site>.d/proxy.conf`, you can add or adjust proxy headers, timeouts, or WebSocket support for a single site without touching the generated vhost or risking a config that gets overwritten on the next change.
SSL termination happens once, at nginx-cs, rather than at every backend process it forwards to. CloudStick issues free Let's Encrypt certificates with automatic renewal, stores them at `/home/<username>/ssl/<sitename>/`, and terminates TLS 1.2 and 1.3 with OCSP stapling enabled — the backend, whether that's apache2-cs or a Node process, never sees an encrypted connection at all, only a plain HTTP request forwarded from localhost.
Caching works the same way in principle: because the reverse proxy sees every request before the backend does, it's the natural place to serve static assets, cached pages, or repeated identical responses without spending a PHP-FPM worker or a Node event-loop tick on them. This is also why the proxy headers matter beyond logging — a backend that trusts `X-Forwarded-Proto` correctly can skip issuing its own redundant HTTPS redirect, since nginx-cs already guarantees the public-facing connection was encrypted before the request ever reached it.
If you hand-edit a reverse proxy config outside of CloudStick's managed `vhosts.d/` files and drop the `proxy_set_header` lines, your application logs and any IP-based rate limiting will start recording every visitor as 127.0.0.1 — always make edits in the `extra.d/<site>.d/` override directory so they survive regeneration and keep the standard headers intact.

