The Plex server stopped appearing on the plex.tv account. plex.home.keithjackson.dev showed
"not authorized"; connecting straight to the VIP + port worked.
Root cause (measured, not inferred)
customConnections held two hand-typed URLs and neither could ever complete. Because customConnectionsoverrides Plex's own connection publishing, the server advertised only dead
endpoints. Live Preferences.xml, 2026-10-04:
The server's cert is *.130072f2….plex.direct with no IP SAN — bare-IP https fails TLS for every client
https://plex.home.keithjackson.dev:32400
Nothing listens. The Gateway serves that host on 443
Not theoretical: Plex's log carries a continuous storm of CERT: incomplete TLS handshake … sslv3 alert certificate unknown from real LAN clients arriving
through the plex-l4-tcp VIP (source 10.0.1.78 = Cilium identity 1 = reserved:host, i.e. SNAT'd
LAN traffic).
With nothing completable: plex.tv published nothing (MyPlex: mapping state set to 'Mapped - Not Published') → Plex fell back to Plex Relay → the server left the account → clients
reported "not authorized".
The fix
Advertise Plex's own <dashed-ip>.<serverhash>.plex.direct form — public DNS that resolves to the
embedded address, so it points at the LAN VIP and matches the wildcard cert.
Declared as ADVERTISE_IP so git owns the value instead of a web form: the image's 40-plex-first-run writes it into customConnections on every pod start, outside the claim guard
(verified in the running image, line 127; /.firstRunComplete is on the overlay fs, not /config).
catalog/plex.yaml — ADVERTISE_IP: ${PLEX_ADVERTISE_URL}, replacing the old DEFERRAL note
clusters/base/cluster-vars.yaml — new PLEX_ADVERTISE_URL knob (base default is an explicit
non-functional placeholder; dev is decommissioned)
clusters/prod/kustomization.yaml — the prod value
apps/wave-0/plex.yaml — re-rendered with the canonical command (two added lines)
A whole-URL cluster-var rather than something derived from ${PLEX_L4_VIP} because envsubst cannot
dash an IP and the hash is certificate identity, derivable from nothing in this tree.
Gate
./infra/devcontainer/dev.sh run bash -c './ci/gate.sh' → rc 3, with spec-immutability, decisions-log, lint, l0, render-golden, l1, approval-policy all pass. The rc 3 is the
two known environmental skips: gitea-settings (no local SETTINGS_API_TOKEN) and conformance
L2/L3 (CLUSTER_AVAILABLE unset). Not green — reported as rc 3, not as pass.
What this does NOT do
It is unreconciled. Nothing here has been observed to put the server back on the account. What
is proven is that the new URL answers 200 with a valid cert and that every currently advertised URL
cannot complete.
Off-LAN access is not fixed. The advertised address is RFC1918. The WAN tcp/32400 forward
remains unverified (no NAT hairpin here, so the LAN-side probe is inconclusive, not proof).
ManualPortMappingMode="1" with an absent port is left as-is — no GitOps path exists for it
(the image supports no PLEX_PREFERENCE*), so it is an operator UI step.
PiHole resolver behaviour untested.plex.direct resolved from a pod; LAN clients resolve via
PiHole, and dnsmasq's stop-dns-rebind would reject a public answer carrying a private address.
This is the most likely way the fix still fails.
This env var will overwritecustomConnections on the next pod start, deliberately discarding
anything typed into the web form since.
⚠️ Needs /approve-override
This touches clusters/** (two files), so ci/approval-policy.sh will block it. It needs /approve-override <reason> posted as a PR review of type "Comment". I have deliberately not
posted it: CLAUDE.md §1 forbids an agent clearing this on its own PR, and the agent account holds owner, so the prohibition is a rule rather than a limit.
## The symptom
The Plex server stopped appearing on the plex.tv account. `plex.home.keithjackson.dev` showed
"not authorized"; connecting straight to the VIP + port worked.
## Root cause (measured, not inferred)
`customConnections` held two hand-typed URLs and **neither could ever complete**. Because
`customConnections` *overrides* Plex's own connection publishing, the server advertised only dead
endpoints. Live `Preferences.xml`, 2026-10-04:
```
customConnections="https://plex.home.keithjackson.dev:32400/,https://192.168.2.24:32400/"
ManualPortMappingMode="1" ManualPortMappingPort=<ABSENT>
```
| Advertised URL | Why it cannot work |
|---|---|
| `https://192.168.2.24:32400` | The server's cert is `*.130072f2….plex.direct` with **no IP SAN** — bare-IP https fails TLS for every client |
| `https://plex.home.keithjackson.dev:32400` | **Nothing listens.** The Gateway serves that host on 443 |
Not theoretical: Plex's log carries a continuous storm of
`CERT: incomplete TLS handshake … sslv3 alert certificate unknown` from **real LAN clients** arriving
through the `plex-l4-tcp` VIP (source `10.0.1.78` = Cilium identity 1 = `reserved:host`, i.e. SNAT'd
LAN traffic).
With nothing completable: plex.tv published nothing (`MyPlex: mapping state set to
'Mapped - Not Published'`) → Plex fell back to **Plex Relay** → the server left the account → clients
reported "not authorized".
## The fix
Advertise Plex's own `<dashed-ip>.<serverhash>.plex.direct` form — public DNS that resolves to the
embedded address, so it points at the LAN VIP **and** matches the wildcard cert.
Verified with full TLS validation (no `curl -k`):
```
https://192-168-2-24.130072f2….plex.direct:32400/identity -> 200, TLS verify OK
https://192.168.2.24:32400/identity -> 000, cert validation FAILED
```
Declared as `ADVERTISE_IP` so **git owns the value instead of a web form**: the image's
`40-plex-first-run` writes it into `customConnections` on every pod start, outside the claim guard
(verified in the running image, line 127; `/.firstRunComplete` is on the overlay fs, not `/config`).
- `catalog/plex.yaml` — `ADVERTISE_IP: ${PLEX_ADVERTISE_URL}`, replacing the old DEFERRAL note
- `clusters/base/cluster-vars.yaml` — new `PLEX_ADVERTISE_URL` knob (base default is an explicit
non-functional placeholder; dev is decommissioned)
- `clusters/prod/kustomization.yaml` — the prod value
- `apps/wave-0/plex.yaml` — re-rendered with the canonical command (two added lines)
A whole-URL cluster-var rather than something derived from `${PLEX_L4_VIP}` because envsubst cannot
dash an IP and the hash is certificate identity, derivable from nothing in this tree.
## Gate
`./infra/devcontainer/dev.sh run bash -c './ci/gate.sh'` → **rc 3**, with `spec-immutability`,
`decisions-log`, `lint`, `l0`, `render-golden`, `l1`, `approval-policy` all **pass**. The rc 3 is the
two known environmental skips: `gitea-settings` (no local `SETTINGS_API_TOKEN`) and `conformance`
L2/L3 (`CLUSTER_AVAILABLE` unset). **Not green — reported as rc 3, not as pass.**
## What this does NOT do
- **It is unreconciled.** Nothing here has been observed to put the server back on the account. What
is proven is that the new URL answers 200 with a valid cert and that every currently advertised URL
cannot complete.
- **Off-LAN access is not fixed.** The advertised address is RFC1918. The WAN tcp/32400 forward
remains unverified (no NAT hairpin here, so the LAN-side probe is inconclusive, not proof).
- **`ManualPortMappingMode="1"` with an absent port is left as-is** — no GitOps path exists for it
(the image supports no `PLEX_PREFERENCE*`), so it is an operator UI step.
- **PiHole resolver behaviour untested.** `plex.direct` resolved from a pod; LAN clients resolve via
PiHole, and dnsmasq's `stop-dns-rebind` would reject a public answer carrying a private address.
This is the most likely way the fix still fails.
- This env var will **overwrite** `customConnections` on the next pod start, deliberately discarding
anything typed into the web form since.
## ⚠️ Needs `/approve-override`
This touches `clusters/**` (two files), so `ci/approval-policy.sh` will **block** it. It needs
`/approve-override <reason>` posted as a PR **review** of type "Comment". I have deliberately not
posted it: CLAUDE.md §1 forbids an agent clearing this on its own PR, and the agent account holds
`owner`, so the prohibition is a rule rather than a limit.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
customConnections held two hand-typed URLs and neither could ever complete, so
Plex had nothing publishable and the server disappeared from the account:
- https://192.168.2.24:32400 -- the server's cert is
*.130072f20c334d6c8f63d0dfef87309b.plex.direct with NO IP SAN, so bare-IP
https fails TLS for every client. Measured: a continuous storm of
"CERT: incomplete TLS handshake ... certificate unknown" from real LAN
clients arriving through the plex-l4-tcp VIP.
- https://plex.home.keithjackson.dev:32400 -- nothing listens; the Gateway
serves that host on 443 (measured http=000 against 192.168.2.20:32400).
With no completable connection plex.tv published nothing ("Mapped - Not
Published"), Plex fell back to Relay, and clients reported "not authorized".
Fix: advertise <dashed-ip>.<serverhash>.plex.direct -- public DNS that resolves
to the embedded address, so it points at the LAN VIP and matches the wildcard
cert. Verified with full TLS validation (no curl -k): 200 + verify OK, where
the bare-IP form fails to connect.
Declared as ADVERTISE_IP so git owns the value instead of a web form: the
image's 40-plex-first-run writes it into customConnections on every pod start,
outside the claim guard.
Does NOT fix off-LAN access -- the advertised address is RFC1918 and the WAN
tcp/32400 forward remains unverified. ManualPortMappingMode=1 with an absent
port is left as-is (no GitOps path exists for it).
Touches clusters/** -> sensitive paths -> needs the operator's
/approve-override, deliberately not self-posted (CLAUDE.md 1).
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The symptom
The Plex server stopped appearing on the plex.tv account.
plex.home.keithjackson.devshowed"not authorized"; connecting straight to the VIP + port worked.
Root cause (measured, not inferred)
customConnectionsheld two hand-typed URLs and neither could ever complete. BecausecustomConnectionsoverrides Plex's own connection publishing, the server advertised only deadendpoints. Live
Preferences.xml, 2026-10-04:https://192.168.2.24:32400*.130072f2….plex.directwith no IP SAN — bare-IP https fails TLS for every clienthttps://plex.home.keithjackson.dev:32400Not theoretical: Plex's log carries a continuous storm of
CERT: incomplete TLS handshake … sslv3 alert certificate unknownfrom real LAN clients arrivingthrough the
plex-l4-tcpVIP (source10.0.1.78= Cilium identity 1 =reserved:host, i.e. SNAT'dLAN traffic).
With nothing completable: plex.tv published nothing (
MyPlex: mapping state set to 'Mapped - Not Published') → Plex fell back to Plex Relay → the server left the account → clientsreported "not authorized".
The fix
Advertise Plex's own
<dashed-ip>.<serverhash>.plex.directform — public DNS that resolves to theembedded address, so it points at the LAN VIP and matches the wildcard cert.
Verified with full TLS validation (no
curl -k):Declared as
ADVERTISE_IPso git owns the value instead of a web form: the image's40-plex-first-runwrites it intocustomConnectionson every pod start, outside the claim guard(verified in the running image, line 127;
/.firstRunCompleteis on the overlay fs, not/config).catalog/plex.yaml—ADVERTISE_IP: ${PLEX_ADVERTISE_URL}, replacing the old DEFERRAL noteclusters/base/cluster-vars.yaml— newPLEX_ADVERTISE_URLknob (base default is an explicitnon-functional placeholder; dev is decommissioned)
clusters/prod/kustomization.yaml— the prod valueapps/wave-0/plex.yaml— re-rendered with the canonical command (two added lines)A whole-URL cluster-var rather than something derived from
${PLEX_L4_VIP}because envsubst cannotdash an IP and the hash is certificate identity, derivable from nothing in this tree.
Gate
./infra/devcontainer/dev.sh run bash -c './ci/gate.sh'→ rc 3, withspec-immutability,decisions-log,lint,l0,render-golden,l1,approval-policyall pass. The rc 3 is thetwo known environmental skips:
gitea-settings(no localSETTINGS_API_TOKEN) andconformanceL2/L3 (
CLUSTER_AVAILABLEunset). Not green — reported as rc 3, not as pass.What this does NOT do
is proven is that the new URL answers 200 with a valid cert and that every currently advertised URL
cannot complete.
remains unverified (no NAT hairpin here, so the LAN-side probe is inconclusive, not proof).
ManualPortMappingMode="1"with an absent port is left as-is — no GitOps path exists for it(the image supports no
PLEX_PREFERENCE*), so it is an operator UI step.plex.directresolved from a pod; LAN clients resolve viaPiHole, and dnsmasq's
stop-dns-rebindwould reject a public answer carrying a private address.This is the most likely way the fix still fails.
customConnectionson the next pod start, deliberately discardinganything typed into the web form since.
⚠️ Needs
/approve-overrideThis touches
clusters/**(two files), soci/approval-policy.shwill block it. It needs/approve-override <reason>posted as a PR review of type "Comment". I have deliberately notposted it: CLAUDE.md §1 forbids an agent clearing this on its own PR, and the agent account holds
owner, so the prohibition is a rule rather than a limit.🤖 Generated with Claude Code