# Node behind IIS or Nginx: WebSocket, SSE and proxy timeouts

> Reverse-proxy Node with correct Host/X-Forwarded headers, WebSocket upgrades, SSE buffering and timeouts that do not cut long connections.

**Tested on**

- Build: 26100.33650
- Edition: Standard / Datacenter
- Architecture: x64
- Evidence: E1
- Verified: 2026-10-29

## The short answer

Node listens on `127.0.0.1:3000`; IIS ARR or Nginx owns 443. The proxy must:

- preserve `Host`;
- send `X-Forwarded-For`/`X-Forwarded-Proto`;
- allow WebSocket upgrade;
- not buffer SSE;
- use a read timeout longer than the longest legitimate connection.

## Nginx

```nginx
location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 60s;
}
```

WebSocket:

```nginx
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
```

SSE:

```nginx
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
```

Without `proxy_buffering off`, events may arrive in bursts or not until the connection closes.

## IIS ARR

- Farm to `127.0.0.1:3000`.
- Preserve Host, forward XFF/XFP.
- Enable WebSocket in the ARR/farm settings if the app uses it.
- Set proxy timeout; do not disable it globally.

## Express/Node trust proxy

```js
app.set('trust proxy', 1);
```

Set this only when the app is behind a trusted proxy. Then `req.ip`, `req.protocol` and secure cookies behave correctly. `trust proxy` when Node is directly exposed lets clients spoof `X-Forwarded-*`.

## Keep-alive and timeouts

- Node server `keepAliveTimeout` should be **higher** than the proxy’s idle timeout, or the proxy may reuse a socket the app just closed.
- Proxy `proxy_read_timeout` should exceed the longest response.
- For SSE/WebSocket, a long read timeout is expected; for normal APIs, keep it short.

## Verify

```powershell
curl.exe -I https://app.example/
curl.exe -N https://app.example/events
Get-Content C:\logs\api-a\out.log -Tail 50
```

Check the app sees the public host/proto, SSE arrives incrementally, and WebSocket upgrades return `101`.

> **Caution: Do not expose Node directly and call it a reverse proxy**
>
> Binding Node to 0.0.0.0:3000 and opening 3000 is not a proxy. Bind to localhost/private and let IIS/Nginx own the public endpoint.

## Lab boundary

E1. The lab ran Nginx on 8081 but did not proxy a Node app; the configuration is the verification plan for Server 2025.

## Sources

- [nginx — WebSocket proxying](https://nginx.org/en/docs/http/websocket.html)
- [nginx — proxy module](https://nginx.org/en/docs/http/ngx_http_proxy_module.html)
- [Microsoft Learn — ARR reverse proxy](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/reverse-proxy-with-url-rewrite-v2-and-application-request-routing)

---

Source: https://windows.homes/en/server/runtimes/node-proxy/
