Plex Media Server

Plex Media Server organises your own movies, series, music and photos and streams them to Plex apps on phones, TVs, browsers and set-top boxes, at home and remotely, with transcoding on the fly.

The Plex app announcement TBA

Plex Media Server is a community application from the tebbiworld repository. Documentation and source can be found on Github

The module runs the official plexinc/pms-docker image on a NethServer 8 node. The media libraries live on storage that the node mounts — an iSCSI disk, NFS shares from a NAS or from the Samba file server — and are passed read-only into the container. The module ships no Plex software: the node pulls the image published by Plex, Inc. at install and update time.

  • One Traefik host name with Let's Encrypt, or optionally host networking for LAN discovery
  • Any number of media paths, mounted read-only at the same path inside the container, so an existing Plex library can be migrated without re-scanning
  • A pre-start guard that refuses to run Plex while a media mount is missing, so a library is never emptied by a scan against an empty directory
  • One-time claim token to link the server to a Plex account, optional /dev/dri pass-through for hardware transcoding
  • Backup of the Plex database and metadata through the NS8 backup

A Plex account is required to link the server. Hardware transcoding additionally requires Plex Pass.

A single rootless container.

Component Purpose
plex Plex Media Server, port 32400, pinned image docker.io/plexinc/pms-docker:<version>
volume plex-config database, metadata, thumbnails (in the NS8 backup, Cache and Logs excluded)
volume plex-transcode transcoder scratch space (not in the backup)
media paths host paths, bind-mounted read-only at the same path (not in the backup — back up the storage itself)

The Plex version is pinned in the module; a new module release through the Software Center brings a new Plex version. The self-updating public and beta image tags are deliberately not used.

  • Bridge (default): only the web/API port is published on the node loopback and fronted by Traefik with TLS. Clients find the server through plex.tv and the custom access URL https://<host>:443, which the module sets automatically.
  • Host: the container shares the node network. Plex binds 32400 and the GDM (32410–32414/udp) and DLNA (1900/udp, 32469) discovery ports on the node IP, so clients on the LAN discover the server by themselves. Only one host-mode instance per node.

On the NethServer 8 cluster leader:

add-module ghcr.io/tebbiworld/plex:1.0.0 1

Configure it, replacing the host name and the media paths (see Preparing the media storage first):

api-cli run module/plex1/configure-module --data '{
  "host": "plex.example.org",
  "lets_encrypt": true,
  "http2https": true,
  "network_mode": "bridge",
  "media_paths": ["/media/plex", "/media/music"],
  "media_require_mount": true,
  "timezone": "Europe/Berlin"
}'

Then get a claim token at https://www.plex.tv/claim, paste it into the Claim token field of the settings page and save within four minutes. The server is linked to your account at the next start; the settings page shows whether it is linked. Open https://<host>/web, check Settings → Network → Custom server access URLs contains https://<host>:443 and Secure connections is Preferred, then add libraries pointing at the media paths.

The container cannot mount iSCSI or NFS itself; the node does, exactly like a plain Linux Plex host would. Three things matter, plus the special case where the share lives on the NS8 file server itself.

NethServer 8 nodes on Rocky Linux 9 ship with nfs-utils in the base image: the NFS client works out of the box (mount -t nfs), and the NFS server is present but disabled (nfs-server.service). Nothing needs to be installed for NFS. The iSCSI initiator (iscsi-initiator-utils) is not part of the image and must be installed. firewalld is active on every node; it does not affect outgoing client mounts, only an NFS server running on the node (see below).

NS8 nodes run SELinux in enforcing mode. A plain mount is unlabeled_t and the container is denied every read, even of world-readable files. The context= mount option labels the whole mount without touching a single file (unlike chcon -R, which does not work on NFS and would relabel terabytes):

# /etc/fstab
UUID=<uuid>  /media/plex  ext4  _netdev,nofail,errors=remount-ro,context="system_u:object_r:container_file_t:s0"  0 0
nas:/export/music  /media/music  nfs  ro,_netdev,nofail,x-systemd.automount,context="system_u:object_r:container_file_t:s0"  0 0

