Reverse proxy edge

Attach HTTPS deliberately, with narrow limits and minimal logs.

This runbook turns the public edge into a controlled handoff: the Rust app remains private, TLS terminates at Caddy or nginx, request bodies stay tiny, detailed diagnostics remain local, and abuse controls are described without pretending body caps are a WAF.

Bundle

Files in this slice

FilePurposeRule
deploy/reverse-proxy/Caddyfile.exampleCaddy HTTPS edge template with body caps, no persistent access log, one private upstream, and an explicit stock-Caddy rate-limit boundary.Copy and replace/export placeholders on the VPS; do not commit live config. Supply a separately validated trusted-edge throttle before widening.
deploy/reverse-proxy/nginx.nocturne.conf.examplenginx http-context template with TLS placeholders, body caps, media buffering stance, no access log, and memory-only sensitive-POST request buckets.Include from nginx http context after replacing placeholders; validate and tune the baseline from private-test evidence.
make reverse-proxy-template-checkDependency-free structural check that the copy-safe templates still carry the intended privacy and limit markers.Run locally before editing or shipping template changes.
Contract

Edge behavior to preserve

Option A

Caddy deployment sketch

sudo cp deploy/reverse-proxy/Caddyfile.example /etc/caddy/Caddyfile
sudoedit /etc/caddy/Caddyfile

# If keeping the env-placeholder style:
export NOCTURNE_PUBLIC_DOMAIN=members.final-domain.example
export NOCTURNE_UPSTREAM=127.0.0.1:3000

caddy fmt --diff /etc/caddy/Caddyfile
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl reload caddy

Caddy is the lower-ops option when automatic certificate management fits the VPS. The template uses request_body caps, discards access logs by default, and proxies every public route to the private app upstream.

Rate-limit boundary: stock Caddy does not provide request-rate limiting. Body caps and the app's report/media guardrails are not an edge throttle. Before widening private testing, either use the nginx baseline or separately validate a privacy-minimizing trusted-edge throttle. Do not silently add an unreviewed plugin or persistent identifying logs.
Version caveat: if the installed Caddy build does not support the request_body directive, do not remove body limits silently. Upgrade Caddy, use nginx, or enforce equal/stricter body limits at another trusted edge layer.
Option B

nginx deployment sketch

sudo cp deploy/reverse-proxy/nginx.nocturne.conf.example \
  /etc/nginx/conf.d/nocturne.conf
sudoedit /etc/nginx/conf.d/nocturne.conf
sudo nginx -t
sudo systemctl reload nginx

nginx is the explicit-control option. The example uses client_max_body_size 16k globally, 8k for /report, no persistent access log, and proxy_buffering off for /media/. It also applies bounded shared-memory source buckets only to POST /report, POST /join, POST /login, and POST /owner/login, returns 429 when exceeded, forwards no client-IP header, and loses counters on restart.

Baseline, not certification: the template starts at 6 report posts/minute with burst 3 and 12 auth posts/minute with burst 6 per source. Validate legitimate shared-NAT/browser behavior on the private test VPS, keep raw source identifiers out of evidence, and tune conservatively. This does not prove WAF or DDoS resilience.
External smoke

After proxy attachment

Prefer the scripted harness once the final origin resolves externally:

scripts/external-edge-smoke.sh https://members.final-domain.example

The manual commands below mirror the same intent when debugging a failed smoke step. The full public-edge sequence lives in External edge smoke; run smoke-member-provision --recovery-key-file ..., then External member smoke, then smoke-member-cleanup --confirm-smoke-member-cleanup to prove authenticated media and checkout paths without leaving active smoke access behind.

# Shallow public health only.
curl -fsS https://members.final-domain.example/healthz

# Confirm docs are reachable but deep diagnostics are not public routes.
curl -fsSI https://members.final-domain.example/docs/
curl -fsSI https://members.final-domain.example/doctor || true

# Oversized public report body should be rejected by the edge or app.
python3 - <<'PY' >/tmp/nocturne-report-oversize.txt
print('a' * 9000)
PY
curl -sS -o /tmp/nocturne-report-response.txt \
  -w '%{http_code}\n' \
  -X POST --data-binary @/tmp/nocturne-report-oversize.txt \
  https://members.final-domain.example/report

# Then run locally on the VPS with production env loaded.
set -a; . ./.env.production; set +a
nocturne-platform production-preflight
Stop conditions

Do not continue external testing if any are true