You Ticked the Box. The CPU Is Still on Fire.
You opened Dashboard, found the transcoding page, picked a hardware acceleration method, and hit Save. A guest starts a movie on a phone, the stream stutters, and htop shows every core pinned. The setting looks enabled. The GPU is asleep.
Jellyfin does not warn you when a transcode lands on the CPU. A broken device mapping is loud: playback fails the moment a transcode starts. The quiet cases are worse. A codec left unticked under hardware decoding, “Enable hardware encoding” left off, or a Blu-ray folder rip (Jellyfin only picks hardware encoders for plain video files) all produce an FFmpeg command with software decode or libx264, and the stream still plays. Another checkbox won’t fix that. Evidence will: proof that the encoder is a hardware one and the GPU engines are busy.
Below: both common routes in Docker (Intel Quick Sync and NVIDIA NVENC), then a proof checklist you can run in two minutes. Target version: Jellyfin 12.x (12.2 is current as of October 2026), which ships jellyfin-ffmpeg built on FFmpeg 8.1. Jellyfin moved from 10.x numbering to 12.0 in September 2026, and upgrades from 10.10.7 and 10.11.x are supported.
Full example: Clone the working files at github.com/KingPin/sumguy-examples/self-hosting/jellyfin-hardware-transcoding
First, Make Sure You Actually Need a Transcode
The cheapest transcode is the one that never happens. Jellyfin has four playback types, from lowest to highest server load: Direct Play, Remux, Direct Stream, and Transcode. If your client can play the file as is, the GPU does nothing and that is fine. A GPU that looks idle during Direct Play is not broken.
Force the issue when you test. Start a video in the web player, open the settings gear, and set the quality to something low like 720p at 2 Mbps. That demands a video transcode on any source.
Intel Quick Sync: The Route for Most Home Servers
Intel iGPUs (the N100 class of mini PCs included) are the cheapest, lowest-power way to serve home media: the encoder is already on the CPU die you paid for. On Linux, Jellyfin’s docs list QSV as the recommended method for Intel, and it needs Broadwell (5th gen Core) or newer.
On Intel, pick QSV, not VAAPI. VAAPI still works on nearly everything and is the only choice for pre-Broadwell GPUs, but QSV is the faster path on mainstream hardware. One caveat from the docs: Intel is phasing out QSV on Linux for Ice Lake, Jasper Lake, Elkhart Lake and older, so those chips may end up on VAAPI eventually.
The compose file
services: jellyfin: image: jellyfin/jellyfin:latest container_name: jellyfin user: 1000:1000 network_mode: host restart: unless-stopped group_add: - "${RENDER_GID:?run: export RENDER_GID=$(getent group render | cut -d: -f3)}" devices: - /dev/dri/renderD128:/dev/dri/renderD128 volumes: - /opt/jellyfin/config:/config - /opt/jellyfin/cache:/cache - /srv/media:/media:roTwo details matter here. The devices line hands the render node to the container. The group_add line gives the container user permission to open it. Miss the second one and the device is visible but unusable, which is the most common failure on this page.
Get the render group’s numeric ID from the host, not from inside the container:
getent group render | cut -d: -f3ls -l /dev/driUse the number, not the name render. The container has its own /etc/group, and its render entry often carries a different GID than your host’s, so the name can resolve to the wrong group. The Jellyfin docs show the same pattern with a numeric GID. A second GPU shows up as renderD129.
The Jellyfin settings
Go to Dashboard, Playback, Transcoding. Set “Hardware acceleration” to “Intel Quicksync (QSV)”. Then:
- Under “Enable hardware decoding for”, tick only the codecs your iGPU can decode.
- Tick “Enable hardware encoding”.
- If you tick HEVC or AV1 encoding (“Allow encoding in HEVC format”), make sure the GPU supports it.
Jellyfin’s Intel docs are blunt about this: enable QSV, and uncheck the unsupported codecs. Here is the generation table that matters:
| Codec | Needs |
|---|---|
| H.264 8-bit | Any QSV-capable GPU |
| HEVC 8-bit | 6th gen Core (Skylake) and newer |
| HEVC 10-bit | 7th gen Core (Kaby Lake), Apollo Lake, Gemini Lake and newer |
| AV1 decode | 11th gen Core (Tiger Lake) and newer |
| AV1 encode | Arc A-series, Meteor Lake and newer |
Get this wrong in either direction and you pay. Tick “HEVC 10-bit” on a 6th gen chip and Jellyfin asks the GPU to decode something it can’t, so those files fail to play. Untick a codec the GPU does support and Jellyfin decodes it on the CPU: a hardware-encode, software-decode hybrid that burns CPU while the GPU looks half busy. Jasper Lake and Elkhart Lake have no AV1 acceleration at all.
The low-power encoder and firmware
You will also see “Enable Intel Low-Power H.264 hardware encoder” and a matching HEVC option. These use the GPU’s HuC firmware. The docs say you only need them on Jasper Lake and Elkhart Lake, or when you want faster OpenCL tone-mapping on Linux. If you enable them without working GuC/HuC firmware, transcodes can fail. Jellyfin’s own help text says that on Linux the options must stay disabled if the i915 HuC firmware is not configured.
To enable GuC on older parts that don’t turn it on by default:
sudo sh -c "echo 'options i915 enable_guc=2' >> /etc/modprobe.d/i915.conf"sudo update-initramfs -u && sudo update-grubsudo rebootThen check that the firmware loaded without errors:
sudo dmesg | grep -E "i915|xe"sudo sh -c "cat /sys/kernel/debug/dri/0/gt*/uc/huc_info"You want no FAIL or ERROR lines. Skip all of this on 12th gen (ADL-P), 13th and 14th gen, and Arc A-series, which enable GuC by default. Battlemage and newer cards use the xe driver, where enable_guc does not apply.
NVIDIA NVENC: Fine, If You Already Own the Card
If a spare NVIDIA GPU already lives in the server, NVENC works well. Buying one just for Jellyfin is hiring a forklift to move a couch. Technically it works, but the Intel iGPU you already own carries that couch fine. Still, the setup is simple once the host is ready.
The host needs the NVIDIA driver and the NVIDIA Container Toolkit. The official Jellyfin image does not ship the driver.
services: jellyfin: image: jellyfin/jellyfin:latest container_name: jellyfin user: 1000:1000 network_mode: host restart: unless-stopped runtime: nvidia deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] volumes: - /opt/jellyfin/config:/config - /opt/jellyfin/cache:/cache - /srv/media:/media:roIn Dashboard, Playback, Transcoding, set “Hardware acceleration” to “Nvidia NVENC”, tick “Enable hardware encoding”, and untick codecs your card can’t handle. The “Enable enhanced NVDEC decoder” option switches between the newer NVDEC path and the older CUVID one. If you hit decoding errors, untick it to fall back to CUVID. Dolby Vision support needs it ticked.
Some low-end and mobile cards, such as the GT1030 and MX450, have no NVENC or NVDEC at all. Check NVIDIA’s codec support matrix before you blame Jellyfin.
The session limit
Consumer GeForce cards cap concurrent NVENC encode sessions. Jellyfin’s known-issues page ties the cap to the driver version: 590 and newer allow up to 12, 550 through 58x allow 8, 530 through 546 allow 5, and anything before 530 allows 3. NVIDIA’s support matrix lists 12 for current GeForce cards. When the limit is hit, extra streams fail or fall back to the CPU, so test with more than one stream before you declare victory.
Two other Docker gotchas from the same page. The container can randomly lose GPU access under Docker or LXC (the page links workarounds), and Docker Desktop is not supported by the NVIDIA Container Toolkit. If you see CUDA_ERROR_NO_DEVICE, the NVIDIA docs list extra device mappings such as /dev/nvidia-uvm and /dev/nvidiactl.
The Proof Checklist
Everything above is setup. Everything below is evidence. Start a forced transcode (low-quality setting in a client), leave it running, and work through these in order.
1. Can the container see the GPU?
docker exec -it jellyfin ls -l /dev/dridocker exec -it jellyfin /usr/lib/jellyfin-ffmpeg/vainfo --display drm --device /dev/dri/renderD128The first command must list renderD128. The second must print a driver and a list of profiles such as VAProfileH264Main and VAProfileHEVCMain. An iHD driver supports both QSV and VAAPI. An i965 driver supports VAAPI only, which means QSV will not work on that setup.
For NVIDIA:
docker exec -it jellyfin nvidia-smiIf that errors out, nothing else matters yet. Fix the container first.
2. What does Playback Info say?
While the stream plays, open the player’s settings and choose “Playback Info”. Look at the transcoding reasons. Reasons like VideoCodecNotSupported, AudioCodecNotSupported or SubtitleCodecNotSupported tell you why the server transcodes. They do not tell you whether the GPU does the work. That comes next.
3. Which encoder did FFmpeg actually use?
This is the check that settles arguments. Jellyfin writes one log per transcode into the log directory. In the official image that is /config/log, and the files are named like FFmpeg.Transcode-2026-10-19_13-00-00_<id>_<suffix>.log. The first lines contain the full FFmpeg command line, and Jellyfin always passes the video encoder as -codec:v:0 <name>.
docker exec jellyfin sh -c 'ls -t /config/log/FFmpeg.Transcode-*.log | head -1'docker exec jellyfin sh -c 'head -c 20000 "$(ls -t /config/log/FFmpeg.Transcode-*.log | head -1)"' \ | grep -oE -- '-codec:v:0 [a-z0-9_]+' | sort -uGrep for that exact token. A bare grep libx264 matches the FFmpeg banner further down the log, whose configuration: line lists every library jellyfin-ffmpeg was built with, so a working QSV setup looks broken.
Read the result like this:
-codec:v:0 h264_qsv # hardware encode on Intel QSV-codec:v:0 h264_nvenc # hardware encode on NVIDIA-codec:v:0 h264_vaapi # hardware encode via VAAPI-codec:v:0 libx264 # software. The CPU is doing the work.If you see -codec:v:0 libx264 and you thought hardware encoding was on, check that “Enable hardware encoding” is ticked and that the source is a plain video file, not a Blu-ray or DVD folder rip. If the stream refuses to start at all, scroll the log for the first error line instead: a failed device init, a permission denied on /dev/dri/renderD128, or an unsupported pixel format.
Also look at the decoder. The command line has a -hwaccel flag when hardware decode is requested. An h264_qsv encoder with no hardware decode means the CPU is decoding and the GPU is only encoding. That is better than nothing, though not the full win.
4. Are the GPU engines busy?
On an Intel host:
sudo apt install -y intel-gpu-toolssudo intel_gpu_topDuring a QSV transcode the Video engine should show real activity. The Jellyfin docs describe the engines: Video is QSV decode and encode, VideoEnhance is QSV VPP (scaling and similar filters), and Render/3D shows 2D/3D, QSV VPP or GPGPU work such as OpenCL tone-mapping. A transcode with a flat Video line is not on the GPU.
On NVIDIA:
nvidia-smiYou should see a jellyfin-ffmpeg process with VRAM in use in the process list. For a quick pass/fail on encoder sessions, nvidia-smi --query-gpu=encoder.stats.sessionCount --format=csv prints the active count.
5. Does the CPU agree?
docker stats jellyfin --no-streamA 1080p hardware transcode should cost a small slice of one core, mostly for audio and container work. If one stream eats several cores, you have a software step somewhere.
The one-command version
The example repo bundles the container checks, the GID comparison and the log grep into one script:
./check-hwaccel.sh jellyfinIt prints OK or FAIL for device visibility, group membership, render node access and the video encoder in the newest transcode log, lists any -hwaccel decode flags, and exits non-zero on a problem. Your 2 AM self will thank you for having it.
The Classic Failures, in Order of Frequency
The container can’t see /dev/dri
Symptom: playback fails as soon as a transcode starts, and ls /dev/dri inside the container errors. The cause is almost always a missing devices: entry, a render node with a different number (renderD129 on a box with two GPUs), or the host lacking the kernel driver. Run ls /dev/dri on the host first.
Render group GID mismatch
Symptom: ls /dev/dri works, but FFmpeg logs a permission denied on the render node. The container user is not in the group that owns the device. Use group_add with the numeric GID from getent group render on the host, as in the compose file above. Don’t hardcode 122 because a doc example said so. Your host picked its own number.
Wrong method
Symptom: hardware acceleration is set to VAAPI on a modern Intel chip, so you are on the slower path, or set to QSV on a setup that only has the i965 driver. Match the method to the driver vainfo reports.
Codec boxes that don’t match the GPU
Two symptoms, two causes. Files in one codec refuse to play while others work: you ticked AV1 on a 10th gen chip or HEVC 10-bit on a 6th gen chip, so untick what the table above says you lack. The CPU is busy even though the encoder is h264_qsv: that codec is unticked (or the GPU lacks it), so the log shows no -hwaccel flag and the CPU decodes.
HDR tone-mapping falls back
HDR to SDR conversion is expensive. Jellyfin does not do HDR-to-HDR tone mapping or metadata passthrough, so an HDR file on an SDR display always needs it. The docs call software tone-mapping very slow and recommend a GPU. Tone-mapping works on Intel GPUs with HEVC 10-bit decoding, and “Enable Tone mapping” must be ticked. On Intel you get two paths: OpenCL (which needs the compute runtime on Linux and supports Dolby Vision profile 5) or VPP (“Enable VPP Tone mapping”), which uses less power but works on limited hardware. NVIDIA uses CUDA. Jellyfin’s known-issues page also flags Linux kernels 5.18 through 6.1.3 as prone to lock up during OpenCL tone-mapping, and compute-runtime releases starting at 25.18.33578.6 as broken on certain GPUs.
Subtitle burn-in
Image-based subtitles such as PGS cannot be rendered by many clients, so the server paints them into the video. That forces a full video transcode even when the video codec was fine. Playback Info shows SubtitleCodecNotSupported. The GPU handles it if hardware encoding works, but the stream is no longer a cheap Direct Play. Switch the client to a text subtitle format like SRT, or turn subtitles off, when you test.
What 12.x Changed for Transcoding
Jellyfin 12.0 shipped with a fix for an int32 overflow in QSV rate control. It also fixed hevc_vaapi extradata on AMD and sped up OpenCL tone-mapping on Mali. Release 12.1 fixed transcode throttling not turning on because of a race condition. If a QSV stream misbehaved on 10.11, upgrade before you start tuning.
The SumGuy Take
Treat “hardware acceleration enabled” as a claim, not a fact. Run the log grep, glance at intel_gpu_top or nvidia-smi, and confirm h264_qsv or h264_nvenc is in the FFmpeg command line. Do that once after every driver, kernel or image update. A silent fallback to libx264 is invisible until the one night eight people watch something at once.
Common Questions
Does Jellyfin hardware transcoding work on an Intel N100?
Yes. The N100 has an Intel iGPU that supports Quick Sync, so Jellyfin hardware transcoding works through QSV. Pass /dev/dri/renderD128 into the container, add the host render GID with group_add, and select “Intel Quicksync (QSV)” in the transcoding settings. Then confirm with intel_gpu_top.
Do I need an NVIDIA GPU for Jellyfin hardware transcoding?
No. Intel iGPUs use Quick Sync and AMD GPUs use VAAPI on Linux. Jellyfin supports Intel, AMD, NVIDIA, Apple and Rockchip hardware. An NVIDIA card is only worth adding when you already own one, because a modern Intel iGPU handles typical home streams.
Why is Jellyfin still using my CPU with hardware acceleration enabled?
Jellyfin uses the CPU when “Enable hardware encoding” is off, when a codec is not ticked for hardware decoding, or when the source is a Blu-ray or DVD folder rip. Check the latest FFmpeg.Transcode-*.log for -codec:v:0 libx264 and tick only the codecs the GPU can decode. Burned-in image subtitles and HDR tone-mapping also add CPU load.
How many simultaneous NVENC streams can a GeForce card handle?
Current GeForce cards allow up to 12 concurrent NVENC sessions on driver 590 and newer. Older drivers allow fewer: 8 on 550 through 58x, 5 on 530 through 546, and 3 before 530. Sessions beyond the limit fail or fall back to the CPU.
Do I need to enable the Intel low-power encoder?
No, not on most chips. Jellyfin’s docs say the Low-Power encoder options are needed only on Jasper Lake and Elkhart Lake, or for faster OpenCL tone-mapping on Linux. Without working GuC and HuC firmware on Linux, leave both options unticked.