What Is a Reverse Proxy?
A reverse proxy is a server that sits in front of one or more backend servers and forwards client requests to them. Instead of connecting directly to an application, clients communicate with the reverse proxy, which selects an upstream server, sends the request, and returns the response.
Reverse proxies are widely used in production systems for HTTPS termination, load balancing, caching, request routing, access control, and protecting application servers from direct public exposure. NGINX, HAProxy, Envoy, and Caddy are common examples.
Although the basic concept is straightforward, production reverse proxies introduce important engineering decisions. Connection pooling, forwarded headers, health checks, timeouts, buffering, and failure handling can determine whether the proxy improves reliability or becomes a bottleneck.
Table of Contents
- How a Reverse Proxy Works
- Core Reverse Proxy Capabilities
- Reverse Proxy vs. Related Components
- NGINX Configuration Examples
- Production Design Example
- Ready-to-Use Example
- Common Mistakes
- Production Checklist
- Frequently Asked Questions
- Conclusion
How a Reverse Proxy Works
A reverse proxy accepts requests on behalf of backend servers. From the client's perspective, the proxy is the server responsible for the requested hostname, even though the application may run on a completely different machine or private network.
This separation allows infrastructure teams to change backend addresses, deploy additional application instances, or replace unhealthy servers without requiring clients to discover those changes.
Request and Response Lifecycle
Consider an application exposed at https://api.example.com. Its backend runs privately at 10.0.1.25:8000, while NGINX accepts public requests on port 443.
When a client requests GET /products/481, the reverse proxy performs the following operations:
- Accept: Receive the client connection and complete the TLS handshake.
- Match: Select the appropriate virtual host and routing configuration.
- Prepare: Set upstream headers and apply configured request policies.
- Forward: Send the HTTP request to the selected backend.
- Receive: Read the backend response.
- Process: Apply response headers, compression, or caching policies where configured.
- Return: Send the response to the original client.
The backend does not need a publicly reachable address. It only needs to accept connections from the proxy or from the private network where the proxy operates.
A reverse proxy can also expose multiple applications through one hostname. For example, requests to /api/ may reach a FastAPI service while requests to /assets/ are served directly from static storage.
Reverse Proxy vs. Forward Proxy
Both forward proxies and reverse proxies act as intermediaries, but they represent opposite sides of a network connection.
A forward proxy primarily acts on behalf of clients. A reverse proxy acts on behalf of servers.
| Property | Forward Proxy | Reverse Proxy |
|---|---|---|
| Represents | Clients | Backend servers |
| Typical Placement | Between clients and external destinations | Between external clients and applications |
| Primary Purpose | Control outbound access | Control inbound traffic |
| Common Features | Filtering, outbound policies, caching | Routing, TLS, balancing, caching |
| Typical Example | Corporate internet proxy | NGINX in front of a web application |
Neither type of proxy guarantees anonymity or security by itself. Their behavior depends on configuration, logging, network boundaries, and which parties are trusted.
Core Reverse Proxy Capabilities
A reverse proxy can perform several functions that would otherwise need to be implemented separately by backend applications. Centralizing these capabilities helps keep application servers focused on business logic.
The most useful capabilities are routing, load balancing, TLS termination, caching, and traffic control. Not every deployment requires all of them.
Load Balancing and Routing
When several application instances provide the same functionality, a reverse proxy can distribute requests across them. This improves aggregate capacity and allows unhealthy instances to be removed from rotation.
Common algorithms include round robin, least connections, and consistent hashing. Each makes different assumptions about request cost and backend capacity.
| Algorithm | How It Works | Best Fit |
|---|---|---|
| Round Robin | Distributes requests across upstreams in sequence | Similar backend capacity and request cost |
| Least Connections | Prefers backends with fewer active connections | Variable request duration |
| IP Hash | Uses client IP to select an upstream | Limited client-affinity requirements |
| Consistent Hashing | Maps a key to a backend with limited remapping | Affinity and partitioned workloads |
Load balancing works best when applications are stateless or use shared storage for session data. Depending on client affinity for correctness makes deployments and failover more difficult.
The relationship between DNS, routing layers, and backend selection is covered in DNS, Load Balancers, and Reverse Proxies.
TLS Termination
Reverse proxies commonly terminate HTTPS connections. They manage the public TLS certificate, decrypt incoming requests, and forward HTTP traffic to the backend using either HTTP or a new HTTPS connection.
This removes public certificate management from individual application instances and allows consistent TLS policies across multiple services.
However, terminating HTTPS at the proxy does not automatically encrypt the connection to the backend. If traffic crosses a network requiring additional protection, the proxy should establish a separate TLS connection to the upstream.
Certificate validation, encryption boundaries, and internal TLS are discussed in TLS, HTTPS, and Secure Communication.
Caching and Compression
A reverse proxy can cache eligible HTTP responses and serve them without contacting the backend. This is particularly effective for public, frequently requested content that changes infrequently.
For example, a product catalog may receive thousands of requests for the same product description. A short-lived proxy cache can reduce application and database work.
Cache keys must include every request dimension that changes the response, such as relevant query parameters or selected headers. Responses containing private user data must not be stored in a shared cache unless isolation is explicitly designed and enforced.
- Static assets: Images, JavaScript, CSS, and versioned resources.
- Public API responses: Frequently requested, non-personalized GET results.
- Compression: Reduce response sizes for compressible content.
- Conditional requests: Use validators such as ETag and Last-Modified where appropriate.
Caching improves performance only when freshness requirements are understood. Stale pricing, account balances, and authorization-sensitive responses can cause serious correctness or security problems.
Security and Traffic Control
A reverse proxy can reject malformed or oversized requests before they reach application servers. It may also enforce connection limits, request-rate limits, IP-based policies, and selected security headers.
These controls reduce unnecessary backend work, but they do not replace application authentication or authorization. A proxy cannot determine whether a user owns a particular order unless the required business context is available.
Reverse proxies also form an important trust boundary. Applications must distinguish client-supplied headers from forwarding metadata added by trusted infrastructure.
Reverse Proxy vs. Related Components
Reverse proxies overlap with API gateways, load balancers, and CDNs. These technologies are not mutually exclusive, and a single product can implement several roles.
The distinction matters when choosing infrastructure. A system may need only basic request forwarding, while another requires authentication policies, global edge caching, or complex service-to-service traffic management.
Reverse Proxy vs. API Gateway
An API gateway is typically a reverse proxy with additional API management capabilities. These may include authentication integration, consumer quotas, API keys, request transformations, and API-specific analytics.
A conventional reverse proxy is often sufficient for serving websites, terminating TLS, or routing requests to internal applications.
API gateways become more useful when many services need shared API policies or different consumers require distinct access controls. Adding a full API management platform solely to forward a few HTTP routes may introduce unnecessary operational complexity.
Reverse Proxy vs. Load Balancer
A load balancer distributes traffic among multiple destinations. A reverse proxy handles requests on behalf of backend servers and may also perform load balancing.
Layer 4 load balancers can route TCP or UDP traffic without interpreting HTTP semantics. Layer 7 reverse proxies understand HTTP paths, methods, headers, and responses.
Many production architectures use a managed load balancer to distribute traffic across several reverse proxy instances. Each proxy then applies HTTP routing and forwards requests to application services.
Reverse Proxy vs. CDN
A content delivery network (CDN) distributes content through geographically dispersed edge locations. Many CDNs operate as reverse proxies, but their defining advantage is global placement close to users.
A reverse proxy deployed in one region can still cache content and protect an origin. A CDN adds geographically distributed caching, network routing, and edge capabilities.
The two often work together: a CDN handles global traffic while a regional reverse proxy manages routing, connections, and policies for origin applications.
NGINX Configuration Examples
NGINX is commonly used as a reverse proxy because it supports event-driven connection handling, HTTP routing, upstream connection reuse, buffering, and configurable traffic policies.
The examples below focus on ordinary HTTP proxying. Production deployments also need certificate management, resource limits, monitoring, and configuration validation.
Basic Reverse Proxy
Suppose a FastAPI application listens on port 8000 and NGINX accepts client traffic on port 80. The following configuration forwards requests to the application and provides standard forwarding metadata.
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 2s;
proxy_read_timeout 15s;
proxy_send_timeout 15s;
}
}
The proxy_pass directive specifies the upstream destination. Forwarding the Host header allows the backend to identify the original requested hostname when that information is needed.
This example deliberately replaces X-Forwarded-For with the directly connected client address. When another trusted load balancer sits in front of NGINX, the real-IP trust configuration must be adjusted to preserve the correct client chain without accepting arbitrary spoofed addresses.
Load Balancing with NGINX
An upstream group allows several backend instances to serve the same application. NGINX Open Source supports passive failure detection, while active health checks depend on the product or additional infrastructure.
The following configuration uses least-connections selection and keeps upstream HTTP connections available for reuse.
upstream api_backend {
least_conn;
server 10.0.1.11:8000 max_fails=3 fail_timeout=10s;
server 10.0.1.12:8000 max_fails=3 fail_timeout=10s;
server 10.0.1.13:8000 max_fails=3 fail_timeout=10s;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Least-connections selection can help when request durations vary. It does not guarantee equal CPU utilization because a connection count does not fully describe the cost of each request.
Passive failure detection also has limitations. In particular, the behavior of an upstream group with only one server differs from groups with multiple servers, so health and failover assumptions should be tested against the actual deployment.
Timeouts and Connection Management
Timeouts prevent slow or unavailable upstreams from holding resources indefinitely. Connection, read, and send timeouts cover different stages of communication and should not be treated as one overall request deadline.
For example, proxy_read_timeout limits inactivity between successive reads from an upstream. It does not necessarily impose a maximum duration on a response that continuously sends data.
location /api/ {
proxy_pass http://api_backend;
proxy_connect_timeout 2s;
proxy_send_timeout 10s;
proxy_read_timeout 15s;
client_max_body_size 2m;
proxy_buffering on;
proxy_request_buffering on;
}
Buffering can protect backends from slow clients, but it consumes proxy memory or temporary disk space. Large uploads, streaming responses, and server-sent events may require different settings.
For example, server-sent events often need response buffering disabled and a read timeout appropriate to the expected heartbeat interval. A single global configuration rarely fits every route.
Production Design Example
Consider a SaaS platform with a public REST API, an administrative web interface, and several FastAPI backend instances. The application operates across two availability zones and serves an illustrative peak workload of 5,000 requests per second.
The architecture needs TLS termination, HTTP routing, connection reuse, protection against slow clients, and the ability to continue serving requests when an application instance fails.
Architecture and Request Flow
The public hostname resolves to a managed regional load balancer. Traffic is distributed across redundant NGINX instances, which route requests to backend application services on private networks.
The load balancer and reverse proxies run in separate failure domains where supported. The application fleet also spans multiple zones so that losing one instance or zone does not automatically remove all serving capacity.
- DNS: Resolve the public hostname to the regional entry point.
- Load balancer: Select a healthy reverse proxy instance.
- Reverse proxy: Apply routing, forwarding headers, limits, and timeouts.
- Application: Process the request and access required data services.
- Response: Return the result through the proxy and load balancer.
- Telemetry: Record request identifiers, latency, upstream status, and failures.
Different routes can receive different policies. For example, /api/ may use short timeouts, while a dedicated streaming endpoint uses buffering and idle-timeout settings appropriate for long-lived connections.
The design must also reserve sufficient capacity for failover. Two zones each running near maximum utilization cannot necessarily absorb a full zone outage.
Failure Handling and Observability
A reverse proxy may remain reachable even when every upstream is unhealthy. Monitoring only the proxy's TCP port or process status is therefore insufficient to establish application availability.
Proxy health, backend readiness, and end-to-end API availability should be measured separately. Failure behavior must also account for connection draining during deployments and autoscaling events.
| Failure | Expected Behavior | Important Metric |
|---|---|---|
| Backend instance fails | Route traffic to remaining healthy capacity | Upstream failures and 502 responses |
| Backend becomes slow | Apply timeouts and bounded concurrency | Upstream p95 and p99 latency |
| Proxy instance fails | Shift traffic to another proxy | Healthy proxy targets |
| Traffic spike | Limit excess traffic before saturation | Active connections and rejected requests |
| Certificate expires | Alert before expiration and renew automatically | Certificate lifetime and TLS errors |
Operational metrics should include active connections, upstream connection errors, response codes, request duration, upstream response time, and proxy resource utilization.
For broader guidance on connection limits, network overload, and routing reliability, see Networking Best Practices for Production Systems.
Ready-to-Use Example
This example runs two FastAPI application instances behind an NGINX reverse proxy using Docker Compose. NGINX distributes incoming requests between the applications and forwards request metadata.
The setup is suitable for local experimentation. It intentionally uses HTTP on localhost and does not include production certificate issuance or public network exposure.
Project Setup
Create a project directory containing the application, Dockerfile, NGINX configuration, and Compose configuration.
reverse-proxy-demo/
app.py
Dockerfile
nginx.conf
compose.yaml
The two backend containers will run the same application with different instance identifiers. Only the NGINX service will publish a port to the host.
FastAPI Backend
Save the following code as app.py. The application returns its instance identifier so request distribution can be observed.
import os
import socket
from fastapi import FastAPI, Request
app = FastAPI()
INSTANCE_ID = os.getenv(
"INSTANCE_ID",
socket.gethostname(),
)
@app.get("/health")
async def health():
return {"status": "healthy"}
@app.get("/api/hello")
async def hello(request: Request):
return {
"message": "Hello from FastAPI",
"instance": INSTANCE_ID,
"host": request.headers.get("host"),
"forwarded_proto": request.headers.get(
"x-forwarded-proto"
),
"forwarded_for": request.headers.get(
"x-forwarded-for"
),
}
The endpoint displays forwarding headers for demonstration purposes. Production code must not use unverified header values as authoritative identity or client IP information.
Next, create the Dockerfile used by both backend services.
FROM python:3.12-slim
WORKDIR /app
RUN pip install --no-cache-dir \
fastapi \
uvicorn
COPY app.py .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
NGINX and Docker Compose
The NGINX configuration defines an upstream group containing both FastAPI containers. Docker Compose provides service-name resolution inside its private network.
Save this configuration as nginx.conf.
events {
worker_connections 1024;
}
http {
upstream fastapi_backend {
server app1:8000;
server app2:8000;
}
server {
listen 80;
location / {
proxy_pass http://fastapi_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 2s;
proxy_read_timeout 10s;
proxy_send_timeout 10s;
}
}
}
The upstream uses round-robin selection by default. This configuration is intentionally static; production container platforms typically require a deliberate service-discovery or DNS refresh strategy as backend instances change.
Save the following as compose.yaml.
services:
app1:
build: .
environment:
INSTANCE_ID: app1
expose:
- "8000"
app2:
build: .
environment:
INSTANCE_ID: app2
expose:
- "8000"
nginx:
image: nginx:stable-alpine
ports:
- "127.0.0.1:8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app1
- app2
Only port 8080 is exposed on the host loopback interface. The backend services remain reachable within the Compose network without publishing their ports externally.
Testing the Proxy
Start the services, validate the NGINX configuration, and send several requests through the proxy.
docker compose up -d --build
docker compose exec nginx nginx -t
for i in $(seq 1 6); do
curl -s http://127.0.0.1:8080/api/hello
echo
done
docker compose logs nginx
The responses should identify both app1 and app2, demonstrating that NGINX forwards requests to multiple upstream instances.
To observe failure behavior, stop one backend and repeat the requests:
docker compose stop app1
for i in $(seq 1 6); do
curl -s -i http://127.0.0.1:8080/api/hello
done
docker compose start app1
Some requests may fail while NGINX detects that an upstream is unavailable. Subsequent requests can continue through the remaining instance, subject to passive failure detection and configuration behavior.
This experiment demonstrates request distribution, but it is not a complete high-availability deployment. NGINX itself remains a single container, and Docker Compose does not provide automatic multi-zone failover.
Common Mistakes
Reverse proxy configuration errors can affect every request even when backend applications are healthy. The most consequential problems usually involve trust boundaries, resource limits, and incorrect availability assumptions.
Trusting Unverified Forwarded Headers
Headers such as X-Forwarded-For and X-Forwarded-Proto are ordinary HTTP headers unless trusted infrastructure controls them. Clients may submit their own values to manipulate logging, rate limiting, or security decisions.
Configure explicit trusted proxy ranges and replace untrusted forwarding metadata at the correct boundary. Applications should accept proxy-derived client information only from known intermediaries.
Incorrect Timeout Configuration
Using very long upstream timeouts can allow slow services to consume connections and memory for extended periods. Extremely short timeouts may terminate valid requests during normal load.
Choose timeouts based on measured latency and endpoint behavior. Streaming, file uploads, and ordinary JSON APIs often need different policies.
Deploying a Single Proxy Instance
Running several application instances behind one reverse proxy does not make the complete system highly available. If the proxy fails, clients may lose access to every healthy backend.
Use redundant proxy instances or a managed high-availability entry point when availability requirements justify it. The broader failure-domain design is explained in Designing Highly Available Network Architectures.
Caching Sensitive Responses
A shared proxy cache can expose private information if responses are cached without accounting for authentication, cookies, or tenant-specific content.
Cache only responses whose sharing rules are understood. Use appropriate Cache-Control directives, avoid caching sensitive responses by default, and validate cache keys against every relevant request dimension.
Production Checklist
Before placing a reverse proxy in a critical request path, verify its security boundaries, resource limits, and recovery behavior. These checks cover the most important deployment controls.
- TLS: Enforce HTTPS and automate certificate renewal and expiration monitoring.
- Headers: Define trusted proxies and sanitize forwarded client metadata.
- Connections: Configure worker limits, upstream pools, and connection reuse.
- Timeouts: Set route-appropriate connection, read, and send timeouts.
- Payloads: Limit request sizes and configure buffering for upload and streaming workloads.
- Availability: Deploy redundant proxy capacity and verify failover and connection draining.
- Monitoring: Track upstream errors, active connections, latency percentiles, and resource saturation.
- Configuration: Validate and test configuration changes before reloading production proxies.
Frequently Asked Questions
The following questions address common reverse proxy use cases, implementation choices, performance expectations, and architectural limitations.
Why Use a Reverse Proxy?
A reverse proxy provides a controlled entry point for backend applications. It can centralize HTTPS, distribute requests, apply traffic policies, cache responses, and hide private backend addresses from direct public access.
It is especially useful when multiple application instances or services share a public hostname.
Is NGINX a Reverse Proxy?
Yes. NGINX is a web server that can also operate as a reverse proxy, HTTP load balancer, cache, and TLS termination endpoint.
Its behavior depends on configuration. Installing NGINX does not automatically enable load balancing, caching, or secure forwarding policies.
Does a Reverse Proxy Improve Performance?
It can. Connection reuse, response caching, compression, and load balancing may reduce backend work and improve response times.
However, a reverse proxy adds processing and a network hop. Incorrect buffering, connection limits, or upstream configuration can make performance worse rather than better.
Can a Reverse Proxy Hide Backend Servers?
A reverse proxy can prevent clients from directly discovering or connecting to private backend addresses when network access is correctly restricted.
However, the proxy is not a substitute for firewall rules, authentication, or backend authorization. Applications must remain protected even if routing information or internal addresses become known.
When Is a Reverse Proxy Unnecessary?
A separate reverse proxy may be unnecessary when a managed platform already provides TLS termination, routing, load balancing, and traffic controls.
For a small application with simple requirements, introducing another proxy layer may increase operational complexity without providing meaningful benefits.
Conclusion
A reverse proxy separates public client connections from backend application infrastructure. It enables centralized HTTPS termination, request routing, load balancing, caching, and traffic control while allowing applications to operate on private networks.
Its effectiveness depends on correct configuration. Trusted forwarding headers, connection limits, timeout budgets, upstream health behavior, and redundant proxy capacity are essential for reliable production operation.
Key Takeaway: A reverse proxy is more than a request-forwarding server. It is a critical traffic-management boundary that can improve security, scalability, and availability when its configuration and failure behavior are designed deliberately.
More Articles to Read
These related articles provide deeper coverage of networking fundamentals, traffic security, and the infrastructure surrounding reverse proxies.
Comments (0)