Files

103 lines
4.7 KiB
Markdown
Raw Permalink Normal View History

# Nginx Proxy Manager proxy hosts
This file records the docker-1 compose setup. Once production and test run on their native VMs, the proxy hosts point at the VMs over the LAN instead: see docs/DEPLOY.md, "Native VM install (phase 10)", NPM section.
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).