For an iSCSI disk, once:

dnf install -y iscsi-initiator-utils
iscsiadm -m discovery -t st -p <portal-ip>
iscsiadm -m node -T <iqn> -p <portal-ip> -o update -n node.startup -v automatic
systemctl enable --now iscsid

_netdev and nofail because the device or server only exists after the network is up: a missing mount does not block the boot, it only blocks the start of Plex (see the guard below). mount -a applies the new entries; findmnt /media/music must show the mount with the context option.

The container is rootless: files owned by any user other than the module user appear as nobody inside it. Plex therefore only sees files with the other read bit set.

  • Local disk (iSCSI): once chmod -R o+rX /media/plex. Read-only is all Plex needs; the mount is read-only inside the container anyway.
  • NFS exported with all_squash,anonuid=…: nothing to do — the NFS server checks every access on its side as the configured user, the permission bits do not matter.
  • NFS with root_squash only: the permission bits apply as for a local disk.

Plex stores absolute paths in its database. Mount the storage at the same path as on the previous host and a migrated library keeps working without a re-scan.

If the media already lives in a share of the NS8 File server (Samba account provider), the files are in that module's shares volume on the node that runs it, for example /home/samba1/.local/share/containers/storage/volumes/shares/_data/music. Whether Plex runs on another node or on that very node, the share is made available through a small NFS export on the Samba node — see the two cases below. The export is read-only and squashes every client to the Samba module user, so the Samba permissions and ACLs never need to be touched.

On the node that runs the Samba module (here samba1), find the two ids the export needs:

id -u samba1                                                             # uid of the module user, e.g. 1008
stat -c %g /home/samba1/.local/share/containers/storage/volumes/shares/_data/music   # gid owning the share directory

Plex on another node

# /etc/exports on the Samba node — one line per share, <plex-node-ip> = the node that runs Plex
/home/samba1/.local/share/containers/storage/volumes/shares/_data/music  <plex-node-ip>(ro,sync,no_subtree_check,all_squash,anonuid=<uid of samba1>,anongid=<gid of the directory>)
systemctl enable --now nfs-server
exportfs -ra && exportfs -v
firewall-cmd --permanent --add-service=nfs --add-service=mountd --add-service=rpc-bind && firewall-cmd --reload

On the Plex node the share is mounted as in step 1, for example:

# /etc/fstab on the Plex node
<samba-node>:/home/samba1/.local/share/containers/storage/volumes/shares/_data/music  /media/music  nfs  ro,_netdev,nofail,x-systemd.automount,context="system_u:object_r:container_file_t:s0"  0 0

Plex on the node that holds the share

When the Plex module is installed on the Samba node itself, the files are already on the local disk — but the Plex container still cannot use them directly:

  • the Samba module's home is 0700, so the Plex module user cannot traverse the path (this could be fixed with a traversal ACL, setfacl -m u:plex1:x on every path component);
  • the share directories and files carry Samba's modes, typically drwxrws— and -rw-rw—-, owned by sub-UIDs of the Samba container. Inside the rootless Plex container they appear as nobody and are not readable, even with the traversal ACL in place (verified). Making them world-readable would mean changing modes inside the Samba volume, which Samba's create mask reverts for new files.

Therefore use the same NFS export, just towards the node itself. NFS is already installed, and firewalld does not filter the loopback interface, so no firewall rule is needed:

# /etc/exports on the node (which is both Samba and Plex node)
/home/samba1/.local/share/containers/storage/volumes/shares/_data/music  127.0.0.1(ro,sync,no_subtree_check,all_squash,anonuid=<uid of samba1>,anongid=<gid of the directory>)
systemctl enable --now nfs-server
exportfs -ra && exportfs -v
# /etc/fstab on the same node
127.0.0.1:/home/samba1/.local/share/containers/storage/volumes/shares/_data/music  /media/music  nfs  ro,_netdev,nofail,x-systemd.automount,context="system_u:object_r:container_file_t:s0"  0 0

