Proxmox Unprivelliged LXC with shared Intel ARC GPU for Plex or Jellyfin transcoding

Status: CURRENT
Last reviewed: 31 August 2026
Applies to: Proxmox VE 8.2+ / 9.x, unprivileged LXC, Intel Arc GPU
Example LXC: Debian Trixie

This guide originally used manual cgroup, UID/GID, subuid and subgid mappings. Modern Proxmox has native LXC Device Passthrough, so the current method is much simpler.

This guide is for running Plex and/or Jellyfin in Docker inside an unprivileged Proxmox LXC, while sharing an Intel Arc GPU from the Proxmox host for hardware transcoding.

I’m using a Debian Trixie LXC for this example. This assumes you have:

  • Created an unprivileged LXC (Proxmox instructions)
  • Installed Docker inside the LXC (Docker instructions)
  • An Intel Arc GPU detected and working on the Proxmox host
  • An active Plex Pass subscription if you want to use Plex hardware transcoding

Find the Intel Arc render device

On the Proxmox host, check the available render devices:

ls -l /dev/dri/render*


Example output from my host:

crw-rw---- 1 root render 226, 128 Nov 16 21:02 /dev/dri/renderD128
crw-rw---- 1 root render 226, 129 Nov 16 21:02 /dev/dri/renderD129


In my system, renderD128 is the Intel iGPU and renderD129 is the Intel Arc A310. Your device numbers may be different, so don’t blindly copy mine.

If you’re not sure which render device belongs to which GPU, check the DRI device links:

ls -l /dev/dri/by-path/


You can compare the PCI addresses shown there with:

lspci | grep -Ei 'VGA|Display'


Pass the Arc GPU into the LXC

This is the bit that became much easier in newer Proxmox versions.

In the Proxmox web interface, select the LXC and go to:

Resources → Add → Device Passthrough

  • Device Path: select your Intel Arc render device. Mine is /dev/dri/renderD129
  • Mode: 0666
  • UID: leave blank
  • GID: leave blank

For my system, Proxmox creates an LXC config entry equivalent to:

dev0: /dev/dri/renderD129,mode=0666


That’s it. No manual lxc.idmap entries, no editing /etc/subuid or /etc/subgid, and no manually-created cgroup or bind-mount rules.

Why 0666?
This gives processes inside the LXC read/write access to the passed-through render device, which avoids the UID/GID mapping mess used by older methods. For a dedicated unprivileged Plex/Jellyfin LXC this is the simple option. If you want tighter permissions, Proxmox also supports assigning a specific UID/GID to the device instead.

Restart the LXC after adding the device, then check from inside the LXC:

ls -l /dev/dri/


You should see the render device you passed through. In my case that is renderD129.

Docker Compose

The Arc render device also needs to be passed from the LXC into the Plex or Jellyfin Docker container.

Add the following to the Plex or Jellyfin service in your Compose file, changing the render device if yours is different:

devices:
  - /dev/dri/renderD129:/dev/dri/renderD129


If your Arc GPU is renderD128, use /dev/dri/renderD128:/dev/dri/renderD128 instead.

After recreating the Docker container, you can confirm the device is visible inside it:

docker exec -it <container-name> ls -l /dev/dri/


Plex

In Plex, go to Settings → Server → Transcoder and enable Show Advanced if required.

  • Enable Use hardware acceleration when available
  • Enable Use hardware-accelerated video encoding
  • Under Hardware transcoding device, select the Intel Arc GPU

Mine appears as Intel DG2 [Arc A310].

To test it, play a video and force a transcode by changing the quality to something lower. Then go to Activity → Dashboard. The video transcode should show the hardware (hw) tag.

If the Arc GPU doesn’t appear in Plex, check the device in this order:

  1. Proxmox host: ls -l /dev/dri/
  2. Inside the LXC: ls -l /dev/dri/
  3. Inside the Docker container: docker exec -it <container-name> ls -l /dev/dri/

Note: Plex hardware-accelerated transcoding requires an active Plex Pass subscription.

Plex hardware transcoding using Intel Arc

Jellyfin

For Jellyfin, open the Administration Dashboard, then go to Playback → Transcoding.

  • Under Hardware Acceleration, select Intel Quick Sync (QSV)
  • Set the QSV device to the Intel Arc render device. Mine is /dev/dri/renderD129

