There is a significant difference between an application that runs on localhost:3000 and one that is ready for public traffic. 

When it is time to go live, you need SSL, a custom domain, and a secure way to route traffic to your backend. If you are like most developers, you probably grab the top NGINX reverse proxy snippet from Stack Overflow, paste it into your server, and call it a day.

Then the weird bugs start.

Your real-time WebSockets silently disconnect every 60 seconds. You check your application logs, only to realize every single visitor’s IP address is logged as a Cloudflare server or your own local proxy’s IP. Then a user tries to upload a basic 2MB image and gets slammed with a frustrating 413 Request Entity Too Large error.

We have all been there.

Instead of relying on incomplete configuration snippets, we will show you a more complete NGINX reverse proxy setup that you can adapt to your application and server environment. This will help you prevent common WebSocket timeout problems, preserve the correct client connection information, and terminate SSL cleanly before forwarding requests to your application. 

What an NGINX Reverse Proxy Actually Does (And Why You Need One)

If you are using NGINX for the first time, you can read our NGINX configuration basics guide first. But in short, a reverse proxy sits in front of your application server (like Node, Python, or Go) and intercepts all incoming internet traffic.

This proxy handles several important tasks, such as:

SSL termination, port consolidation, and HTTP/2 to the client

When people visit a website, they expect secure https:// traffic on port 443 served via modern HTTP/2 protocols. Your application is probably served via plain HTTP/1.1 on port 3000. 

In this setup, NGINX “terminates” the SSL connection, meaning it handles the heavy lifting of decryption and HTTP/2 multiplexing, and passes plain, unencrypted traffic to your app locally. This will allow you to centralize your certificate management.

Why Put NGINX in Front of Your Application? 

You can technically configure a Node.js or Go application to listen directly on port 443 and manage its own TLS certificates. In many production environments, it is simpler to place NGINX in front of the application instead.

NGINX can handle TLS termination, connection management, request limits, compression, logging, and other web-server responsibilities while your application remains focused on handling application requests.

Reverse proxy vs forward proxy vs load balancer

When you are deciding between web servers and reverse proxies, you need to understand the terminology. A forward proxy sits between clients and external services and sends requests on the clients’ behalf. A reverse proxy sits in front of one or more backend servers and receives requests on their behalf. NGINX can also act as a load balancer by distributing requests across multiple backend instances. 

Before You Configure the Reverse Proxy

Before setting up NGINX, make sure:

  • Your application is already running.
  • You know the local port or Unix socket used by the application.
  • The application is not unnecessarily exposed on a public interface.
  • Your domain points to the server.
  • Ports 80 and 443 are reachable if you are serving the application publicly.
  • Your SSL certificate is already available if you use the HTTPS configuration shown below.

You can test a TCP-based application locally before configuring NGINX. For example:

curl http://127.0.0.1:3000

If the application does not respond locally or if you see an error message saying “Failed to connect”, then you must fix the application or service first. NGINX cannot proxy successfully to an upstream service that is not running.

Check application accessible via CURL

Example NGINX Reverse Proxy Configuration 

Most tutorials give you only the basic proxy configuration and leave you to handle features such as WebSockets, client IP forwarding, upload limits, and timeouts separately. The following example provides a more complete starting point that you can adapt to your application and server environment. 

Example HTTPS Reverse Proxy Configuration 

The map directive must be defined in the NGINX http context, outside the server block. The server block can then use the resulting $connection_upgrade variable when forwarding WebSocket requests. 

map $http_upgrade $connection_upgrade { 
    default upgrade; 
    '' close; 
} 
server {
    listen 443 ssl;
    http2 on;
    server_name myapp.com;
    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
    # Example upload limit
    client_max_body_size 50M; 
    location / {
        # Proxy pass to your application
        proxy_pass http://127.0.0.1:3000;


        # Support HTTP/1.1 and WebSocket upgrades 
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;


        # Forward the original host and client connection details
        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;


        # Connection timeouts
        proxy_read_timeout 300;
        proxy_send_timeout 300; 
    }
}

