Fixes deployment docs to reverse-proxy on a separate Pi, not pi-main
nginx and certbot were designed assuming they lived on the same host as the app. They don't - a dedicated proxy Pi terminates TLS and forwards plain HTTP over the LAN. Kestrel now binds 0.0.0.0 instead of localhost, the whole certbot procedure moved to the proxy Pi's side, and pi-main gets a firewall rule restricting the backend ports to just the proxy Pi's address - otherwise binding all interfaces would let anything on the LAN skip the proxy's TLS entirely.
This commit is contained in:
+44
-18
@@ -136,7 +136,11 @@ Contents (fill in real values — this file is never read by the workflow, only
|
||||
below):
|
||||
```ini
|
||||
ASPNETCORE_ENVIRONMENT=Production
|
||||
ASPNETCORE_URLS=http://localhost:<port>
|
||||
# 0.0.0.0, not localhost: the reverse proxy handling TLS for this domain runs on a SEPARATE Pi
|
||||
# (§ 1.7), so it must reach Kestrel over the LAN, not loopback. See § 1.7's firewall note — binding
|
||||
# to all interfaces means the port must be restricted to the proxy Pi's address, not left open to
|
||||
# the whole LAN.
|
||||
ASPNETCORE_URLS=http://0.0.0.0:<port>
|
||||
ConnectionStrings__DefaultConnection=Server=127.0.0.1;Port=3306;Database=SlpSoftware<Env>;Uid=<user>;Pwd=<password>
|
||||
JwtSettings__Secret=<secure-long-random-secret>
|
||||
JwtSettings__Issuer=SlpModularCms
|
||||
@@ -211,18 +215,28 @@ systemctl --user enable slpsoftware-test.service
|
||||
systemctl --user enable slpsoftware-production.service
|
||||
```
|
||||
|
||||
### 1.7 nginx Routing
|
||||
### 1.7 TLS-Terminating Reverse Proxy — On a Separate Pi, Not This One
|
||||
|
||||
The existing reverse proxy (`infrastructure-design.md` § 1, § 4) needs a server block per domain,
|
||||
routing to the matching local port. Two subsections: § 1.7.1 is the repeatable, from-scratch
|
||||
procedure for adding *any* new site or API to this Pi (this deployment's domains included, the
|
||||
first time); § 1.7.2 is simply what that procedure produced for `slpsoftware.nl` — read it as the
|
||||
worked example, not a separate step.
|
||||
**Revised from the original design**: the reverse proxy that terminates TLS for these domains runs
|
||||
on a **different, dedicated Pi** ("the proxy Pi"), not on this one ("pi-main", where
|
||||
`gitea-workflow` and the release directories from §§ 1.2–1.4 live). The proxy Pi already handles
|
||||
SSL and forwards plain HTTP to pi-main. Two consequences that change earlier sections:
|
||||
|
||||
#### 1.7.1 Adding a New Domain From Scratch
|
||||
- **Kestrel must be reachable over the LAN, not just loopback** — hence `ASPNETCORE_URLS=http://0.0.0.0:<port>`
|
||||
in § 1.5, not `http://localhost:<port>`
|
||||
- **pi-main runs no nginx and holds no certificates for these domains at all** — everything in this
|
||||
section happens **on the proxy Pi**, except the firewall step at the end, which is on pi-main
|
||||
|
||||
As in § 1.7.1–1.7.2 below: § 1.7.1 is the repeatable, from-scratch procedure for routing *any* new
|
||||
domain through the proxy Pi to *any* backend (this deployment's domains included, the first time);
|
||||
§ 1.7.2 is what that procedure produced for `slpsoftware.nl` — the worked example, not a separate
|
||||
step.
|
||||
|
||||
#### 1.7.1 Adding a New Domain From Scratch (on the proxy Pi)
|
||||
|
||||
Starting from nothing — no existing server block, no certificate — for a new domain
|
||||
`<new-domain>` routing to a local port `<local-port>`:
|
||||
`<new-domain>` that should route to `<backend-host>:<backend-port>` (a service on some other Pi
|
||||
or container on the LAN — pi-main and its two ports, in this deployment's case):
|
||||
|
||||
**Step 1 — plain HTTP block, no TLS yet.** Certbot's nginx plugin (step 2) needs a working HTTP
|
||||
server block for the domain to attach to and to answer the HTTP-01 validation challenge; asking
|
||||
@@ -233,7 +247,7 @@ server {
|
||||
listen 80;
|
||||
server_name <new-domain>;
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:<local-port>;
|
||||
proxy_pass http://<backend-host>:<backend-port>;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
@@ -245,8 +259,9 @@ Save as `/etc/nginx/sites-available/<new-domain>`, symlink it into `sites-enable
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
Confirm the domain's DNS `A`/`AAAA` record already points at this Pi before continuing — the
|
||||
HTTP-01 challenge in step 2 needs the domain to actually resolve here.
|
||||
Confirm the domain's DNS `A`/`AAAA` record already points at **the proxy Pi** (not the backend)
|
||||
before continuing — the HTTP-01 challenge in step 2 needs the domain to resolve to whichever host
|
||||
is answering on port 80, which is the proxy Pi.
|
||||
|
||||
**Step 2 — request and install the certificate.** The nginx plugin edits the file from step 1 in
|
||||
place: it adds the `listen 443 ssl` block, the certificate/key paths, and (by default) an
|
||||
@@ -260,23 +275,34 @@ Certbot's own systemd timer handles renewal automatically — nothing further to
|
||||
```bash
|
||||
curl -I https://<new-domain>/health # or whichever path this new site/API actually serves
|
||||
```
|
||||
Confirm it resolves over HTTPS with a valid certificate and reaches the expected local port.
|
||||
Confirm it resolves over HTTPS with a valid certificate and reaches the expected backend.
|
||||
|
||||
Repeat steps 1–3 once per domain. This is the same procedure regardless of whether the new domain
|
||||
is another environment for this feature, a completely different project's API, or a plain static
|
||||
site — nginx and certbot don't know or care what's listening on the local port they proxy to.
|
||||
site — nginx and certbot don't know or care what's actually listening on the backend they proxy to.
|
||||
|
||||
**Step 4 — restrict the backend port on pi-main (do this once the domain works end to end).**
|
||||
Since Kestrel now listens on `0.0.0.0:<backend-port>` (§ 1.5), anything on the LAN can reach it
|
||||
directly, bypassing the proxy Pi's TLS entirely, unless pi-main's firewall says otherwise. On
|
||||
**pi-main**:
|
||||
```bash
|
||||
sudo ufw allow from <proxy-pi-lan-ip> to any port <backend-port> proto tcp
|
||||
sudo ufw deny <backend-port>/tcp
|
||||
```
|
||||
(Or the equivalent `nftables`/`iptables` rules if `ufw` isn't what this Pi uses — the point is:
|
||||
only the proxy Pi's address may reach these ports, everything else is denied.)
|
||||
|
||||
#### 1.7.2 Current State for This Deployment
|
||||
|
||||
Running the procedure above for `test.slpsoftware.nl` and `slpsoftware.nl` produces (after
|
||||
certbot has added its blocks) server blocks equivalent to:
|
||||
Running the procedure above (on the proxy Pi) for `test.slpsoftware.nl` and `slpsoftware.nl`,
|
||||
pointing at pi-main's LAN address, produces server blocks equivalent to:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name test.slpsoftware.nl;
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5100;
|
||||
proxy_pass http://<pi-main-lan-ip>:5100;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
@@ -287,7 +313,7 @@ server {
|
||||
listen 443 ssl;
|
||||
server_name slpsoftware.nl;
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5101;
|
||||
proxy_pass http://<pi-main-lan-ip>:5101;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user