Nginx Reverse Proxy Explained
A practical guide to Nginx reverse proxies, including request routing, proxy_pass, headers, HTTPS, WebSockets, caching, timeouts, load balancing and production configuration.
Nginx is often introduced as a web server, but it is also widely used as a reverse proxy. Instead of allowing clients to communicate directly with an application server, Nginx can accept incoming HTTP or HTTPS requests and forward them to another service.
This architecture is common for Node.js, Next.js, Python, PHP and other backend applications. Nginx can terminate TLS, route different domains or paths, add HTTP headers, serve static files, compress responses, cache selected resources and distribute requests across multiple application servers.
The basic idea is simple, but Nginx configuration becomes much easier to understand once you know what proxy_pass does, how request headers are handled and how Nginx decides which server and location block should process a request.
What Is a Reverse Proxy?
A reverse proxy is a server that receives requests from clients and forwards those requests to one or more backend servers. From the client's perspective, the reverse proxy is the public server. The backend application can remain behind it and does not have to be directly exposed to the Internet.
Nginx is one of the most common technologies used for this role. A browser might request https://example.com, while Nginx forwards the request internally to an application listening on another port such as 3000.
Client
-> Nginx
-> Application serverThe application server does not necessarily need to listen on the public network interface. It can listen on localhost or an internal network, while Nginx provides the public entry point.
Reverse Proxy vs Forward Proxy
The terms reverse proxy and forward proxy describe two different directions of traffic. A forward proxy represents clients when they access external servers. A reverse proxy represents servers when clients access an application.
| Property | Forward Proxy | Reverse Proxy |
|---|---|---|
| Represents | Clients | Servers or applications |
| Typical location | Between clients and the Internet | In front of application servers |
| Common purpose | Client privacy, filtering or controlled outbound access | Routing, TLS termination and application delivery |
| Client usually knows it exists | Often yes | Usually the client sees only the public server |
Nginx is commonly used as a reverse proxy in front of web applications, APIs and internal services.
Why Use Nginx as a Reverse Proxy?
Putting Nginx in front of an application provides a convenient place to handle concerns that should not necessarily be implemented inside the application itself.
- Terminate HTTPS connections.
- Forward requests to Node.js, Python, PHP or other application servers.
- Route different domains to different applications.
- Route different URL paths to different services.
- Add or modify HTTP headers.
- Serve static files efficiently.
- Enable response caching for suitable resources.
- Handle compression.
- Set request and response timeouts.
- Distribute traffic across multiple backend servers.
- Hide internal application ports from direct public access.
A Basic Nginx Reverse Proxy
A minimal reverse proxy configuration can be surprisingly small. Suppose an application is listening on port 3000 and Nginx should expose it through a public domain.
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}When a client requests a URL handled by this server block, Nginx forwards the request to the application listening on 127.0.0.1:3000.
The application still handles the actual business logic. Nginx is acting as the HTTP intermediary between the client and the application.
Understanding proxy_pass
The proxy_pass directive tells Nginx where to send a proxied request. It can point to an IP address, hostname, port or upstream group.
location / {
proxy_pass http://127.0.0.1:3000;
}The difference between proxy_pass URLs with and without a trailing path is important when proxying locations. Nginx's URI replacement behavior depends on how proxy_pass is written.
location /api/ {
proxy_pass http://127.0.0.1:3000;
}Here Nginx forwards requests while preserving the relevant URI. If a URI is specified in proxy_pass, Nginx can replace the part of the normalized request URI that matched the location.
location /api/ {
proxy_pass http://127.0.0.1:3000/internal/;
}This distinction is a frequent source of reverse-proxy bugs. When routing a subpath, always test the exact request URL that reaches the backend instead of assuming the path is preserved or replaced.
The server Block
The server block defines a virtual server. It commonly contains the listening port, domain names and locations that determine how requests are handled.
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}Multiple server blocks can coexist on the same Nginx instance. This allows one server to host or proxy several domains.
The location Block
The location directive determines how Nginx handles requests matching a particular URI. A reverse proxy commonly places proxy_pass inside a location block.
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
location /static/ {
root /var/www/site;
}
}This allows Nginx to proxy application requests while handling selected resources itself.
Proxying Different Paths to Different Services
A reverse proxy can route different URL paths to different backend services.
server {
listen 80;
server_name example.com;
location /api/ {
proxy_pass http://127.0.0.1:4000;
}
location /admin/ {
proxy_pass http://127.0.0.1:5000;
}
location / {
proxy_pass http://127.0.0.1:3000;
}
}For example, /api/ can be handled by an API service while the main application uses another process. This pattern is useful for gradually separating services without exposing every backend directly.
Passing the Original Host Header
A proxied request does not automatically mean that the backend sees exactly the same request metadata as the client sent. It is common to explicitly configure the Host header.
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
}The Host header can be important for applications that generate absolute URLs, perform domain-based routing or use host information for security checks.
Forwarding the Client IP
The application may need to know the original client's IP address. Because the direct TCP connection to the application comes from Nginx, the backend cannot necessarily determine the original client IP from the socket alone.
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}The backend framework must also be configured correctly to trust proxy headers. Otherwise, blindly trusting X-Forwarded-For can allow clients to spoof values in environments where the proxy boundary is not controlled.
The X-Forwarded-Proto Header
When Nginx terminates HTTPS and communicates with an application over HTTP internally, the backend may otherwise believe the original request used HTTP.
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;
}This can be important for redirects, secure cookies and applications that need to know whether the original request was HTTP or HTTPS.
A Common Production Proxy Configuration
server {
listen 80;
server_name 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;
}
}This is a useful starting point, but production configuration should be adapted to the application, network topology, HTTPS setup, WebSocket requirements, timeouts and security model.
HTTPS Termination at Nginx
A common architecture is to terminate TLS at Nginx. The client connects to Nginx over HTTPS, while Nginx forwards the request to an internal application over HTTP.
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/example/fullchain.pem;
ssl_certificate_key /etc/ssl/example/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}This means Nginx handles the public TLS connection. Whether the internal connection should also use TLS depends on the deployment architecture and trust boundaries.
Redirecting HTTP to HTTPS
When HTTPS is the canonical protocol, a separate HTTP server block can redirect clients to HTTPS.
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}The HTTPS server then handles the actual application proxying.
WebSocket Proxying
WebSockets require additional proxy configuration because the connection is upgraded from HTTP to the WebSocket protocol.
location /socket/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}Without the appropriate upgrade handling, WebSocket connections may fail even though ordinary HTTP requests work correctly.
Proxy Timeouts
Nginx has timeout directives that control how long it waits for different stages of communication with the upstream server.
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}The correct values depend on the application. An API with quick responses may use relatively short timeouts, while a long-running streaming or generation endpoint may require a longer read timeout.
Client Request Body Size
Applications that accept file uploads or large request bodies may need a larger client_max_body_size value.
server {
client_max_body_size 20m;
location / {
proxy_pass http://127.0.0.1:3000;
}
}If this limit is smaller than the application's expected upload size, Nginx can reject the request before it reaches the backend.
Reverse Proxy Buffering
Nginx can buffer data received from upstream applications. Buffering can be useful for ordinary HTTP responses, but some applications such as streaming endpoints may require different behavior.
location /stream/ {
proxy_pass http://127.0.0.1:3000;
proxy_buffering off;
}Whether buffering should be disabled depends on the application's response model. Do not turn it off globally without understanding the performance implications.
Caching Through Nginx
A reverse proxy can cache selected upstream responses. This can reduce backend work and improve response times for resources that are safe to cache.
location /assets/ {
proxy_pass http://127.0.0.1:3000;
proxy_cache my_cache;
proxy_cache_valid 200 10m;
}Caching dynamic application responses requires more care. Authentication state, cookies, personalized content and cache-control headers can make a response unsuitable for shared caching.
Adding Cache-Control Headers
Nginx can add response headers that influence browser and intermediary caching.
location /assets/ {
add_header Cache-Control "public, max-age=86400";
}The appropriate caching policy depends on the resource. Immutable versioned assets can often use long-lived caching, while frequently changing or private responses require different directives.
Compression at the Reverse Proxy
Nginx can compress suitable responses before sending them to clients. Compression is especially useful for text-based resources such as HTML, CSS, JavaScript and JSON.
gzip on;
gzip_types
text/plain
text/css
application/json
application/javascript
application/xml;Modern deployments may also use Brotli through an appropriate Nginx module or infrastructure layer. Compression should be configured with consideration for CPU usage and the content types being served.
Load Balancing With upstream
A reverse proxy can distribute requests across multiple backend instances using an upstream group.
upstream app_servers {
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app_servers;
}
}Nginx supports several load-balancing approaches and upstream parameters. The correct strategy depends on whether the application is stateless, whether sessions are sticky and how backend health is managed.
Why Stateless Applications Work Well Behind a Reverse Proxy
If every application instance can handle any request without depending on local in-memory state, requests can be distributed more easily across multiple servers.
Applications that store sessions only in one backend process may require sticky routing or, preferably in many architectures, an external session store that all instances can access.
Health Checks and Backend Failures
A reverse proxy can detect some upstream connection failures and stop sending traffic to an unavailable server under supported upstream behavior. However, a complete production health-check strategy may require additional infrastructure or active health monitoring.
Do not assume that an upstream being reachable at the TCP level means that the application is healthy. A process can accept connections while its database, external API or internal dependencies are failing.
Security Benefits and Limitations
Keeping an application behind Nginx can reduce direct exposure of internal ports and provides a central point for TLS, request limits and selected security headers. However, a reverse proxy is not a complete security boundary by itself.
The backend should still authenticate users, authorize operations, validate input and protect sensitive resources. Firewall rules and network configuration should also prevent unintended direct access to internal application ports when that access is not required.
CORS and Nginx
Nginx can add CORS response headers, but whether CORS should be handled by Nginx or the application depends on the architecture.
location /api/ {
proxy_pass http://127.0.0.1:4000;
add_header Access-Control-Allow-Origin "https://example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
}CORS configuration must match the actual browser security model. Avoid using a permissive wildcard origin together with credentials when the intended policy requires a specific trusted origin.
HTTP Headers in a Reverse Proxy
Nginx can set, remove or pass headers between the client and upstream application. This is useful for proxy metadata, caching, security policies and application-specific behavior.
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}Headers should be configured deliberately. Forwarding every client-supplied header to an internal service without understanding its meaning can create unexpected behavior or security issues.
Nginx and Secure Cookies
Applications using session cookies may need to know that the original request was HTTPS even when the internal Nginx-to-application connection uses HTTP. The X-Forwarded-Proto header can provide this information when the application is configured to trust the proxy correctly.
Secure cookie behavior also depends on the framework and session configuration. The reverse proxy should not be treated as a replacement for correct cookie security settings.
Nginx and Next.js
Nginx can sit in front of a Next.js server when Next.js is self-hosted. The Next.js process might listen on localhost:3000 while Nginx handles the public domain and HTTPS connection.
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/example/fullchain.pem;
ssl_certificate_key /etc/ssl/example/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}This setup is different from deploying Next.js on a platform that already provides an edge proxy, TLS termination and routing layer. Nginx is most relevant when you control the server or need its specific proxy capabilities.
Nginx as a Gateway for Multiple Applications
One Nginx server can provide a single public entry point for several internal applications.
server {
listen 443 ssl;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
location /api/ {
proxy_pass http://127.0.0.1:4000;
}
location /admin/ {
proxy_pass http://127.0.0.1:5000;
}
}This pattern can simplify DNS and TLS management because the public domain terminates at one proxy while individual services remain on internal ports.
Common proxy_pass Mistakes
| Mistake | Result | What to Check |
|---|---|---|
| Wrong upstream port | 502 Bad Gateway | Verify that the application is listening on the configured address and port. |
| Incorrect trailing slash | Unexpected backend URL | Check proxy_pass URI replacement behavior. |
| Missing Host header | Application may generate incorrect URLs | Set proxy_set_header Host when required. |
| Missing forwarded protocol | HTTPS may appear as HTTP to the application | Pass X-Forwarded-Proto and configure trusted proxies. |
| No WebSocket upgrade | WebSocket connection fails | Configure HTTP/1.1 and Upgrade headers. |
| Too-small body limit | Large uploads are rejected | Check client_max_body_size. |
| Too-short timeout | Long requests fail | Review proxy timeout values. |
| Unsafe caching | Private content may be reused | Review cache keys, cookies and response headers. |
What Does 502 Bad Gateway Mean?
A 502 Bad Gateway response commonly means that Nginx could not obtain a valid response from the configured upstream server. It does not necessarily mean that Nginx itself is broken.
Common causes include a stopped application, incorrect port, incorrect upstream hostname, connection refusal, crashed backend process or network restrictions.
curl http://127.0.0.1:3000
sudo nginx -t
sudo systemctl status nginxTesting the backend directly can quickly distinguish an application problem from a proxy configuration problem.
What Does 504 Gateway Timeout Mean?
A 504 Gateway Timeout generally indicates that Nginx did not receive an upstream response within the configured timeout period.
The cause may be a slow backend, blocked dependency, overloaded server or timeout configuration that is too short for the intended operation. Increasing the timeout can be appropriate for genuinely long operations, but it should not be used to hide a backend performance problem.
Always Test Nginx Configuration
Before reloading Nginx after a configuration change, test the configuration syntax.
sudo nginx -tIf the test succeeds, the configuration can be reloaded without unnecessarily stopping the entire service.
sudo systemctl reload nginxA syntax check does not prove that the application behind the proxy is reachable or that every routing rule behaves correctly. Functional requests should also be tested.
Use curl to Test the Proxy
curl is useful for checking status codes, headers, redirects and response behavior without relying on browser caching or application UI.
curl -I https://example.com
curl -v https://example.com/api/healthFor proxy problems, compare the response received through Nginx with the response from the backend directly. Differences can reveal header, routing, TLS or timeout problems.
Read Nginx Logs
Nginx access and error logs are essential when diagnosing reverse-proxy problems. The error log can contain useful information about upstream connection failures, timeouts and configuration behavior.
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.logAvoid adding sensitive headers, credentials or complete request bodies to custom logs simply to make debugging easier.
A Production-Oriented Reverse Proxy Example
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/example/fullchain.pem;
ssl_certificate_key /etc/ssl/example/privkey.pem;
client_max_body_size 20m;
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;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location /socket/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}This configuration illustrates several common production concerns, but it should not be copied blindly. TLS settings, timeouts, upload limits, WebSocket paths and proxy trust should match the actual application.
Nginx Reverse Proxy Best Practices
- Keep internal application ports inaccessible from the public Internet when direct access is unnecessary.
- Use HTTPS for public client connections.
- Pass the original Host and forwarding information when the application needs it.
- Configure the backend framework to trust proxy headers only across trusted proxy boundaries.
- Use appropriate request and upstream timeouts.
- Configure client_max_body_size according to actual upload requirements.
- Treat WebSocket endpoints separately when they require upgrade handling.
- Do not cache authenticated or personalized responses without a carefully designed cache policy.
- Test configuration with nginx -t before reloading.
- Use access and error logs when diagnosing proxy problems.
- Do not place production secrets in Nginx configuration files unnecessarily.
- Keep Nginx and the application separately observable so failures can be distinguished.
Nginx Reverse Proxy vs Direct Application Access
| Architecture | Advantages | Trade-offs |
|---|---|---|
| Client → Application | Simple architecture and fewer components | Application handles more infrastructure concerns and is directly exposed |
| Client → Nginx → Application | Centralized TLS, routing, headers, caching and proxy controls | Additional configuration and another component to operate |
| Client → Nginx → Multiple Applications | Centralized public entry point and service routing | More complex routing and observability requirements |
A reverse proxy adds an operational layer, so it is not required for every application. It becomes particularly useful when one server needs to expose multiple services or when TLS, routing, caching and proxy-level controls should be separated from application code.
Frequently Asked Questions
What is an Nginx reverse proxy?
An Nginx reverse proxy receives client requests and forwards them to an application or upstream server. Nginx becomes the public entry point while the application can remain behind it on an internal address or port.
What does proxy_pass do?
The proxy_pass directive specifies the upstream server to which Nginx should forward a proxied request. Its URI and trailing-slash behavior can affect how the request path is passed to the backend.
Why does Nginx return 502 Bad Gateway?
A 502 commonly means Nginx could not establish a valid connection or response from the upstream server. Check that the application is running, the upstream address and port are correct and local firewall or network rules allow the connection.
Why does Nginx return 504 Gateway Timeout?
A 504 generally means Nginx did not receive an upstream response within the configured timeout. The backend may be slow, overloaded or blocked by another dependency, or the timeout may be too short for the operation.
Do I need Nginx in front of a Node.js application?
Not necessarily. A Node.js application can listen directly for HTTP requests, but Nginx can provide useful reverse-proxy capabilities such as TLS termination, routing, caching, request limits and load balancing.
Can Nginx proxy WebSockets?
Yes. WebSocket connections require appropriate proxy configuration, typically including HTTP/1.1 and Upgrade and Connection headers.
Can Nginx handle HTTPS and proxy to HTTP?
Yes. A common deployment pattern is to terminate the public HTTPS connection at Nginx and proxy internally to an HTTP application. The application should receive appropriate forwarded-protocol information when it needs to know that the original request used HTTPS.
Helpful Nginx Tools
An Nginx Config Formatter can help keep configuration files readable, while an Nginx Config Generator can speed up the creation of common server and reverse-proxy configurations. HTTP Header Generator and CORS Header Generator tools are useful when configuring headers passed between clients, Nginx and upstream applications.
For static assets and cacheable responses, a Cache-Control Generator can help construct caching directives before they are added to an Nginx configuration or application response policy.
Conclusion
Nginx reverse proxying is fundamentally about placing Nginx between clients and application servers. Nginx receives the public request, applies the appropriate server and location rules, and forwards the request to an upstream service.
The core configuration is simple: define a server, select a location and use proxy_pass. Production setups become more involved because the proxy may also need to handle HTTPS, forwarded headers, WebSockets, request limits, timeouts, caching and multiple backend instances.
The most reliable approach is to keep the configuration explicit, understand exactly how request paths and headers are transformed, validate changes with nginx -t and test the complete request path through the proxy. Once these concepts are clear, Nginx can serve as a predictable gateway between public HTTP traffic and internal applications.