JOIN THE NEWSLETTER
ADVERTISEMENT
reserved 728 x 90
Homelab

Plex LXC on Proxmox: the full setup, from template to first stream

The whole Plex container, start to finish: the pct create line, one bind mount, Plex's own apt repo, the 100999 ownership fix and the claim step.
Title card: Plex in an LXC, template to stream. Two cores, 2048 MB, one bind mount and the 100999 ownership fix.

My Plex server is one container on a Proxmox box with an i7-4790. It has two cores, 2048 MB of RAM, one bind mount and two device lines. That is the whole thing, and it is smaller than most guides make it look.

Short version for the skimmers. A Plex LXC on Proxmox takes five steps. Create an unprivileged Ubuntu container. Bind mount your media dataset into it. Install Plex from Plex’s own apt repository. Set the media ownership on the host to the shifted user ID, 100999:100990 on mine. Then claim the server from a browser on your LAN at port 32400. The iGPU is a separate, optional step, and it has its own post.

KEY FACTS

My Plex container, CT 104, runs unprivileged with 2 cores, 2048 MB of RAM and 512 MB of swap. That is about 6% of the host’s 32 GB.

Plex’s own requirements page says 4 GB of RAM is typically more than sufficient, and that Linux installs often run happily on less.

Plex listens on port 32400. First setup happens at http://<container IP>:32400/web from a device on the same network.

Inside the container the plex user is UID 999 and GID 990. On the host that is 100999:100990, and that is the ownership the media needs.

The community Plex helper script defaults to Ubuntu 24.04, 2 cores, 2048 MB and an 8 GB disk.

What does a Plex LXC on Proxmox actually consist of?

Less than you would think. Here is the part of my container config that matters, from /etc/pve/lxc/104.conf. I trimmed the lines Proxmox writes for itself, like the MAC address and the root disk volume name.

cores: 2
memory: 2048
swap: 512
features: keyctl=1,nesting=1
unprivileged: 1
mp0: /ZFSDisk/media,mp=/mnt/media
dev0: /dev/dri/renderD128,gid=993
dev1: /dev/dri/card1,gid=44

Each line has one job. Here is what each one is for and why mine is set the way it is.

Setting On CT 104 Why
cores 2 Matches the two simultaneous transcodes I allow in Plex’s transcoder settings.
memory / swap 2048 MB / 512 MB Plex says 4 GB is typically more than enough. Linux needs less, and this host has 32 GB to share with a Windows Server VM.
unprivileged 1 The Proxmox default for new containers. Root inside is not root on the host.
features nesting=1, keyctl=1 Proxmox documents nesting as something systemd uses to isolate services. keyctl is documented as being for Docker.
mp0 /ZFSDisk/media at /mnt/media The one dataset Plex needs to read. Not the whole pool.
dev0 / dev1 renderD128, card1 The iGPU. Optional, and only useful with a Plex Pass.

Everything below builds that file one piece at a time. If you already have a container and just want the GPU working, skip to the iGPU passthrough post.

Should Plex run in a container or a VM?

A container, on this hardware. My board has no VT-d, so a VM cannot get the iGPU at all. A container shares the host kernel, so it can open the GPU’s device nodes directly. It also costs less memory, because there is no second kernel and no reserved block of RAM sitting idle.

I went through the full tradeoff in Proxmox LXC vs VM. For Plex the GPU argument usually settles it before anything else comes up.

Step 1: create the container

Start on the Proxmox host shell. Refresh the template list and pick an Ubuntu template. Template filenames change between releases, so I pull the current name into a variable instead of typing it.

pveam update
T=$(pveam available --section system | awk '/ubuntu-24.04-standard/ {print $2}' | tail -1)
echo $T
pveam download local $T

If echo $T prints nothing, run pveam available --section system on its own and look at what Ubuntu releases it lists today.

Then create the container. The ID, hostname and address are mine. Change them to suit your network.

pct create 104 local:vztmpl/$T \
  --hostname plex \
  --ostype ubuntu \
  --unprivileged 1 \
  --features nesting=1 \
  --cores 2 --memory 2048 --swap 512 \
  --rootfs local-lvm:8 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.4.142/22,gw=192.168.4.1 \
  --onboot 1

Three notes on that command.

The root disk goes on local-lvm, the boot SSD. That is where my Plex container’s root lives. The media stays on the ZFS pool and comes in through a bind mount. 8 GB is the community script’s default. Plex keeps its metadata and thumbnails on the root disk, so size it for your library.

The network is a /22, not a /24. My LAN runs 192.168.4.0 to 192.168.7.255. Typing /24 out of habit has broken firewall rules on this network before. It belongs in the address here too.

I left keyctl out. My own container has it on, but Proxmox documents it as a Docker feature, and this container does not run Docker.

Step 2: bind mount the media dataset, not the pool

My media lives on a ZFS dataset called ZFSDisk/media, with movies, tv and music folders inside. One line gives the container that dataset at /mnt/media.

pct stop 104
pct set 104 -mp0 /ZFSDisk/media,mp=/mnt/media
pct start 104

Point it at the dataset, not at /ZFSDisk, the pool root. The pool holds other things too. On mine that means a Windows Server VM’s disks and the file server container’s root filesystem. Plex has no reason to be anywhere near those, so give it the one dataset it needs.

If you want Plex to be unable to delete anything, add ,ro=1 to the end of the mp0 value. Mine is read-write.

Step 3: install Plex from Plex’s repository

Get a shell inside the container. You are root in there, so there is no sudo in what follows.

pct enter 104

These are the commands from Plex’s own repository article. They add the signing key and the repository, then install the server. Using the repository rather than a one-off .deb means normal apt upgrades keep Plex current.

