Skip to content

Plex

Last Updated: 2026-08-11

Two Plex Media Server instances run in the heezy environment — one on the nebula k8s cluster, one on big-boi directly.

big-boi Plex

Property Value
Host big-boi (192.168.1.21)
Container plex (Docker Compose via Ansible big-boi role)
Image linuxserver/plex
Network mode host
Port 32400
Public URL plex.trentnielsen.me

External Access Architecture

Internet → 76.112.11.44:443 → FortiGate VIP (plex_bigboi_443)
  → swag-plex LoadBalancer (192.168.1.30:443)
  → swag-plex pod (nginx + Let's Encrypt)
  → bigboi.heezy.local:32400

Port 80 follows the same path via plex_bigboi_80 VIP → 192.168.1.30:80.

FortiGate VIPs (terraform-managed)

Resource External Internal Purpose
plex_bigboi_443 76.112.11.44:443 192.168.1.30:443 HTTPS via swag-plex
plex_bigboi_80 76.112.11.44:80 192.168.1.30:80 HTTP via swag-plex

All three plex VIPs and their WAN policies (210, 211, 214) are managed in terraform-heezy/environments/shared/heezy/firewall-objects.tf and firewall.tf.

swag-plex

swag-plex is a dedicated SWAG (LinuxServer.io nginx + certbot) instance in the heezy k8s namespace. It handles TLS termination for plex.trentnielsen.me and nebula-plex.trentnielsen.me.

Property Value
Pod swag-plex-* in heezy namespace
Manifests heezy-k8s/apps/swag-plex/
MetalLB VIP 192.168.1.30
Proxy config swag-plex-proxy-confs ConfigMap

The nginx proxy config for plex.trentnielsen.me proxies to bigboi.heezy.local:32400. The bigboi.heezy.local DNS entry is served by dnsmasq (192.168.1.29).

Set in the big-boi docker-compose via OpenBao secret production/heezy/big-boi/plex, key ADVERTISE_IP:

https://plex.trentnielsen.me/,http://192.168.1.21:32400/

Preferences.xml

Persists on the host at /opt/big-boi/plex/Library/Application Support/Plex Media Server/Preferences.xml.

Key settings written directly to this file (Plex must be stopped before editing):

Setting Value
customConnections https://plex.trentnielsen.me,http://192.168.1.21:32400
LanNetworks 192.168.1.0/24,192.168.2.0/24

customConnections is what gets published to plex.tv for remote client discovery. Without it, plex.tv has no external URL to advertise and remote access fails even if the firewall is correct.

Troubleshooting big-boi Plex

Remote access green but mobile app can't see libraries: Check what plex.tv has registered for the server's connections:

docker exec plex curl -s "https://plex.tv/api/resources?includeHttps=1&X-Plex-Token=<token>" | grep -A5 "big-boi"
Look for a non-local (local="0") connection with the correct external URL. If it shows port 443 pointing to 192.168.1.21, the VIP is misconfigured — it should point to 192.168.1.30 (swag-plex).

Preferences.xml edit procedure:

sudo docker stop plex
# edit /opt/big-boi/plex/Library/Application Support/Plex Media Server/Preferences.xml
sudo docker start plex
Plex overwrites Preferences.xml on shutdown, so always stop it before editing.


nebula k8s Plex

Property Value
Pod plex-* in heezy namespace
Image lscr.io/linuxserver/plex:latest
Manifests heezy-k8s/apps/plex/
Port 32400
MetalLB VIP 192.168.1.28
Network mode hostNetwork: true
Public URL nebula-plex.trentnielsen.me (via swag-plex) and direct TCP/32400 via plex_nebula_32400 VIP

External Access Architecture

Internet → 76.112.11.44:32400 → FortiGate VIP (plex_nebula_32400)
  → MetalLB VIP 192.168.1.28:32400
  → plex pod

HTTPS via swag-plex also works:

Internet → 76.112.11.44:443 → swag-plex → nebula-plex.trentnielsen.me proxy → 192.168.1.28:32400

Networking — hostNetwork

Plex runs with hostNetwork: true and dnsPolicy: ClusterFirstWithHostNet. This is intentional and critical.

Why: Plex auto-discovers all network interfaces on the host it runs on and registers them with plex.tv as reachable addresses. Without hostNetwork, the pod gets a 10.1.x.x cluster IP. Clients on the LAN receive that address from plex.tv, can't reach it, and fall back to the Plex relay — showing as Remote or Indirect with throttled bandwidth and forced transcoding.

With hostNetwork: true, the pod inherits the node's real LAN IP (192.168.1.x). LAN clients connect directly, showing as Direct with no relay and no forced transcode.

The pod is not pinned to a specific node. It floats freely across nebula-1 through nebula-5. The MetalLB VIP (192.168.1.28) handles routing regardless of which node it lands on.

http://76.112.11.44:32400/,http://192.168.1.28:32400/

customConnections (Preferences.xml)

Plex stores custom connection URLs in /config/Library/Application Support/Plex Media Server/Preferences.xml. This file persists on the Longhorn PVC and survives pod restarts.

Current value:

http://192.168.1.28:32400,https://nebula-plex.trentnielsen.me,http://nebula-plex.trentnielsen.me

Do not add node IPs here (e.g. 192.168.1.15:32400). Node IPs become stale when the pod reschedules. Always use the MetalLB VIP.

Storage

Volume Type Mount Purpose
plex-config Longhorn 50Gi /config Plex database, metadata, preferences
transcode emptyDir /transcode Temporary transcode scratch space
nfs-tv NFS /tv TV library
nfs-movies NFS /movies Movies library
nfs-music NFS /music Music library
dri hostPath /dev/dri /dev/dri Hardware transcoding (Intel QSV)

Token Restore (init container)

An init container (plex-token-restore) runs before Plex starts. It writes PlexOnlineToken into Preferences.xml if it's missing — this handles the case where the config PVC is wiped or restored from backup without the token.

The token is sourced from the plex-secrets k8s secret, key PLEX_TOKEN.

Troubleshooting nebula Plex

Clients show Remote or Indirect:

  1. Check hostNetwork is set:

    kubectl get pod -n heezy -l app=plex -o wide
    
    The pod IP should be 192.168.1.x, not 10.1.x.x.

  2. Check customConnections doesn't contain stale node IPs.

  3. Restart the pod to re-register with plex.tv:

    kubectl rollout restart deployment/plex -n heezy
    

Pod won't schedule:

With hostNetwork: true, port 32400 must be free on the target node. Check:

kubectl describe pod -n heezy -l app=plex | grep -A5 Events