Developers

Reverse Proxies Explained: Nginx in Front of Your Apps

A reverse proxy sits in front of your applications and handles HTTPS, routing, caching and load balancing. See how it works and how to configure it with Nginx.

By Zebra Hosting Cloud

If you have ever visited a site where one domain serves a blog, an app and an API, there is a good chance a reverse proxy was involved. A reverse proxy is a server that receives requests on behalf of other servers and forwards them. It is one of the most useful building blocks in web hosting, and understanding it makes many deployment problems easier to reason about.

What a reverse proxy is and does

Forward proxy versus reverse proxy

A forward proxy sits in front of clients, for example a company gateway that employees use to reach the internet. A reverse proxy sits in front of servers: visitors talk to the proxy and never directly to your application. From the visitor's side, the proxy is the website. Behind it can be one app, ten apps, or a pool of identical servers.

What a reverse proxy does for you

  • TLS termination. The proxy handles HTTPS and certificates once, so each application can speak plain HTTP on a private local address.
  • Routing. One server and one public IP can serve many sites and apps, choosing the backend by hostname (app.example.com) or path (/api/).
  • Load balancing. Requests can be spread over several backend instances, with failed ones skipped automatically.
  • Caching and compression. The proxy can serve repeated responses and static files without bothering the application.
  • Protection. Backends are not exposed directly, so you can add rate limits, request size limits and header controls in one place.
  • Smooth deployments. You can swap or restart backends behind a stable front door.

A basic Nginx configuration

This server block forwards a hostname to an app listening on local port 3000:

server {
    listen 80;
    server_name app.example.com;

    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;
    }
}

The headers matter. Without them the application sees every request as coming from 127.0.0.1 over HTTP on the wrong host name, which breaks logging, redirects and "secure cookie" logic. Your framework must also be told to trust the proxy's headers; many have a "trusted proxies" or "trust proxy" setting. Trust them only when the app is reachable exclusively through your proxy, because otherwise a visitor could fake those headers.

Routing and load balancing

Routing several apps

You can map paths on one domain to different backends:

location /api/ {
    proxy_pass http://127.0.0.1:4000/;
}
location / {
    proxy_pass http://127.0.0.1:3000;
}

Watch the trailing slashes. With proxy_pass http://127.0.0.1:4000/; Nginx replaces the matched /api/ prefix with /, whereas without the slash it passes the original path through unchanged. This single character is behind many "works on the backend, 404 through the proxy" problems.

Load balancing

Define a group of servers with an upstream block and proxy to it by name:

upstream myapp {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
}

server {
    listen 80;
    server_name app.example.com;
    location / {
        proxy_pass http://myapp;
    }
}

By default Nginx rotates through the servers in turn, and open-source Nginx marks a server as unavailable temporarily when connections to it fail. If your app keeps session data in its own memory, either move sessions to a shared store such as Redis or use ip_hash so a visitor sticks to one backend.

WebSockets, timeouts, uploads and caching

Long-lived connections need extra headers: set proxy_http_version 1.1; together with proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection "upgrade";. Slow backends hit proxy_read_timeout, which defaults to 60 seconds and produces a 504 error when exceeded. Large uploads fail with 413 unless client_max_body_size is raised. Know the common codes: 502 (Bad Gateway) means the proxy could not talk to the backend, usually because it is down; 504 means the backend was too slow; 413 means the request body was too large.

Caching with care

Nginx can also cache backend responses with proxy_cache_path and proxy_cache, which takes load off a busy application. Be selective: cache public, identical-for-everyone pages and assets, and never cache responses that depend on a login or cookies unless you have designed the cache key to account for it. Serving one user's account page to another is a far worse problem than a slow page. Start with short cache lifetimes and add a header showing whether a response was a cache hit, so you can verify behaviour while testing.

CDNs and security

Proxies in layers: CDNs

A content delivery network is also a reverse proxy, run at many locations around the world. When a CDN sits in front of your server, your own Nginx sees the CDN's addresses, so you configure it to read the real visitor IP from the CDN's forwarding header, and only for requests that truly come from the CDN. Doing this wrongly lets visitors forge their address, which undermines rate limits and bans.

Security reminders

Bind backends to 127.0.0.1 or a private network, and keep their ports closed in the firewall; otherwise anyone can bypass the proxy and its protections. Keep the proxy updated, since it is now your public face, and hide version details with server_tokens off;.

If you are new to the idea, practise with a harmless test app on a local port first, then add the proxy in front, and compare the logs on both sides to see exactly what each one receives. Our knowledge base has further server guides.

Reverse proxies at Zebra Hosting

Cloudflare CDN is available on hosted sites, adding a global proxy layer in front of your origin; read more on the CDN page. To build your own Nginx proxy setup for several applications, a VPS is the right starting point.