If the Arc GPU is the only Intel GPU in your system it will often be /dev/dri/renderD128. Again, use the device you identified earlier rather than assuming the number.

To test Jellyfin, play a video, lower the playback quality so it has to transcode, then open Playback Info. Look for Play method: Transcoding.

If transcoding fails completely, first confirm that the render device exists inside both the LXC and the Jellyfin Docker container. Jellyfin’s Intel hardware acceleration documentation also includes additional QSV/VA-API and driver checks if required:

Jellyfin Intel GPU hardware acceleration documentation

Jellyfin Intel Quick Sync transcoding settings

Verify the GPU is actually being used

If you want to confirm the GPU itself is doing the work, install intel-gpu-tools on the Proxmox host and run intel_gpu_top while a transcode is active. You should see activity on the Intel GPU’s video engines.

Archived method: manual UID/GID mapping

ARCHIVED – November 2025 method

The original version of this guide manually added lxc.cgroup2.devices.allow, bind-mounted the DRI devices, created custom lxc.idmap rules and edited /etc/subuid and /etc/subgid.

That method worked, but it is no longer the recommended path for a current Proxmox install. Native LXC Device Passthrough creates the device node for the container and can assign its mode, UID and GID directly, so the old mapping instructions have been removed from the active guide to avoid unnecessary complexity.

If you’re deliberately running an older Proxmox release, use documentation appropriate for that version rather than copying the old mappings from this article.

Credit: Thanks to gnd-7000 in the comments for pointing out that Proxmox 9 no longer needs the old GID mapping method and that the GPU can be added directly through Resources → Device Passthrough. Much cleaner 🙂

References: Proxmox pct documentation · Jellyfin Intel GPU documentation · Plex Transcoder documentation

Proxmox cluster with Traefik

Status: CURRENT
Last reviewed: 31 August 2026
Applies to: Proxmox VE 8/9 cluster + Traefik v3.x

Traefik can sit in front of multiple Proxmox nodes so a single hostname such as proxmox.richay.au reaches whichever cluster node is available. Modern Traefik supports Proxmox shell/noVNC WebSockets without a special WebSocket middleware.

Why put a Proxmox cluster behind Traefik?

Each node in a Proxmox cluster provides the web interface, and Proxmox cluster management can be performed by connecting to any cluster node.

Instead of remembering:

https://10.10.10.1:8006
https://10.10.10.2:8006
https://10.10.10.3:8006


I can use one address:

https://proxmox.richay.au


Traefik then load balances the browser traffic between the available nodes.

Security: Proxmox is a management interface with control over your VMs, containers, storage and cluster. I recommend keeping this hostname private rather than publishing it to the open internet.

I use Tailscale Split DNS for this type of service so proxmox.richay.au resolves only while I’m at home or connected to Tailscale. My guide is here: Tailscale Split DNS by Domain for Secure Home Server Access.

The old WebSocket workaround is no longer needed

The original version of this article added these request headers manually:

Upgrade: websocket
Connection: Upgrade


That is no longer required with current Traefik. Traefik v3 supports WebSocket and WebSocket Secure connections out of the box and automatically handles the protocol upgrade using normal HTTP routing.

So there is no need for a custom websocket-upgrade middleware just to make the Proxmox shell or noVNC console work.

Current Traefik dynamic configuration

This is the modern equivalent of my original config.yaml.

This example assumes your Proxmox nodes are using their normal cluster-generated HTTPS certificates and Traefik is connecting to them by IP address. Because those certificates are normally signed by the private Proxmox cluster CA, the example uses a dedicated ServersTransport with certificate verification disabled.

http:
  routers:
    proxmox:
      entryPoints:
        - "https"
      rule: "Host(`proxmox.richay.au`)"
      service: proxmox
      tls: {}

  services:
    proxmox:
      loadBalancer:
        serversTransport: proxmox-transport

        sticky:
          cookie:
            name: proxmox_lb
            secure: true
            httpOnly: true
            sameSite: lax

        servers:
          - url: "https://10.10.10.1:8006" # Proxmox node 1
          - url: "https://10.10.10.2:8006" # Proxmox node 2
          - url: "https://10.10.10.3:8006" # Proxmox node 3

  serversTransports:
    proxmox-transport:
      insecureSkipVerify: true


That’s it. No manual WebSocket headers.