A loopback NFS mount is fully supported by the Linux NFS client; the server reads the files as the Samba module user (which owns the whole volume), the Plex container sees a normal read-only NFS mount at /media/music and the Samba side stays untouched. Enter /media/music as media path in the module, not the volume path.

In both cases the export is not managed by NS8: it survives module updates, but /etc/exports (and, for the remote case, the firewall rule) are yours to maintain, and the exported path changes if the Samba module is ever moved to another node or re-created under a new instance name.

Setting Description
Host name FQDN under which Plex is published. Also set as the Plex custom server access URL.
Let's Encrypt certificate Requests a certificate for the host name. Requires that ports 80 and 443 of this name reach this node.
HTTP to HTTPS redirection Redirects HTTP to HTTPS.
Network mode Bridge (Traefik only) or Host (32400 and LAN discovery on the node IP, one instance per node).
Media paths Absolute node paths, one per line. No spaces, commas or colons. Each is mounted read-only at the same path in the container.
Require mounted filesystem Pre-start guard: every media path must exist, be readable by the module user and be on a mounted filesystem other than /. Disable only for media on the system disk.
Claim token One-time token from plex.tv/claim, valid four minutes. Not stored after use.
Hardware transcoding Passes /dev/dri into the container. Requires Plex Pass and, once on the node, usermod -aG render,video plex1.
Timezone TZ for the container, for example Europe/Berlin.
  1. Install the module and configure the host name, without media paths, without linking.
  2. Stop Plex on the old host. For iSCSI, log the LUN out there — a LUN can only be attached to one initiator. Mount the storage on the node, check the permissions.
  3. Copy the old Plex Media Server directory (Linux package: /var/lib/plexmediaserver/Library/Application Support/Plex Media Server) into the plex-config volume:
runagent -m plex1 podman volume inspect plex-config --format '{{.Mountpoint}}'
systemctl --user -M plex1@ stop plex.service
rsync -a "old:/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/" \
      "/home/plex1/.local/share/containers/storage/volumes/plex-config/_data/Library/Application Support/Plex Media Server/"
chown -R plex1:plex1 /home/plex1/.local/share/containers/storage/volumes/plex-config/_data
systemctl --user -M plex1@ start plex.service
  1. Enter the media paths in the settings and save. Server identity, the account link and the libraries come along; no claim token is needed.
  2. Check the custom server access URL in Plex as described under Installation.

The NS8 backup contains the module state and the plex-config volume, minus Cache, Logs and Crash Reports (state-exclude.conf). plex-transcode and the media are excluded; back up the media where it lives.

Symptom Cause and fix
Service does not start, journal says plex-check-media: … is not on a mounted filesystem The mount is missing (after a reboot, iSCSI/NFS not reachable). Mount it, then systemctl –user -M plex1@ restart plex.service.
… is not readable by plex1 Permissions: chmod -R o+rX <path> or the NFS export options.
Library empty although the files exist The SELinux context is missing (context= in fstab), or the path in the container differs from the path stored in the library.
“Bad Gateway” on the host name The container is still starting (first start about 30 s): runagent -m plex1 podman logs plex.
Server cannot be linked The token is older than four minutes — get a new one and save immediately.
LAN clients stream through the Plex relay Bridge mode has no LAN discovery. Enter the access URL in the clients or switch to host mode.
runagent -m plex1 podman logs plex                   # Plex log
journalctl --user -M plex1@ -u plex.service          # guard messages
api-cli run module/plex1/get-configuration --data null

Unofficial, community-built package, not affiliated with or endorsed by Plex, Inc. “Plex” is a trademark of Plex, Inc. and is used here only to identify the packaged software. The module uses the default NS8 module logo, not the Plex logo.

The module pulls the official Plex image at runtime and does not redistribute it; Plex Media Server is proprietary software subject to the Plex Terms of Service. The module's own code is licensed GPL-3.0-or-later.

Private hobby project, shared as-is. No warranty, no guaranteed support, use at your own risk.


  • ns8/applications/plex.txt
  • Last modified: 2 weeks ago
  • by LayLow