Ctrl + K
Servers19 min read

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.

Published: 2026-10-05

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 server

The 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.

PropertyForward ProxyReverse Proxy
RepresentsClientsServers or applications
Typical locationBetween clients and the InternetIn front of application servers
Common purposeClient privacy, filtering or controlled outbound accessRouting, TLS termination and application delivery
Client usually knows it existsOften yesUsually 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.

⚠️ Increasing every timeout to a very large value is not automatically better. Long timeouts can keep connections occupied for longer and may hide backend problems.

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.

⚠️ Never introduce shared caching for authenticated or personalized responses without understanding exactly which requests and responses can be safely reused between clients.

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

MistakeResultWhat to Check
Wrong upstream port502 Bad GatewayVerify that the application is listening on the configured address and port.
Incorrect trailing slashUnexpected backend URLCheck proxy_pass URI replacement behavior.
Missing Host headerApplication may generate incorrect URLsSet proxy_set_header Host when required.
Missing forwarded protocolHTTPS may appear as HTTP to the applicationPass X-Forwarded-Proto and configure trusted proxies.
No WebSocket upgradeWebSocket connection failsConfigure HTTP/1.1 and Upgrade headers.
Too-small body limitLarge uploads are rejectedCheck client_max_body_size.
Too-short timeoutLong requests failReview proxy timeout values.
Unsafe cachingPrivate content may be reusedReview 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 nginx

Testing 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 -t

If the test succeeds, the configuration can be reloaded without unnecessarily stopping the entire service.

sudo systemctl reload nginx

A 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/health

For 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.log

Avoid 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

ArchitectureAdvantagesTrade-offs
Client → ApplicationSimple architecture and fewer componentsApplication handles more infrastructure concerns and is directly exposed
Client → Nginx → ApplicationCentralized TLS, routing, headers, caching and proxy controlsAdditional configuration and another component to operate
Client → Nginx → Multiple ApplicationsCentralized public entry point and service routingMore 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.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.