apt update
apt install -y curl gnupg2
mkdir -p /etc/apt/keyrings
curl -L https://downloads.plex.tv/plex-keys/PlexSign.v2.key | gpg --yes --dearmor -o /etc/apt/keyrings/plexmediaserver.v2.gpg
echo "deb [signed-by=/etc/apt/keyrings/plexmediaserver.v2.gpg] https://repo.plex.tv/deb/ public main" > /etc/apt/sources.list.d/plex.list
apt update
apt install -y plexmediaserver

The mkdir line is mine, not Plex’s. It does nothing if the folder already exists and saves an error on an image where it does not.

Before leaving the container, read the plex user’s IDs. You need them in the next step.

id plex

Mine prints UID 999 and GID 990. Yours may not. A walkthrough I followed assumed 998, and that one digit cost me an afternoon.

Step 4: fix ownership before the first library scan

This is the step that makes an empty library. An unprivileged container shifts every ID by 100000. The plex user is 999 inside, so the host sees it as 100999. The files on the dataset have to be owned by that shifted number, or Plex finds the folders and cannot read anything in them.

Back on the host:

chown -R 100999:100990 /ZFSDisk/media

Files copied in over my Samba share arrive owned by the other container’s root, which is 100000:100000 on the host. They vanish from Plex until someone fixes them. My fix is a cron job on the host that resets ownership every five minutes.

*/5 * * * * chown -R 100999:100990 /ZFSDisk/media/ > /dev/null 2>&1

It is blunt and it works. The full model behind the offset, including why chmod 777 does not save you, is in unprivileged LXC permissions.

Step 5: claim the server from your LAN

Plex’s setup article says to open the web app from a device on the same local network. For my container that is:

http://192.168.4.142:32400/web

Sign in and the server gets claimed to your account. Then add one library per folder: Movies at /mnt/media/movies, TV Shows at /mnt/media/tv, Music at /mnt/media/music. Those are paths inside the container, not on the host.

If you are not on the same network, Plex says to set up an SSH tunnel first so the server sees you as local. Through the Proxmox host that looks like this, run from your own machine:

ssh -L 8888:192.168.4.142:32400 root@your-proxmox-host

Then open http://localhost:8888/web in your browser.

One setting to check on a network like mine. Under Settings, Network, Plex has a LAN Networks field. Plex’s network article says it decides which addresses count as local, and that remote bandwidth limits only apply to devices that are not. If you fill it in on a /22, write 192.168.4.0/22. A /24 there would class every device on 192.168.5.x to 192.168.7.x as remote.

Leave the field next to it alone, the list of networks allowed without auth. Plex warns that anything connecting that way is treated as the server owner.

Should you use the community helper script instead?

There is a well known community Plex LXC script that does steps 1 and 3 in one command. Here is how its defaults line up against the manual route.

Community script default Manual, as above
OS Ubuntu 24.04 Whatever template you pick
Cores / RAM 2 / 2048 MB 2 / 2048 MB on mine
Root disk 8 GB 8 GB in the command, size to your library
Privileged Either, your choice Unprivileged
Media bind mount Not mentioned on the script page Step 2
Media ownership Not mentioned on the script page Step 4

My container ended up with exactly the script’s core and memory defaults. The script does not remove steps 2 and 4, though, and those are where an empty library comes from. If you use it, read it first. It runs as root on your Proxmox host.

Adding the iGPU later

The last two lines of the config, dev0 and dev1, hand the Intel iGPU’s device nodes to the container. On my host the card node is card1, not card0, and the gid values are the render and video groups as the container sees them. I wrote the whole thing up in Proxmox iGPU passthrough to an unprivileged LXC.

Know one thing before you spend an evening on it. Plex only uses hardware transcoding with an active Plex Pass, and I do not have one. Everything below Plex works on my container and Plex still declines to use it. For most people the bigger win is free: set every client to Original quality so it direct plays. That and the transcoder settings I use are in the Plex hardware transcoding guide.

FAQ

Should Plex run in an LXC or a VM on Proxmox?

An LXC, for most home servers. A container shares the host kernel, so it can use an Intel iGPU through its device nodes without VT-d or PCI passthrough. It also uses less memory than a VM, because there is no second kernel.

Does a Plex LXC need to be privileged?

No. My Plex container is unprivileged. The only thing that changes is ownership: media on the host has to belong to the plex user plus 100000, which is 100999:100990 on mine.

How much RAM does a Plex LXC need?

Mine runs with 2048 MB and 512 MB of swap. Plex says 4 GB is typically more than sufficient and that Linux installs often run on less. The community helper script also defaults to 2048 MB.

Why does Plex show an empty library in my container?

Almost always ownership. In an unprivileged container the plex user is shifted by 100000 on the host, so the media must be owned by that shifted ID. Run id plex inside the container, add 100000, and chown the media on the host to match.

Can I set up a Plex server from outside my network?

Plex says to open the web app from the same local network, at port 32400. From outside, set up an SSH tunnel first so the server sees the connection as local, then open the web app through the tunnel.

The short version

Create an unprivileged container with 2 cores and 2048 MB. Mount the media dataset, not the pool. Install Plex from Plex’s repository. Run id plex, add 100000, and chown the media on the host to match. Claim it from your LAN at port 32400. The GPU is the last step, and it only pays off with a Plex Pass.

This container is one piece of a bigger build. The plain text prompt I used to plan the whole server with Claude is here: the Proxmox home server prompt. The build itself is in my Proxmox home server setup.

MP
Written by Marcos Ponce
I build things and write down what actually happened: AI automation, micro-cap deep dives, 3D printed PC cases, and the occasional song.
ADVERTISEMENT
reserved 336 x 280

One useful build a week.

The next automation, deep dive, or 3D print, written up the way I wish someone had written it for me.

SUBSCRIBE