If NGINX receives traffic directly from the client, $remote_addr contains the client’s IP address. If your site sits behind another proxy or CDN such as Cloudflare, you must also configure NGINX to trust that proxy and restore the original client IP. Otherwise, $remote_addr will contain the proxy’s IP address instead.

If you use RunCloud, you can create and manage reverse proxy configurations from the RunCloud dashboard rather than manually editing generated NGINX configuration files.

Configure Multiple Upstream Application Instances 

If you run multiple instances of your application, you can define an upstream block outside the server block. NGINX can then distribute requests across those application instances.

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

You would then change your location block to use proxy_pass http://my_nodejs_app.

Reverse Proxy a Node.js App (with PM2)

When you deploy a long-running Node.js application on a VPS, you will usually run it under a process manager or service manager so that it can restart after a crash or server reboot. PM2 is a common option. 

Run the Node.js App with PM2 on Port 3000 

For an application that accepts a -p port argument, you could start it with PM2 on port 3000:

pm2 start server.js --name "my-app" -- -p 3000

The exact command depends on how your application accepts its host and port settings.

The proxy_pass + headers config for Node

Many Node.js frameworks use the Host and X-Forwarded-Proto headers when determining the original hostname and protocol. Forwarding these headers allows the application to identify that the original client connection used HTTPS even though NGINX communicates with the application over local HTTP.

Depending on your framework, you may also need to configure the application to trust the reverse proxy before it uses forwarded headers.

If you manage the application with RunCloud, you can create the reverse proxy from the RunCloud dashboard. Set the web application’s stack to Native NGINX + Custom Config, then use the predefined Proxy configuration under NGINX Config and set it to the port used by your application. 

Zero-downtime reload pattern

After changing a standard NGINX configuration, test the configuration before reloading the service:

nginx -t && systemctl reload nginx

A graceful reload applies valid configuration changes without unnecessarily interrupting active connections.

Check NGINX config syntax

If your server is managed by RunCloud, use the NGINX Config tools in the RunCloud dashboard to configure web applications. RunCloud uses its own NGINX package and configuration structure, so generic nginx service commands and paths may not match a RunCloud-managed server. 

Reverse Proxy a Python App 

Python applications handle concurrency differently than Node.js, usually relying on WSGI (Gunicorn) or ASGI (Uvicorn) servers.

Gunicorn on a Unix socket vs TCP port

Gunicorn can listen either on a local TCP port, such as 127.0.0.1:8000, or on a Unix socket. Unix sockets can be useful when NGINX and Gunicorn run on the same server because access can be controlled through filesystem permissions.

For example:

gunicorn --bind unix:/tmp/myapp.sock wsgi:app

The NGINX upstream config for Unix sockets

To point NGINX to a Unix socket instead of a TCP port, specify the socket path using NGINX’s Unix-domain socket syntax:

proxy_pass http://unix:/tmp/myapp.sock:;

Disable Proxy Buffering for Streaming Responses 

NGINX buffers proxied responses by default. For applications that depend on incremental delivery, such as Server-Sent Events or streamed application responses, buffering can delay data reaching the client.

You can disable proxy response buffering for the relevant location:

proxy_buffering off;

Reverse Proxy a Go App

Go’s net/http package can serve HTTP traffic directly without requiring a separate web server. Placing NGINX in front of a Go application can simplify TLS termination, compression, rate limiting, logging, and other HTTP-level configuration. NGINX includes gzip support, while Brotli requires Brotli module support in the NGINX build. 

The minimal proxy_pass for a Go binary

If your Go application is listening on 127.0.0.1:8080, set proxy_pass to that address:

proxy_pass http://127.0.0.1:8080;

You can then add the required forwarding headers, timeout settings, and WebSocket configuration for your application.

Configure WebSocket Proxying in NGINX 

If you are running real-time applications like chat servers or automating workflows by hosting n8n behind Docker and NGINX, you need WebSockets. But WebSockets break easily behind NGINX if you miss three critical details.

Older NGINX versions default to HTTP/1.0 when proxying HTTP requests to upstream servers. NGINX 1.29.7 and later default to HTTP/1.1.

