# Nginx server blocks and reverse proxy on Windows

> Host multiple names with server_name, serve static roots, proxy Node/Java upstreams, and forward Host/X-Forwarded headers without loops.

**Tested on**

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

## The short answer

One `server` block = one virtual host:

```nginx
server {
    listen 8082;
    server_name lab.local;

    root C:/nginx/html/lab;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
```

`server_name` matches the HTTP `Host` header. Without it, the first/`default_server` block answers. Use one block per hostname, not one port per app.

## Static site

```nginx
server {
    listen 8082;
    server_name lab.local;
    root C:/sites/lab;
    index index.html;

    location /assets/ {
        expires 7d;
        add_header Cache-Control "public";
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}
```

Use forward slashes in Windows paths. Relative `root` resolves against the Nginx working directory, so absolute paths are safer.

Test with a Host header:

```powershell
curl.exe -H "Host: lab.local" http://127.0.0.1:8082/
```

## Reverse proxy

Node/Java/Python upstream on localhost:

```nginx
upstream app_backend {
    server 127.0.0.1:3000;
    keepalive 16;
}

server {
    listen 8083;
    server_name app.local;

    location / {
        proxy_pass http://app_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 $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }
}
```

Do not append a URI to `proxy_pass` unless you intend to rewrite the path. `proxy_pass http://app_backend;` preserves the request URI; `proxy_pass http://app_backend/;` changes it.

## WebSocket

```nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

location /ws/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_read_timeout 3600s;
}
```

`map` must be in the `http` block, not inside `server`.

## Verify

```powershell
cd C:\nginx
.\nginx.exe -t
.\nginx.exe -s reload
curl.exe -I -H "Host: lab.local" http://127.0.0.1:8082/
curl.exe -I -H "Host: app.local" http://127.0.0.1:8083/
Get-Content .\logs\error.log -Tail 50
```

If every hostname returns the same site, `server_name`/Host is not matching, or another block is `default_server`. If a proxy returns 502, the upstream is not listening or the port is wrong.

> **Caution: Do not use if-in-location as routing**
>
> Nginx’s `if` inside `location` has surprising behavior. Prefer separate `location` blocks, `map`, and `try_files`. Test with `nginx -t` and real Host headers.

## Lab boundary

E1. The lab ran Nginx 1.28.0 on 8081, but did not add a second server block or a proxied upstream. The configs are the verification path for Server 2025.

## Sources

- [nginx — Server names](https://nginx.org/en/docs/http/server_names.html)
- [nginx — Reverse proxy](https://nginx.org/en/docs/http/ngx_http_proxy_module.html)
- [nginx — WebSocket proxying](https://nginx.org/en/docs/http/websocket.html)

---

Source: https://winbook.fyi/en/server/web/nginx-sites/