Why use a sticky cookie?

Traefik’s load balancer normally distributes requests between the backend servers. Enabling a sticky cookie keeps subsequent requests from the same browser session on the same Proxmox node where possible.

For a normal website that may not matter much, but for a management UI with long-running shell/noVNC connections I prefer keeping the browser pinned to one node during the session.

The example also marks Traefik’s affinity cookie as Secure and HTTPOnly.

About insecureSkipVerify

insecureSkipVerify: true does not disable HTTPS. Traffic between Traefik and Proxmox is still encrypted, but Traefik does not verify that the backend certificate is trusted and belongs to the expected server.

That’s convenient for a trusted home management network, but it is weaker than actually trusting the Proxmox cluster CA.

Proxmox creates its own cluster CA by default and generates a node certificate for each node. The public cluster CA is stored at:

/etc/pve/pve-root-ca.pem


If you want proper backend certificate verification, copy only the public CA certificate to the Traefik host/container and use node hostnames that match the certificates.

For example:

http:
  services:
    proxmox:
      loadBalancer:
        serversTransport: proxmox-trusted
        sticky:
          cookie:
            name: proxmox_lb
            secure: true
            httpOnly: true

        # Use the actual DNS names present in your node certificates.
        servers:
          - url: "https://pve1.home.arpa:8006"
          - url: "https://pve2.home.arpa:8006"
          - url: "https://pve3.home.arpa:8006"

  serversTransports:
    proxmox-trusted:
      rootCAs:
        - /etc/traefik/certs/pve-root-ca.pem


This is the better option if you want end-to-end TLS verification rather than simply trusting the management LAN.

Do not copy the Proxmox cluster CA private key to Traefik. The public pve-root-ca.pem certificate is all Traefik needs to trust the cluster certificates.

HTTPS redirect

My old dynamic configuration attached an HTTPS redirect middleware to the router even though that router was already listening on the HTTPS entrypoint. That’s redundant.

If you want every request hitting port 80 to redirect to HTTPS, I prefer doing that once on the HTTP entrypoint in Traefik’s static/install configuration:

entryPoints:
  http:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https

  https:
    address: ":443" 


Then every service can use the HTTPS entrypoint without carrying around its own redirect middleware.

Testing it

Open the single cluster hostname:

https://proxmox.richay.au


Log into Proxmox and test:

  • Normal cluster navigation.
  • A node shell.
  • An LXC console.
  • A VM noVNC console.

They should work through the same Traefik route without any WebSocket-specific middleware.

If the normal web UI loads but consoles fail, check the browser developer tools for the WebSocket request and check the Traefik logs. Don’t immediately add fake Upgrade headers back in — modern Traefik already handles that part.

What happens when a node goes down?

Traefik can distribute requests across multiple Proxmox nodes, but this alone is not a full HA design. If the node your sticky cookie points to disappears, Traefik can select another backend when the failed backend is detected, but an existing shell/noVNC connection to the dead node will obviously be lost.

The reverse proxy gives you one convenient cluster URL. Proxmox clustering/HA is still what handles the actual guests and cluster state.

Archived note from the original article

ARCHIVED — old Traefik WebSocket middleware

The original article used a websocket-upgrade Headers middleware with Upgrade: websocket and Connection: Upgrade. That workaround has been removed from the active configuration because current Traefik handles WebSocket upgrades automatically.

References

Much cleaner now — one cluster URL, sticky sessions, working shells, and no janky WebSocket header hack required 🙂

Ansible – updating proxmox host kernel with LXC shared GPU

Status: ARCHIVED — HISTORICAL NVIDIA WORKAROUND
Last reviewed: 31 August 2026
Originally applied to: Proxmox + shared NVIDIA GPU into LXC using manually installed NVIDIA drivers

Do not copy the old playbook as a current NVIDIA update procedure. It hard-codes an old NVIDIA driver and treats manual driver reinstallation after a kernel update as normal. With a properly configured DKMS installation, the NVIDIA kernel module should normally rebuild for the new kernel automatically.

Is this still relevant?

Mostly no. I’m keeping the page because the problem it was trying to solve still exists, but the automation itself belongs to an older version of my setup.

The original idea was simple: after upgrading the Proxmox kernel, check whether the NVIDIA module loaded. If it didn’t, reinstall the NVIDIA .run driver on the host, reboot, then reinstall the matching NVIDIA userspace driver inside the Plex LXC.

