Proxmox iGPU Passthrough to an Unprivileged LXC for Plex
I finished wiring the iGPU into my Plex container on a Tuesday night, watched a 1080p file start transcoding, and saw no (hw) anywhere. The config was fine. The subscription was the problem.
Proxmox iGPU passthrough into an unprivileged LXC is not really passthrough. My board has no VT-d, so no PCI device ever leaves the host. Two device nodes get bind mounted into the container instead. The whole job then comes down to one thing. Does the group ID on those nodes match a group the Plex user is actually in? Get that wrong and you get a permission denied that looks like a broken kernel driver.
KEY FACTS
The host is an Intel i7-4790 with HD Graphics 4600. No VT-d, so PCI passthrough is off the table. The iGPU gets shared through /dev/dri instead.
Proxmox VE 8.2 added a dev[n] line in the container config. Syntax is dev0: [path=]<Path> [,uid=] [,gid=] [,mode=] [,deny-write=].
The gid= value is the group ID the device node gets inside the container, not on the host. That is the detail almost every thread gets stuck on.
On my system the render node is renderD128 and the card node is card1, not card0. Check before you copy anyone else’s config.
Plex will not use hardware acceleration without an active Plex Pass. Jellyfin has no such gate. I found this out after the config already worked.
Why this is device sharing and not PCI passthrough
PCI passthrough hands an entire physical device to one guest. The host loses it. To do that you need an IOMMU, which on Intel means VT-d, and my 4790 does not have it enabled or available in any useful form on this board.
Sharing is the better answer here anyway. An LXC container runs on the host kernel, so it already has the i915 driver loaded. All the container needs is permission to open two files. The host keeps using the iGPU for its own console, and any number of containers can encode at the same time until the hardware runs out of capacity.
This is one of the concrete reasons my media stack runs in containers rather than virtual machines. I went through the rest of that tradeoff in Proxmox LXC vs VM. GPU access is the argument that usually ends the debate for a Plex box without VT-d.
Check the host first: is the iGPU even there?
Before touching any container config, confirm the host can see the device. Run this on the Proxmox node, not inside the container.
ls -la /dev/dri/
You want output with a render node in it:
crw-rw---- 1 root video 226, 1 Sep 15 09:12 card1
crw-rw---- 1 root render 226, 128 Sep 15 09:12 renderD128
Two things to read off that listing. First, the card node number. Mine is card1. Most guides online say card0. If you paste card0 into your config on a system that numbers it 1, the container starts and the device is simply absent.
Second, the owning groups on the host: video for the card node, render for the render node. Those host group numbers do not travel into the container. Note them anyway so you know which node is which.
If /dev/dri does not exist at all, the i915 module is not loaded:
modprobe i915
echo i915 >> /etc/modules
The two lines that do the work
Older guides tell you to hand write lxc.cgroup2.devices.allow and lxc.mount.entry pairs. They still work. You do not need them any more.
Since Proxmox VE 8.2 the container config takes a dev[n] key that handles the cgroup rule, the bind mount and the ownership in one line. My Plex container is CT 104, so the file is /etc/pve/lxc/104.conf. These are the two lines I run:
dev0: /dev/dri/renderD128,gid=993
dev1: /dev/dri/card1,gid=44
Stop the container, add them, start it again. Live editing of the config does not apply device entries.
pct stop 104
nano /etc/pve/lxc/104.conf
pct start 104
That is the whole file change. No idmap gymnastics, no udev rule setting mode 0666 on the host, no privileged container. If you are reading a guide that opens by telling you to make the container privileged, close it.
The group ID is the part that breaks
Here is the sentence I wish someone had put at the top of every thread I read. The number after gid= is the group ID as the container sees it. Not the host value. Not the host value plus 100000.
An unprivileged container shifts every ID by 100000. Root inside is UID 100000 on the host. My Plex service runs as UID 999 and GID 990 inside, which are 100999 and 100990 on the host. That offset is exactly why bind mounted media needs a matching chown, something I wrote up in detail in unprivileged LXC permissions.
Device nodes created by dev[n] skip that arithmetic. Proxmox creates the node inside the container’s namespace and stamps it with the ID you gave it, in container terms. So you do not look up the host group. You look up the group inside the container.
pct exec 104 -- getent group render
pct exec 104 -- getent group video
On my Ubuntu container that returns:
render:x:993:
video:x:44:
993 and 44. Those are the numbers that go in the config, and they are why my two lines read the way they do. Your render GID will very likely differ. Debian 12 hands out 993 or 992 depending on package order. Ubuntu tends toward 993. Some minimal images have no render group at all until you install libdrm or the VA-API packages. Check yours. Do not copy mine.
Put the Plex user in the render and video groups
The device node now has the right group. The Plex process still has to be a member of it.
pct exec 104 -- usermod -aG render,video plex
pct exec 104 -- id plex
Then restart Plex so the process picks up its new group membership. A running daemon does not gain groups retroactively.
pct exec 104 -- systemctl restart plexmediaserver
Confirm from inside the container that the node is present and readable:
pct exec 104 -- ls -la /dev/dri/
You are looking for a group name rather than a bare number. If the listing shows nogroup, or a bare number with no name beside it, your gid value does not match a real group in that container. Plex then falls back to software encoding without telling you why.
How to confirm Quick Sync is actually being used
Two checks, and they disagree often enough that both are worth running.
The first is VA-API on the device itself. Install the tools in the container and query the driver:
pct exec 104 -- apt install -y vainfo intel-media-va-driver-non-free
pct exec 104 -- vainfo --display drm --device /dev/dri/renderD128
A working iGPU lists its profiles, including VAProfileH264Main with VAEntrypointEncSlice. If vainfo fails to open the device, the problem is still permissions and Plex is not involved yet.
The second check is Plex itself. Turn on hardware acceleration under Settings, Server, Transcoder, then play a file that forces a transcode. Open the Activity dashboard and read the video line. When the iGPU is doing the work you get (hw) next to the format. No (hw) means software, whatever the settings page claims.
That is where my build stopped. Plex treats hardware accelerated streaming as a premium feature and requires an active Plex Pass, which I do not have. Everything below the Plex layer works. The permission chain is correct, vainfo reports encode profiles, the device node is owned by a real group. Plex just declines to use it. If you are building this from scratch and do not want the subscription, that is worth knowing before you spend an evening on group IDs.
What an HD 4600 can and cannot do
Haswell Quick Sync is old and the limits are real. Mine decodes H.264, MPEG-2 and VC-1, and encodes H.264 at up to 1080p. It does not encode HEVC. Plex’s own documentation puts HEVC encoding at 7th generation Intel and newer, which is three generations past this chip.
In practice that means 4K HEVC source files get decoded in software, which on four Haswell cores is slow enough that a single stream can stutter. My answer was to stop transcoding those at all. I set clients to Original quality so they direct play, and I keep 1080p H.264 copies of anything that gets watched on a phone. The transcoder settings that go with that approach are in my Plex hardware transcoding guide.
The honest summary of Haswell Quick Sync: good for two or three simultaneous 1080p H.264 transcodes, useless for 4K, and free. On a box that idles at low power that is still a reasonable trade.
What this costs in RAM
Almost nothing, which surprised me. Device sharing adds no memory overhead of its own. My Plex container runs with 2048MB and 512MB of swap on two cores, and enabling the iGPU did not move the resident figure in any way I could measure.
That matters on this host because 32GB of DDR3 is the ceiling and a Windows Server VM already takes a large share of it. Containers that share a device rather than owning one are how the rest of the stack fits. The full layout, including what else is competing for that memory, is in the home server build.
FAQ
Does Proxmox iGPU passthrough require a privileged container?
No. Every configuration in this post runs in an unprivileged container. Privileged containers were the old workaround for device permissions, and they hand the container a root user that is genuine root on the host. The gid option on the dev line solves the same problem without that risk.
Should I use dev0 or the old lxc.cgroup2 and lxc.mount.entry lines?
Use dev0 if you are on Proxmox VE 8.2 or newer. It replaces three or four hand written lines with one, sets the ownership for you, and survives container migration better. The older lines still work, so there is no urgency to rewrite a config that is already running.
Why is my device card1 instead of card0?
Card node numbering follows the order the kernel enumerates DRM devices, and it is not stable across hardware or across kernel versions. A discrete card, a headless framebuffer or a firmware update can shift it. Always run ls on /dev/dri rather than trusting a number from a guide.
What gid should I use for the render group?
Whatever getent group render returns inside your container. Mine is 993 on Ubuntu. Debian images often differ, and some minimal images have no render group until you install the VA-API packages. There is no correct universal value.
Do I need a Plex Pass for hardware transcoding?
Yes. Plex documents hardware accelerated streaming as a premium feature tied to an active Plex Pass, with a small number of grandfathered NVIDIA SHIELD and Western Digital devices as exceptions. Jellyfin and Emby do not gate it, so the same container configuration will hardware transcode there without a subscription.
Can two containers use the same iGPU at once?
Yes. Add the same dev lines to each container, with the gid that is correct for that container. They share the hardware the way two processes on the host would, and they contend for the same finite encode capacity.
The short version
Read the card node number off ls -la /dev/dri/ on the host. Read the render and video GIDs off getent group inside the container. Put those two numbers into two dev lines, add the Plex user to both groups, restart. If vainfo lists encode profiles from inside the container, the hard part is done.
The rest is a Plex licensing question, not a Proxmox one, and I would rather have learned that first.
Stay in the loop.
No spam, just signal.
I send one email when I publish something worth reading. Stock deep dives, new projects, tech breakdowns, and what I'm learning from building in public.
Free forever. Unsubscribe anytime. I respect your inbox.