Reverse proxy vs forward proxy
A forward proxy works for the client. The browser is configured to use it, and it fetches the internet on the browser's behalf — corporate egress filtering, and most of what people mean when they search for "proxy".
A reverse proxy works for the server. Clients have no idea it exists; they resolve your hostname, it answers, and it decides what to do with the request. Your application may be on another machine, in a container, or spread over ten of them.
The distinction matters because the search results blur it. If a page about "proxy servers" is talking about unblocking websites, it is not about the thing in front of your application.
What you actually gain
TLS termination. Certificates live in one place instead of on every application server. Renewal becomes one job.
Routing. One hostname, several backends: /api to the Node service, / to WordPress, /static to object storage. The client sees one site.
Caching. The proxy answers repeat requests itself. This is the largest single performance win available to most sites, and the one most often left switched off.
Compression. Brotli or gzip applied once at the edge of your stack rather than in every application.
Rate limiting and filtering. A place to enforce limits and block traffic before it reaches code that costs money to run.
Hiding the origin. If your application server is only reachable from the proxy, attacks have to come through the door you control.
A place to change behaviour. Redirects, header rewrites and maintenance pages that do not require an application deploy.
A working nginx configuration
The minimum that is actually correct — the headers below are not optional decoration, they are what makes your application see the client rather than the proxy:
``` 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://127.0.0.1:3000;
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;
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ```
Host — without it the backend sees 127.0.0.1 and any absolute URL it builds is wrong.
X-Forwarded-For and X-Real-IP — without them every request in your application log comes from the proxy, and any per-IP logic you have silently applies to the proxy instead of the visitor.
X-Forwarded-Proto — without it an application behind TLS termination thinks it is on plain HTTP and generates http:// links, which produces the redirect loop that costs everybody an afternoon at least once.
The Upgrade pair — without it WebSockets do not work, and the failure looks like an application bug rather than a proxy one.
Caching at the proxy, briefly
nginx can cache upstream responses with proxy_cache. The mechanics are simple enough: declare a cache path, enable it in the location, and decide what gets stored for how long.
``` proxy_cache_path /var/cache/nginx keys_zone=site:50m max_size=5g inactive=24h;
location / { proxy_cache site; proxy_cache_valid 200 10m; proxy_cache_key "$scheme$request_method$host$request_uri"; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://127.0.0.1:3000; } ```
Two things decide whether this helps or hurts.
The cache key. The default includes the full URI, so ?utm_source=twitter creates a separate entry from the clean URL. A campaign can fragment your cache into thousands of copies of one page and drop the hit rate to nothing. Strip the parameters that do not change the response.
Purging. Open-source nginx has no purge mechanism; you either wait for the TTL or delete files from the cache directory by hand, on every machine. This is usually the point where "we run our own proxy" starts to hurt, because publishing a correction and waiting ten minutes for it to appear is not a workflow anyone enjoys.
What running your own actually costs
One nginx in front of one application is cheap and entirely reasonable. The cost arrives later, in pieces.
It is a single machine. A reverse proxy that is the only way in is also the only thing that has to stay up. Making it redundant means a second machine, a floating address or DNS failover, and config that is identical on both.
Certificates. Automated renewal is a solved problem until the renewal hook on the second node silently fails and you find out from a browser warning.
Cache invalidation across nodes. With two proxies you have two caches, and purging means doing it on both, reliably, from your deploy pipeline.
It is in one place. Your visitors are not. A proxy in one datacentre cannot be close to users in another country; that is a physics problem, not a configuration one.
Tuning is ongoing. Buffer sizes, timeouts, keepalive to upstreams, worker connections — every one of these is fine at your current traffic and wrong at ten times it.
When to hand the job over
A CDN is a reverse proxy that someone else operates in many locations. The functions are the same — TLS termination, caching, compression, filtering, routing — and the differences are the parts that were hard: geography, redundancy, and instant purge across every node.
The reasonable line is this. Keep your own proxy while it is doing local work: routing between services inside one machine or one cluster, running rules that depend on internal state. Hand over the public-facing side when you find yourself solving distribution problems — a second node for redundancy, cache purge across machines, visitors in another country waiting on a round trip to yours.
Most teams end up with both, and that is the sensible arrangement: a CDN facing the internet, and a small nginx inside doing the routing it is good at. What you do not want is to spend a quarter rebuilding, badly, the parts a CDN gives you on the first day.
Reverse proxy FAQ
Is a reverse proxy the same as a load balancer?
They overlap. A load balancer distributes requests across several backends; a reverse proxy also terminates TLS, caches, rewrites and filters. nginx does both, which is why the terms get used interchangeably. If the only job is spreading traffic across identical servers, "load balancer" is the more precise word.
Does a reverse proxy slow my site down?
It adds a hop, measured in single-digit milliseconds when the proxy is near the origin, and it removes far more than that when caching is on — a cached response never travels to your application at all. The case where it hurts is a proxy in a different region from both your users and your origin.
Why does my application see the proxy IP instead of the visitor?
Because the forwarded headers are missing, or because the application is not configured to trust them. Both halves are needed: the proxy sends X-Forwarded-For, and the framework has to be told which proxy addresses it may believe. Trusting the header from anywhere lets a client spoof its own IP.
Can I cache logged-in pages?
Not by default, and you usually should not want to — that is how one user sees another user's dashboard. The normal pattern is to bypass the cache when a session cookie is present and cache aggressively for anonymous visitors, which is the bulk of traffic on most sites.
How do I purge an nginx proxy cache?
Open-source nginx has no purge directive. Options are to delete the relevant files from the cache directory on every node, use the third-party purge module, or wait for the TTL. Purge on demand across machines is one of the concrete things you get by moving the caching layer to a CDN.