The old playbook was built around a fixed NVIDIA driver:

NVIDIA-Linux-x86_64-535.154.05.run


That’s the first reason not to use it today. More importantly, a kernel update by itself should not normally require reinstalling the whole NVIDIA driver.

What should happen now?

For an NVIDIA driver installed with DKMS:

  1. A new Proxmox kernel is installed.
  2. The matching Proxmox kernel headers are available.
  3. DKMS builds the NVIDIA kernel module for that new kernel.
  4. You reboot into the new kernel.
  5. nvidia-smi continues to work.

NVIDIA’s current documentation says that kernel updates can occasionally leave DKMS without the correct headers or fail to rebuild the module. When that happens, the modern fix is normally to diagnose/rebuild DKMS rather than blindly reinstalling an old driver package.

Useful checks after a Proxmox kernel update

uname -r
nvidia-smi
dkms status


Make sure the headers for the running kernel are installed:

apt update
apt install proxmox-headers-$(uname -r)


Depending on the driver installation method and package naming, you can then rebuild the NVIDIA DKMS module rather than reinstalling the entire driver.

Proxmox 9 note: NVIDIA kernel/module compatibility can still genuinely break on newer Proxmox kernels. There have been current Proxmox 9.2 reports involving kernel 7.x, DKMS build failures and conflicts with newer kernel drivers. So if nvidia-smi dies after an upgrade, don’t assume your LXC passthrough configuration is the problem — check the host NVIDIA module first.

What about the LXC?

For shared NVIDIA GPU access, the LXC uses the NVIDIA kernel driver loaded by the Proxmox host. The container does not build its own kernel module.

The old playbook installed the same NVIDIA package inside the LXC using:

./NVIDIA-Linux-x86_64-535.154.05.run --no-kernel-module


That installs the userspace side without attempting to build a kernel module inside the container.

The important distinction is that a kernel update does not automatically mean those userspace libraries changed. You normally only need to revisit the LXC-side NVIDIA libraries when the actual NVIDIA driver version changes or the userspace/kernel versions no longer match.

Proxmox device passthrough is cleaner now too

Current Proxmox supports native LXC device passthrough using dev[n] entries, including configurable path, UID, GID and mode. That has replaced a lot of the older hand-written cgroup/bind-mount hacks used in historical GPU-sharing guides.

The NVIDIA-specific wrinkle remains: the host still needs a functioning NVIDIA kernel driver before any /dev/nvidia* devices can be shared into the LXC.

If I automated this today

I would automate verification, not automatic driver reinstallation.

For example, an Ansible playbook can check the host after updates and deliberately fail if the NVIDIA stack is broken:

- name: Verify NVIDIA after Proxmox update
  hosts: proxmox
  become: true

  tasks:
    - name: Check running kernel
      ansible.builtin.command: uname -r
      register: running_kernel
      changed_when: false

    - name: Check NVIDIA
      ansible.builtin.command: nvidia-smi
      register: nvidia_smi
      changed_when: false
      failed_when: false

    - name: Check DKMS state
      ansible.builtin.command: dkms status
      register: dkms_status
      changed_when: false
      failed_when: false

    - name: Stop if NVIDIA is broken
      ansible.builtin.fail:
        msg: |
          NVIDIA did not initialise after the kernel update.
          Kernel: {{ running_kernel.stdout }}
          nvidia-smi:
          {{ nvidia_smi.stdout }}
          {{ nvidia_smi.stderr }}

          DKMS:
          {{ dkms_status.stdout }}
      when: nvidia_smi.rc != 0


That gives you the automation benefit without having Ansible silently run an obsolete .run installer against whatever kernel happened to arrive that day.

Original playbook — archived for reference

ARCHIVED CODE — DO NOT USE AS-IS. This is the original playbook from this article. It is preserved so old links and search results still make sense, not because I recommend deploying it in 2026.

