docs: document NPM dedicated-port jail convention and zsh $path footgun
Two real gotchas from today's goddy->gringo Laravel migration: every jail on an NPM-fronted host needs a dedicated listen port (8000+last-octet), not just the shared port 80, or NPM cutover silently can't reach it even though every direct/Host-header test passes. Separately, zsh ties a variable literally named `path` to $PATH itself - overwriting it broke every subsequent command in a config-editing script with no clear error. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,45 @@ curl -H 'Host: <domain>' http://<jail-ip>/
|
||||
Only switch to testing via the host's `127.0.0.1` (or the public domain)
|
||||
once the `sites-enabled` vhost for this specific site actually exists.
|
||||
|
||||
## Every jail needs a dedicated NPM-facing port, not just `listen 80`
|
||||
|
||||
**Confirmed real gap, 2026-08-07**: on jail hosts fronted by a shared
|
||||
Nginx Proxy Manager (NPM) instance, every existing jail's host-level
|
||||
nginx vhost has **two** `listen` directives — the shared port 80 (for
|
||||
same-host/Host-header testing) **and a dedicated port unique to that
|
||||
jail**, following the convention `8000 + <jail's last IP octet>` (e.g. a
|
||||
jail at `10.20.0.43` gets `listen 8043;`, one at `.50` gets `listen
|
||||
8050;`). This dedicated port is what NPM's own proxy-host config actually
|
||||
targets when routing the public domain to this jail host — **NPM does
|
||||
not rely on Host-header-based routing through the shared port 80 for
|
||||
this fleet**, it connects to a specific port per site.
|
||||
|
||||
This was missed migrating 5 Laravel apps from a Linux host to a FreeBSD
|
||||
jail host (gringo) in one session: all 5 new vhosts were created with
|
||||
only `listen 80;`, matching the *shared* port but missing the dedicated
|
||||
one entirely. Every functional test the delegate ran (raw jail IP, and
|
||||
via the shared port 80 with the right `Host:` header) passed cleanly —
|
||||
the gap was invisible until specifically checked against sibling jails'
|
||||
configs, at which point every other jail on the host turned out to
|
||||
follow the two-port pattern without exception. **Before considering any
|
||||
new jail's nginx vhost complete, diff its `listen` directives against at
|
||||
least one working sibling jail's config on the same host** — don't just
|
||||
confirm the site loads via a manual test, confirm the *routing surface*
|
||||
matches the established convention, since NPM (external, not scriptable
|
||||
from this sandbox) is what will actually determine reachability once
|
||||
cut over, and a working manual test doesn't prove NPM's real path works.
|
||||
|
||||
```bash
|
||||
# find the convention on any host that uses it
|
||||
for f in /usr/local/etc/nginx/sites-available/*.conf; do
|
||||
echo "=== $(basename $f) ==="; grep -E '^\s*listen' "$f"
|
||||
done
|
||||
```
|
||||
|
||||
If a host doesn't show this pattern on ANY existing jail, it likely
|
||||
doesn't use per-jail dedicated ports — don't assume the convention
|
||||
applies fleet-wide without checking the specific host first.
|
||||
|
||||
## Stock stack includes Valkey, not just nginx/php/MariaDB
|
||||
|
||||
The fleet's standard per-host stack (see `docs/server-funky.md`) is
|
||||
|
||||
@@ -60,6 +60,26 @@ decode step per real filesystem boundary crossed is enough.
|
||||
special characters — check for quoting corruption before looking
|
||||
elsewhere.
|
||||
|
||||
## zsh footgun: never name a variable `path` (or a few other reserved names)
|
||||
|
||||
If the remote host's shell is zsh (several boxes in this fleet default to
|
||||
it), assigning a plain string to a variable literally named `path` does
|
||||
not create a normal local variable — zsh ties the lowercase `path` array
|
||||
to `$PATH` (colon-joined ⇄ array elements) automatically. Overwriting it
|
||||
with a single string **replaces `$PATH` itself**, and every subsequent
|
||||
command in that shell (including ones on later lines of the same script)
|
||||
fails with `command not found: cp` / `command not found: awk` / etc. —
|
||||
confirmed live 2026-08-07 editing nginx configs over SSH into a zsh
|
||||
remote shell, where a loop using `path="/usr/local/etc/nginx/..."` broke
|
||||
every command after the assignment with no explanation beyond
|
||||
`command not found`. **Rename to anything else** (`cfgpath`, `target`,
|
||||
`dest`, ...) — this isn't specific to nginx configs, it'll happen with
|
||||
any script that happens to pick `path` as a variable name on a zsh
|
||||
remote. A few other zsh-reserved lowercase names exist for the same
|
||||
reason (`status`, `pipestatus`) — when in doubt, avoid single common
|
||||
English words as variable names in scripts meant to run under an
|
||||
unknown/remote shell, or check `typeset -p <name>` first if unsure.
|
||||
|
||||
## What NOT to do
|
||||
|
||||
Don't respond to a quoting failure by adding more layers of escaping
|
||||
|
||||
Reference in New Issue
Block a user