Explicitly setting the proxy version remains useful when you need compatibility with older NGINX installations and makes the WebSocket requirement clear:

proxy_http_version 1.1;

A WebSocket connection begins as an HTTP request containing an Upgrade header. NGINX needs to pass the relevant upgrade information to the upstream application.

A reusable configuration can define the appropriate Connection value with a map:

map $http_upgrade $connection_upgrade {

    default upgrade;

    ''      close;

}


Then use:

proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection $connection_upgrade;

This sends Connection: upgrade only when an upgrade has been requested.

Increase the WebSocket Read Timeout 

The default proxy_read_timeout is 60 seconds. If the upstream server sends no data during that period, NGINX can close the connection.

For long-lived WebSocket connections, you can increase the timeout:

proxy_read_timeout 86400;

This example allows up to 24 hours between successive read operations. Applications can also use WebSocket ping/pong messages or other heartbeat traffic to prevent idle connections from reaching the timeout.

How to Set Up an NGINX Reverse Proxy in RunCloud

If your server is managed by RunCloud, you can configure the reverse proxy from the dashboard without manually editing the generated NGINX configuration files.

  1. Log in to the RunCloud dashboard and select your server.
  2. Open Web Applications and select the application you want to configure.
  3. Open Settings and change the Web Application Stack to Native NGINX + Custom Config.
  4. Open NGINX Config and select Add a New Config.
  5. Choose Predefined Config and select the Proxy configuration.
  6. Set the proxy destination to the port used by your Node.js, Python, or Go application.
  7. Configure options such as proxy buffering or WebSocket support if your application requires them.
  8. Select Run and Debug to validate the NGINX configuration.
  9. Once the configuration passes validation, select Create Config.
  10. Test the application through its public URL or with curl.

RunCloud recommends creating and editing custom NGINX configuration through the dashboard. Generated application configuration files should not be edited manually because RunCloud manages those files.

Troubleshooting Common NGINX Reverse Proxy Problems

502 Bad Gateway

A 502 error usually means NGINX cannot connect to the upstream application.

Check that the application is running and listening on the address or socket configured in proxy_pass.

For a TCP-based application, test the upstream directly:

curl http://127.0.0.1:3000

Also, confirm that the port in proxy_pass matches the port used by the application.

413 Request Entity Too Large

If uploads fail with a 413 response, increase client_max_body_size to a value appropriate for your application:

client_max_body_size 50M;

Avoid setting a much larger limit than your application actually needs.

WebSockets Disconnect or Fail to Connect

Check that WebSocket upgrade headers are passed to the application and that the proxy uses HTTP/1.1 where required for compatibility.

If connections close after periods of inactivity, review proxy_read_timeout and your application’s WebSocket heartbeat behavior.

Redirect Loops

If NGINX terminates HTTPS but the application believes the request arrived over HTTP, the application may repeatedly redirect the request to HTTPS.

Make sure NGINX forwards:

proxy_set_header X-Forwarded-Proto $scheme;

You may also need to configure your framework to trust the reverse proxy.

Incorrect Client IP Addresses

If NGINX sits directly behind the client, $remote_addr represents the client address. If another proxy, such as Cloudflare, sits in front of NGINX, configure trusted proxy ranges and the appropriate real-IP header before relying on $remote_addr.

Manage Reusable NGINX Configurations with RunCloud Templates 

Note: Use the web application’s NGINX Config tools when configuring a reverse proxy for an individual application. NGINX Templates are useful when you want to reuse and centrally manage the same configuration across multiple applications. 

If you manage the same NGINX rules across several web applications, repeatedly copying configuration files makes those configurations harder to maintain consistently.

RunCloud NGINX Templates let you create reusable configuration files in your workspace and link them to multiple web applications. You can manage these templates centrally from Settings > NGINX Templates rather than manually editing each application’s configuration over SSH.

RunCloud provides two template areas:

  • My Templates contains templates created or duplicated into your workspace.
  • Public Templates contains read-only templates provided by RunCloud and approved community members. You can duplicate a Public Template into your workspace before editing or installing it.