########
- hosts: nvidia
  become: true
  become_user: root
  tasks:
    - name: Wait for 10.77.69.2 to become available
      wait_for_connection:
        delay: 5
        timeout: 300

    - name: Check if NVIDIA kernel module is loaded
      shell: lsmod | grep -q '^nvidia'
      register: nvidia_module_check
      ignore_errors: true

    - name: Set NVIDIA module check result as fact
      set_fact:
        nvidia_module_rc: "{{ nvidia_module_check.rc }}"

    - name: Reinstall NVIDIA driver if module is not loaded
      shell: sh /root/NVIDIA-Linux-x86_64-535.154.05.run --silent
      args:
        executable: /bin/bash
      when: nvidia_module_check.rc != 0

    - name: Set fact if NVIDIA driver was installed
      set_fact:
        driver_installed: true
      when: nvidia_module_check.rc != 0

    - name: Reboot system if NVIDIA driver was reinstalled
      reboot:
      when: nvidia_module_check.rc != 0

    - name: Wait for 10.77.69.2 to become available after reboot
      wait_for_connection:
        delay: 10
        timeout: 600
      when: nvidia_module_check.rc != 0

########
- hosts: plex
  become: true
  become_user: root
  tasks:
    - name: Install NVIDIA driver in LXC
      shell: sh /root/NVIDIA-Linux-x86_64-535.154.05.run --no-kernel-module --silent
      args:
        executable: /bin/bash
      when: hostvars['10.77.69.2'].driver_installed | default(false)

    - name: Reboot 10.77.69.103
      reboot:
      when: hostvars['10.77.69.2'].driver_installed | default(false)

    - name: Wait for 10.77.69.103 to become available
      wait_for_connection:
        delay: 10
        timeout: 300
      when: hostvars['10.77.69.2'].driver_installed | default(false)


Why I’m archiving it instead of deleting it

The original problem was real and NVIDIA + new Proxmox kernels can still occasionally go cactus. But the correct troubleshooting path has changed.

Today I’d check:

  • Is the running Proxmox kernel supported by the installed NVIDIA driver?
  • Are the matching Proxmox kernel headers installed?
  • Did DKMS actually build/install the NVIDIA module for the running kernel?
  • Does nvidia-smi work on the host?
  • Do the /dev/nvidia* devices exist?
  • Only then: does the LXC have the right devices and compatible NVIDIA userspace libraries?

That’s a better model than treating every kernel update as “reinstall NVIDIA everywhere and reboot twice”.

References

Nvidia GPU passthrough in LXC

Status: CURRENT — MAJOR 2026 UPDATE
Last reviewed: 31 August 2026
Applies to: Proxmox VE 8.2+/9.x, NVIDIA GPU device sharing into LXC containers

The idea behind this article is still useful, but the old configuration method is not. Current Proxmox has native LXC Device Passthrough, so you no longer need to hand-write cgroup rules and bind mounts for every NVIDIA device.

Credit where it’s due: the original version of this guide was based on TheOrangeOne’s NVIDIA LXC GPU passthrough article. That method got me working at the time, but Proxmox has made this a hell of a lot cleaner since then.

First: this is not PCIe passthrough

With normal PCIe passthrough you hand the physical GPU over to one VM. The host effectively lets go of it.

This setup is different. The NVIDIA driver stays loaded on the Proxmox host, and the host’s NVIDIA device nodes are exposed to an LXC.

That means the same physical NVIDIA GPU can potentially be exposed to multiple containers at the same time.

Think of it as sharing access to the host GPU driver, not giving the entire PCIe device away.

The old guide is very old now 🙂

The original article used a GTX 760 with NVIDIA driver 450.80.02, manually downloaded the NVIDIA .run installer, created device nodes with custom udev rules, then edited the LXC config with lxc.cgroup2.devices.allow and lxc.mount.entry.

All of that made sense at the time. Most of it is now either unnecessary or something I’d avoid on a fresh build.

Jellyfin users: Jellyfin 10.11 currently requires NVIDIA driver 520.56.06 or newer. NVIDIA’s Kepler GPUs are limited to the 470 legacy driver branch, so the old GTX 760 example from this article no longer meets Jellyfin’s current driver requirement. The poor little thing has finally earned retirement 😅.

1. Install a working NVIDIA driver on the Proxmox host

The host still needs the real NVIDIA kernel driver. That part has not changed.

Before installing the driver, make sure DKMS and the headers for the running Proxmox kernel are available:

apt update
apt install dkms proxmox-headers-$(uname -r)


Then install a current NVIDIA driver that supports your GPU and kernel using the current Debian/NVIDIA package instructions.

I no longer recommend hard-coding an ancient direct download such as:

NVIDIA-Linux-x86_64-450.80.02.run


Package-managed drivers with DKMS are much easier to keep working across Proxmox kernel updates. If you deliberately use NVIDIA’s .run installer, make sure DKMS is enabled so the module can rebuild for new kernels.

Reboot if required, then verify the host first:

uname -r
nvidia-smi
dkms status


If nvidia-smi is cactus on the Proxmox host, stop here. The LXC isn’t going to magically fix it.

2. Check which NVIDIA device nodes exist

On the Proxmox host:

ls -l /dev/nvidia* /dev/nvidia-caps/* 2>/dev/null


A typical modern NVIDIA setup may contain some or all of:

  • /dev/nvidia0
  • /dev/nvidiactl
  • /dev/nvidia-uvm
  • /dev/nvidia-uvm-tools
  • /dev/nvidia-modeset
  • /dev/nvidia-caps/nvidia-cap1
  • /dev/nvidia-caps/nvidia-cap2

Don’t copy a list from a random guide and assume your machine has exactly the same devices. NVIDIA driver versions, GPU generations and workloads can differ.

The old guide also assumed fixed character-device major numbers such as 195 and 243. We don’t need to care about that anymore. Yewww.

3. Pass the NVIDIA devices into the LXC

Proxmox VE introduced native container device passthrough in 8.1, and from 8.2 you can configure it directly in the web interface.

Stop the LXC, then go to:

LXC → Resources → Add → Device Passthrough


Add the NVIDIA device nodes that exist on your host.

For a simple media-server setup I normally start with the common device set below. Replace 103 with your container ID and skip any device that doesn’t exist on your host:

pct set 103 --dev0 path=/dev/nvidia0,mode=0666
pct set 103 --dev1 path=/dev/nvidiactl,mode=0666
pct set 103 --dev2 path=/dev/nvidia-uvm,mode=0666
pct set 103 --dev3 path=/dev/nvidia-uvm-tools,mode=0666
pct set 103 --dev4 path=/dev/nvidia-modeset,mode=0666


If your workload needs the NVIDIA capability devices and they exist on your host, add them too:

pct set 103 --dev5 path=/dev/nvidia-caps/nvidia-cap1,mode=0666
pct set 103 --dev6 path=/dev/nvidia-caps/nvidia-cap2,mode=0666


About mode=0666: I’m using it here because it is the least painful way to prove the GPU works inside an unprivileged LXC. Proxmox creates the device node for the container with that access mode; you are not running a blanket chmod 666 /dev/nvidia* across the host.

Once everything works, you can tighten this using the uid, gid and mode options supported by Proxmox Device Passthrough.

Check what Proxmox saved:

pct config 103 | grep '^dev'


You should see entries similar to:

dev0: /dev/nvidia0,mode=0666
dev1: /dev/nvidiactl,mode=0666
dev2: /dev/nvidia-uvm,mode=0666
dev3: /dev/nvidia-uvm-tools,mode=0666
dev4: /dev/nvidia-modeset,mode=0666


4. Start the LXC and check the devices

Start the container and enter it:

pct start 103
pct enter 103


Then check the device nodes from inside the LXC:

ls -l /dev/nvidia* /dev/nvidia-caps/* 2>/dev/null


If they are present, the Proxmox side of the passthrough is basically done.

5. Install NVIDIA userspace libraries inside the LXC

This is the NVIDIA-specific annoying bit that still exists.

The LXC uses the host’s NVIDIA kernel module. You do not build another NVIDIA kernel module inside the container.

However, applications inside the LXC still need NVIDIA userspace libraries such as NVML, NVDEC and NVENC.

The userspace NVIDIA components inside the LXC need to be compatible with the NVIDIA kernel driver on the host. NVIDIA itself documents driver/library version mismatch errors when the client libraries and loaded kernel module don’t match.

For a Debian-based Jellyfin LXC, Jellyfin’s current documentation calls for its FFmpeg package plus the NVIDIA decode/encode libraries:

apt update
apt install jellyfin-ffmpeg7 libnvcuvid1 libnvidia-encode1


You will also need the matching NVIDIA userspace/NVML utilities for your driver branch so that nvidia-smi is available inside the LXC. Exact package names and versions vary depending on whether you’re using Debian’s NVIDIA packages or NVIDIA’s own repository, so keep them aligned with the host driver branch.

If you deliberately still use NVIDIA’s .run installer, installing the exact same driver version inside the LXC without kernel modules remains possible:

sudo sh ./NVIDIA-Linux-x86_64-<VERSION>.run --no-kernel-modules


I consider that the fallback method now, not the first choice. Package management + DKMS on the host is far less likely to turn a routine Proxmox update into a Saturday-night troubleshooting adventure.

6. Test NVIDIA from inside the LXC

nvidia-smi


If that shows the GPU, driver information and no NVML error, you’re in business.

If you get:

Failed to initialize NVML: Driver/library version mismatch


compare the loaded host driver and the libraries installed inside the LXC. That’s usually version drift, not a Proxmox device-passthrough problem.

7. Actually test hardware acceleration

nvidia-smi proving the GPU exists is nice, but it doesn’t prove Jellyfin/Plex can actually use NVENC/NVDEC.

Start a forced transcode and watch the GPU:

watch -n 1 nvidia-smi


You should see the transcoder process appear and GPU/video-engine activity increase.

For Jellyfin, select NVIDIA NVENC in the transcoding settings and only enable codecs your GPU actually supports. Current Jellyfin documentation is the best source for the codec/support matrix because NVIDIA’s generations and encode-session limits keep changing.

Can I share the same GPU with multiple LXCs?

Yes — and this remains one of the nicest reasons to do LXC device sharing instead of PCIe passthrough.

Because the NVIDIA driver remains owned by the Proxmox host, you can expose the same /dev/nvidia* device nodes to more than one LXC.

Obviously they still share the same physical GPU resources, VRAM and hardware encoder limits. We haven’t invented free GPUs yet. Bloody disappointing.

What if my app runs in Docker inside the LXC?

Then you’ve added another layer to the onion 🙂

This guide gets the NVIDIA GPU from Proxmox → LXC. If Jellyfin/Plex is then running inside Docker within that LXC, Docker also needs access to the NVIDIA devices/userspace stack. NVIDIA Container Toolkit or explicit Docker device mappings may be required depending on the image and runtime.

Always get nvidia-smi working directly inside the LXC first. Only then troubleshoot Docker. Otherwise you’re trying to debug two layers at once and life is too short.

Old method — archived

ARCHIVED — PRE-PROXMOX 8.1 METHOD

The old guide manually allowed NVIDIA character-device major numbers through cgroups and bind-mounted each device into the container. This is preserved only so old search results and existing installations make sense.

The old container configuration looked roughly like this:

lxc.cgroup2.devices.allow: c 195:* rwm
lxc.cgroup2.devices.allow: c 243:* rwm

lxc.mount.entry: /dev/nvidia0 dev/nvidia0 none bind,optional,create=file
lxc.mount.entry: /dev/nvidiactl dev/nvidiactl none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-uvm dev/nvidia-uvm none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-modeset dev/nvidia-modeset none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-uvm-tools dev/nvidia-uvm-tools none bind,optional,create=file


Current Proxmox Device Passthrough replaces both the manual cgroup permission rules and the bind mounts, including for unprivileged containers.

Old udev chmod hack — also archived

The original article also created a custom udev rule that ran nvidia-smi/nvidia-modprobe and then did a wildcard chmod 666 /dev/nvidia* on the host.

I wouldn’t do that by default now. Modern NVIDIA installations include nvidia-modprobe, whose job includes creating the /dev/nvidia* nodes when required, and Proxmox’s device passthrough can set the permissions of the container-side device node itself.

If the NVIDIA device nodes are missing on the host, diagnose the NVIDIA driver/module first rather than immediately masking it with a global permissions rule.

Quick troubleshooting order

  1. nvidia-smi works on the Proxmox host.
  2. The expected /dev/nvidia* nodes exist on the host.
  3. Those nodes are configured with Proxmox Device Passthrough.
  4. The device nodes appear inside the LXC.
  5. The LXC has compatible NVIDIA userspace libraries.
  6. nvidia-smi works inside the LXC.
  7. Only then test Jellyfin/Plex hardware transcoding.

Do it in that order and you know exactly which layer went cactus.

References

Much less black magic than the original version. The NVIDIA driver can still throw the occasional tantrum after a kernel update, obviously — it wouldn’t be NVIDIA on Linux otherwise 🙂