101 lines
4.5 KiB
Markdown
101 lines
4.5 KiB
Markdown
# Nginx Proxy Manager proxy hosts
|
|||
|
|
|
||
|
|
NPM runs on docker-1 as container `npm_app_1` on network `npm_default` (compose project in
|
||
|
|
`/home/spencerm/npm`). It terminates HTTPS for both Packet Station stacks. The stacks publish
|
||
|
|
no port. Each app joins `npm_default` through `deploy/compose.proxy.yaml` with its own alias,
|
||
|
|
and NPM forwards to that alias.
|
||
|
|
|
||
|
|
Make one proxy host per stack in the NPM web UI.
|
||
|
|
|
||
|
|
| | Production | Test |
|
||
|
|
|---|---|---|
|
||
|
|
| Domain name | `packetstation.pescoinc.biz` | `packetstation-test.pescoinc.biz` |
|
||
|
|
| Forward hostname | `packetstation-app` | `packetstation-test-app` |
|
||
|
|
| `APP_ALIAS` in the stack env file | `packetstation-app` | `packetstation-test-app` |
|
||
|
|
|
||
|
|
## Details tab
|
||
|
|
|
||
|
|
- Scheme: `http`
|
||
|
|
- Forward hostname / IP: the alias from the table above. Never an IP address: container
|
||
|
|
addresses change when a stack is recreated.
|
||
|
|
- Forward port: `8000`
|
||
|
|
- Cache assets: off. The app sets its own cache headers.
|
||
|
|
- Block common exploits: on. Confirm it does not reject uploads when the test proxy host is made
|
||
|
|
(plan open question 16).
|
||
|
|
- Websockets support: off. Live updates use server-sent events, not websockets.
|
||
|
|
- Access list: none. Sign-in is done by the app.
|
||
|
|
|
||
|
|
## SSL tab
|
||
|
|
|
||
|
|
- SSL certificate: the PESCO certificate for the name.
|
||
|
|
- Force SSL: on.
|
||
|
|
- HTTP/2 support: on.
|
||
|
|
- HSTS enabled: off. The app sends `Strict-Transport-Security` itself, along with CSP and the
|
||
|
|
other security headers. Two HSTS headers with different values would conflict.
|
||
|
|
- HSTS subdomains: off.
|
||
|
|
|
||
|
|
## Advanced tab
|
||
|
|
|
||
|
|
Paste this block as is for both hosts:
|
||
|
|
|
||
|
|
```nginx
|
||
|
|
client_max_body_size 30m;
|
||
|
|
|
||
|
|
location /api/events {
|
||
|
|
proxy_pass $forward_scheme://$server:$port;
|
||
|
|
proxy_http_version 1.1;
|
||
|
|
proxy_set_header Connection "";
|
||
|
|
proxy_buffering off;
|
||
|
|
proxy_cache off;
|
||
|
|
proxy_read_timeout 3600s;
|
||
|
|
proxy_send_timeout 3600s;
|
||
|
|
proxy_set_header Host $host;
|
||
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Why each part is there:
|
||
|
|
|
||
|
|
- `client_max_body_size 30m`: drawings, MTRs and reports are uploaded through the app. NPM's
|
||
|
|
default is 1 MB at the server level. The app enforces its own, smaller per-type limits.
|
||
|
|
- `location /api/events`: the server-sent events stream. Each open screen holds one request
|
||
|
|
open for up to an hour. Buffering must be off or events arrive late in batches. The app also
|
||
|
|
sends `X-Accel-Buffering: no` on this response.
|
||
|
|
- `proxy_http_version 1.1` with an empty `Connection` header keeps the upstream connection open
|
||
|
|
for the stream.
|
||
|
|
- The three `proxy_set_header` lines repeat what NPM's own `location /` adds. A custom location
|
||
|
|
does not inherit NPM's proxy include, so without them the app would see NPM's address as the
|
||
|
|
client and the wrong scheme.
|
||
|
|
|
||
|
|
### Why `$server` and not the alias by name
|
||
|
|
|
||
|
|
NPM writes `set $forward_scheme http; set $server "<forward hostname>"; set $port 8000;` into
|
||
|
|
each proxy host's server block, and its own `location /` uses `proxy_pass` with those variables.
|
||
|
|
When `proxy_pass` contains a variable, nginx resolves the name at request time through the
|
||
|
|
`resolver` NPM configures (Docker's DNS at 127.0.0.11). A literal
|
||
|
|
`proxy_pass http://packetstation-app:8000;` is resolved once when nginx loads its config. That
|
||
|
|
breaks in two ways: nginx refuses to load the config if the stack is down at that moment, which
|
||
|
|
takes every other proxy host down with it, and after the stack is recreated the app gets a new
|
||
|
|
address while nginx keeps sending to the old one. Using `$forward_scheme://$server:$port` keeps
|
||
|
|
the events location on the same name and the same live resolution as the rest of the host.
|
||
|
|
|
||
|
|
## Trust of X-Forwarded-*
|
||
|
|
|
||
|
|
The app trusts `X-Forwarded-For` and `X-Forwarded-Proto` only from `npm_app_1`'s own address
|
||
|
|
(`TRUSTED_PROXY_HOST=npm_app_1`), resolved at start and re-resolved every 60 seconds so an NPM
|
||
|
|
restart is followed. It does not trust the whole `npm_default` subnet (172.22.0.0/16): other
|
||
|
|
stacks share that network, and the per-address sign-in rate limit depends on the real client
|
||
|
|
address. A request from any other address has its forwarded headers ignored. uvicorn runs
|
||
|
|
without `--proxy-headers`; the app does this check itself.
|
||
|
|
|
||
|
|
## Check after saving
|
||
|
|
|
||
|
|
From a PC on the LAN:
|
||
|
|
|
||
|
|
1. `https://<name>/` loads the sign-in page with a valid certificate.
|
||
|
|
2. The response headers show one `Strict-Transport-Security` header, from the app.
|
||
|
|
3. Open a packet screen and leave it for five minutes. A change made in another browser shows up
|
||
|
|
without a reload, so the events stream is not being buffered or cut.
|
||
|
|
4. Upload a 20 MB PDF on the test stack. It is accepted (Block common exploits did not reject it).
|