This provides several benefits for server management: 

  • Centralized configuration: Link a template to multiple web applications and manage the configuration from one place.
  • Reusable templates: Create your own templates or duplicate a Public Template into your workspace as a starting point.
  • Configuration testing: Test a template against an existing web application before installing it.
  • Controlled propagation: When you update a linked template, RunCloud lets you propagate the new configuration to the selected web applications.
  • Safer deployment: RunCloud checks the NGINX configuration before applying it. If validation fails, the invalid configuration is not activated.
RunCloud NGINX template dashboard for creating reverse proxy

This makes NGINX Templates useful when the same configuration needs to be maintained across several applications without manually updating each one. 

Wrapping Up

An NGINX reverse proxy gives you a central place to handle HTTPS, route requests to your application, forward request information, and configure features such as WebSocket support and upload limits.

The exact configuration depends on your application, whether another proxy or CDN sits in front of NGINX, and which NGINX version you are running. Testing each configuration change before applying it is therefore essential.

RunCloud provides dashboard tools for managing NGINX configuration without manually editing generated application configuration files. You can create reverse proxy configurations, validate them before applying them, and manage reusable configurations through NGINX Templates.

This gives you a safer way to manage reverse proxy configuration across your applications while retaining control over application-specific settings such as ports, WebSockets, buffering, and forwarded headers.

If you want to manage your NGINX reverse proxy and web applications from a central dashboard, sign up for RunCloud and deploy your next application

FAQs

What is the difference between NGINX and Apache as a reverse proxy?

Both NGINX and Apache can act as reverse proxies. NGINX uses an event-driven architecture and is commonly used as a dedicated reverse proxy in front of application servers. Apache supports reverse proxying through modules such as mod_proxy and offers several Multi-Processing Modules with different connection-handling models.
The better choice depends on your existing server stack, configuration requirements, and operational preferences.

Should I use Caddy or Traefik instead of NGINX?

Caddy and Traefik are alternatives worth considering depending on your environment. Caddy focuses heavily on simple configuration and automatic HTTPS, while Traefik is commonly used with container and service-discovery workflows.
NGINX remains a good choice when you need detailed control over proxying, routing, headers, caching, or load balancing.

Why does my WebSocket disconnect every 60 seconds behind NGINX?

NGINX uses a default proxy_read_timeout of 60 seconds. If the upstream sends no data during that period, NGINX can close the connection.
You can increase proxy_read_timeout for long-lived WebSocket connections or use application-level ping/pong messages or other heartbeat traffic to keep the connection active.

How do I pass the real client IP through NGINX and Cloudflare?

If Cloudflare sits in front of NGINX, configure NGINX’s real-IP module to trust only Cloudflare’s published proxy IP ranges and use the appropriate client-IP header. This allows NGINX to replace the Cloudflare proxy address with the original visitor address before forwarding it to your application.
Do not trust forwarded client-IP headers from arbitrary sources, because clients can otherwise supply forged values.

Can NGINX do load balancing between multiple Node.js processes?

Yes. NGINX can distribute traffic across multiple Node.js instances using an upstream block. The available load-balancing methods include the default round-robin behavior as well as methods such as least_conn and ip_hash.
Choose the method according to how your application handles sessions, connection duration, and backend capacity.

Does NGINX HTTP/2 work with the upstream backend?

The protocol used between the client and NGINX is separate from the protocol NGINX uses to communicate with an upstream application. For normal HTTP reverse proxying, NGINX can proxy requests to HTTP upstream servers independently of whether the client connected using HTTP/2 or HTTP/3.
If your application uses a protocol such as gRPC, use the corresponding NGINX proxy module and configuration rather than the standard HTTP proxy_pass configuration.

What is the cleanest way to add SSL to a Node.js app?

A common production approach is to terminate TLS at NGINX and proxy requests to the Node.js application over a local connection. This centralizes certificate management and allows the application to run without managing its own public TLS listener.
You can obtain and renew certificates with a tool such as Certbot or use your server-management platform’s SSL tools.