<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: vast cow</title>
    <description>The latest articles on DEV Community by vast cow (@vast-cow).</description>
    <link>https://dev.to/vast-cow</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3717506%2F91ec5ce0-4f1b-458d-b9ad-d524e0befb41.png</url>
      <title>DEV Community: vast cow</title>
      <link>https://dev.to/vast-cow</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vast-cow"/>
    <language>en</language>
    <item>
      <title>Streaming Video Over a Network with the Lowest Possible Latency Using FFmpeg + MediaMTX</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Tue, 11 Aug 2026 06:54:54 +0000</pubDate>
      <link>https://dev.to/vast-cow/streaming-video-over-a-network-with-the-lowest-possible-latency-using-ffmpeg-mediamtx-18pd</link>
      <guid>https://dev.to/vast-cow/streaming-video-over-a-network-with-the-lowest-possible-latency-using-ffmpeg-mediamtx-18pd</guid>
      <description>&lt;p&gt;When you want to send video captured from an HDMI capture device or similar source to another PC on the same LAN, one issue that can be surprisingly significant is &lt;strong&gt;video latency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you simply encode the video as H.264 and stream it over the network, small amounts of buffering can occur at multiple stages, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture&lt;/li&gt;
&lt;li&gt;FFmpeg’s internal queues&lt;/li&gt;
&lt;li&gt;Encoding&lt;/li&gt;
&lt;li&gt;Multiplexing&lt;/li&gt;
&lt;li&gt;Network transport&lt;/li&gt;
&lt;li&gt;Player-side buffering&lt;/li&gt;
&lt;li&gt;Decoding and display&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, the final latency can range from several hundred milliseconds to several seconds.&lt;/p&gt;

&lt;p&gt;This time, we will build a low-latency streaming setup that minimizes buffering as much as possible using the following configuration:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;V4L2 + ALSA → FFmpeg → MediaMTX → RTSP/UDP → ffplay&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the current FFmpeg documentation, &lt;code&gt;nobuffer&lt;/code&gt; is defined as an option for reducing latency caused by buffering during input analysis, while &lt;code&gt;low_delay&lt;/code&gt; is defined as a flag that forces low-delay operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;The setup used here looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HDMI input
   ↓
Capture device
 /dev/video0
   ↓
FFmpeg
  ├─ Video input via V4L2
  ├─ Audio input via ALSA
  ├─ H.264 encoding with Intel QSV
  ↓
RTSP / UDP
   ↓
MediaMTX
   ↓
LAN
   ↓
ffplay
   ↓
Display
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than having MediaMTX encode the video itself, &lt;strong&gt;we use it as an RTSP server that relays the stream&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;MediaMTX is a media server that supports publishing and reading real-time video and audio streams, including RTSP, and it can also launch external commands as hooks.&lt;/p&gt;

&lt;p&gt;This time, we will use its &lt;code&gt;runOnDemand&lt;/code&gt; feature.&lt;/p&gt;




&lt;h1&gt;
  
  
  MediaMTX Configuration
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;mediamtx.yml&lt;/code&gt; file is configured as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hdmi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runOnDemand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
      &lt;span class="s"&gt;ffmpeg -hide_banner -y&lt;/span&gt;
      &lt;span class="s"&gt;-loglevel warning&lt;/span&gt;
      &lt;span class="s"&gt;-fflags nobuffer&lt;/span&gt;
      &lt;span class="s"&gt;-flags low_delay&lt;/span&gt;
      &lt;span class="s"&gt;-init_hw_device qsv=qsv&lt;/span&gt;
      &lt;span class="s"&gt;-filter_hw_device qsv&lt;/span&gt;
      &lt;span class="s"&gt;-thread_queue_size 4&lt;/span&gt;
      &lt;span class="s"&gt;-f alsa&lt;/span&gt;
      &lt;span class="s"&gt;-ac 2&lt;/span&gt;
      &lt;span class="s"&gt;-ar 48000&lt;/span&gt;
      &lt;span class="s"&gt;-i hw:1,0&lt;/span&gt;
      &lt;span class="s"&gt;-thread_queue_size 1&lt;/span&gt;
      &lt;span class="s"&gt;-f v4l2&lt;/span&gt;
      &lt;span class="s"&gt;-input_format yuyv422&lt;/span&gt;
      &lt;span class="s"&gt;-video_size 1920x1080&lt;/span&gt;
      &lt;span class="s"&gt;-framerate 30&lt;/span&gt;
      &lt;span class="s"&gt;-i /dev/video0&lt;/span&gt;
      &lt;span class="s"&gt;-map 1:v:0&lt;/span&gt;
      &lt;span class="s"&gt;-map 0:a:0&lt;/span&gt;
      &lt;span class="s"&gt;-vf 'hwupload=extra_hw_frames=0,vpp_qsv=format=nv12'&lt;/span&gt;
      &lt;span class="s"&gt;-c:v h264_qsv&lt;/span&gt;
      &lt;span class="s"&gt;-preset veryfast&lt;/span&gt;
      &lt;span class="s"&gt;-global_quality:v 35&lt;/span&gt;
      &lt;span class="s"&gt;-async_depth 1&lt;/span&gt;
      &lt;span class="s"&gt;-bf 0&lt;/span&gt;
      &lt;span class="s"&gt;-g 30&lt;/span&gt;
      &lt;span class="s"&gt;-c:a libopus -b:a 192k&lt;/span&gt;
      &lt;span class="s"&gt;-f rtsp&lt;/span&gt;
      &lt;span class="s"&gt;-rtsp_transport udp&lt;/span&gt;
      &lt;span class="s"&gt;-muxdelay 0&lt;/span&gt;
      &lt;span class="s"&gt;rtsp://127.0.0.1:8554/hdmi&lt;/span&gt;
    &lt;span class="na"&gt;runOnDemandRestart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
    &lt;span class="na"&gt;runOnDemandStartTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
    &lt;span class="na"&gt;runOnDemandCloseAfter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this may seem like a lot of options, but from a low-latency perspective, it becomes easier to understand if you break them down into a few key areas.&lt;/p&gt;




&lt;h1&gt;
  
  
  Start FFmpeg Only When Needed with &lt;code&gt;runOnDemand&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;First, let’s look at the MediaMTX side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runOnDemand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
  &lt;span class="s"&gt;ffmpeg ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;runOnDemand&lt;/code&gt; is a feature that starts an external command when a client accesses the corresponding path.&lt;/p&gt;

&lt;p&gt;In other words, it works like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nobody is watching
↓
FFmpeg stopped

ffplay connects to /hdmi
↓
MediaMTX starts FFmpeg
↓
FFmpeg publishes to /hdmi
↓
Playback starts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The official MediaMTX documentation also describes &lt;code&gt;runOnDemand&lt;/code&gt; as a mechanism that starts the specified command when a reader requests the path.&lt;/p&gt;

&lt;p&gt;Because FFmpeg does not need to run continuously, this is convenient when you only want to use HDMI streaming when needed.&lt;/p&gt;

&lt;p&gt;In addition, we use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runOnDemandRestart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so that if FFmpeg exits for some reason, it will be restarted. The current MediaMTX configuration reference also defines this option as a setting that restarts the command after it exits.&lt;/p&gt;




&lt;h1&gt;
  
  
  Minimize Input-Side Buffers
&lt;/h1&gt;

&lt;p&gt;An important principle in low-latency streaming is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid “buffering first, processing later.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For that reason, the FFmpeg command begins with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-fflags nobuffer
-flags low_delay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;-fflags nobuffer&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-fflags nobuffer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setting reduces latency caused by buffering during input stream analysis. The official FFmpeg documentation also describes it as an option for reducing latency caused by buffering during initial input analysis.&lt;/p&gt;

&lt;p&gt;For real-time input, it is important to configure the pipeline in the following direction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Avoid accumulating packets as much as possible
↓
Pass incoming data to the next processing stage immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;-flags low_delay&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-flags low_delay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the name suggests, this flag configures codec processing for low latency. In FFmpeg, &lt;code&gt;low_delay&lt;/code&gt; is defined as “Force low delay.”&lt;/p&gt;




&lt;h1&gt;
  
  
  Capture Video and Audio from Separate Devices
&lt;/h1&gt;

&lt;p&gt;In this setup, audio is captured through ALSA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-f alsa
-ac 2
-ar 48000
-i hw:1,0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while video is captured through V4L2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-f v4l2
-input_format yuyv422
-video_size 1920x1080
-framerate 30
-i /dev/video0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From FFmpeg’s perspective, the inputs are therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input 0 = ALSA
input 1 = V4L2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output streams are explicitly selected with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-map 1:v:0
-map 0:a:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Video → video 0 from input 1
Audio → audio 0 from input 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Keep &lt;code&gt;thread_queue_size&lt;/code&gt; Small
&lt;/h1&gt;

&lt;p&gt;For the inputs, we specify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-thread_queue_size 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-thread_queue_size 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;thread_queue_size&lt;/code&gt; determines how many packets read from an input device or other source FFmpeg may retain in its internal queue. The official FFmpeg documentation describes it, for inputs, as the maximum number of queued packets when reading from a device or file.&lt;/p&gt;

&lt;p&gt;If you increase the queue size, behavior tends to become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Processing stalls slightly
↓
Packets accumulate in the queue
↓
Frames are processed without being dropped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in low-latency applications, this can become a problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Processing cannot keep up
↓
Old video accumulates in the queue
↓
Displayed video falls further and further behind real time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For that reason, we use very small values here.&lt;/p&gt;

&lt;p&gt;The design philosophy is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prioritize displaying video that is close to the current time over guaranteeing that no frame is ever dropped.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a very important concept in low-latency streaming.&lt;/p&gt;




&lt;h1&gt;
  
  
  Encode with Intel Quick Sync Video
&lt;/h1&gt;

&lt;p&gt;If 1920×1080 30 fps video is encoded to H.264 in software, the CPU load itself can become a source of latency.&lt;/p&gt;

&lt;p&gt;For that reason, we use Intel Quick Sync Video, commonly known as QSV.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-init_hw_device qsv=qsv
-filter_hw_device qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the video is uploaded to the QSV device and converted to NV12 with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-vf 'hwupload=extra_hw_frames=0,vpp_qsv=format=nv12'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The encoder is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-c:v h264_qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the Intel GPU’s hardware encoder to handle H.264 encoding.&lt;/p&gt;




&lt;h1&gt;
  
  
  Reduce Encoder “Lookahead” as Well
&lt;/h1&gt;

&lt;p&gt;This is where some of the most important low-latency settings appear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-preset veryfast
-global_quality:v 35
-async_depth 1
-bf 0
-g 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;-preset veryfast&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-preset veryfast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;QSV presets include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;veryfast
faster
fast
medium
slow
slower
veryslow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In FFmpeg, the &lt;code&gt;veryfast&lt;/code&gt; side prioritizes speed, while the &lt;code&gt;veryslow&lt;/code&gt; side prioritizes quality.&lt;/p&gt;

&lt;p&gt;For live streaming, rather than pushing encoding quality to the limit, we prioritize:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encoding each frame quickly and sending it to the network.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is why we choose &lt;code&gt;veryfast&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;code&gt;async_depth 1&lt;/code&gt; Is Important
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-async_depth 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setting also helps reduce latency.&lt;/p&gt;

&lt;p&gt;QSV can improve throughput by processing multiple frames asynchronously.&lt;/p&gt;

&lt;p&gt;However:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Process multiple frames in parallel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;also means that multiple frames may exist inside the encoder at the same time.&lt;/p&gt;

&lt;p&gt;The FFmpeg QSV documentation defines &lt;code&gt;async_depth&lt;/code&gt; as a parameter related to the number of asynchronous operations, and for the QSV decoder it explicitly notes that increasing the value also increases latency.&lt;/p&gt;

&lt;p&gt;For that reason, we reduce it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-async_depth 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is a simple pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frame input
↓
Encode
↓
Output immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We prioritize latency over throughput.&lt;/p&gt;




&lt;h1&gt;
  
  
  Do Not Use B-Frames
&lt;/h1&gt;

&lt;p&gt;For low-latency H.264, this setting is especially important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-bf 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When B-frames are used, encoding and decoding a frame may require referencing frames that occur later in time.&lt;/p&gt;

&lt;p&gt;Conceptually, the structure may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I P B B P
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, the encoder and decoder may need to reorder frames, which is unfavorable for low-latency use cases.&lt;/p&gt;

&lt;p&gt;Therefore, we completely disable B-frames with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-bf 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compression efficiency is sacrificed to some extent, but this is easier to handle in real-time applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Use a One-Second GOP
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-g 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is another key setting.&lt;/p&gt;

&lt;p&gt;Here, we use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-framerate 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so the frame rate is 30 fps.&lt;/p&gt;

&lt;p&gt;Therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30 frames ÷ 30 fps = 1 second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which means the GOP is divided at roughly one-second intervals.&lt;/p&gt;

&lt;p&gt;In FFmpeg, &lt;code&gt;-g&lt;/code&gt; specifies the GOP size.&lt;/p&gt;

&lt;p&gt;Shortening the GOP slightly reduces compression efficiency, but it makes the stream easier to handle in situations such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Playback startup&lt;/li&gt;
&lt;li&gt;Recovery after packet loss&lt;/li&gt;
&lt;li&gt;Joining the stream mid-session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For low-latency applications, compression ratio is not the only concern. &lt;strong&gt;It is also important to catch up to the current video quickly.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;code&gt;global_quality:v 35&lt;/code&gt;
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-global_quality:v 35
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets the QSV quality level.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;global_quality:v&lt;/code&gt; is specified with &lt;code&gt;h264_qsv&lt;/code&gt;, quality-based rate control such as ICQ may be used depending on the conditions. The FFmpeg QSV documentation states that the ICQ range is 1–51, with &lt;strong&gt;1 being the highest quality&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In other words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Smaller value
↓
Higher quality, larger data volume

Larger value
↓
Lower quality, smaller data volume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;35&lt;/code&gt; is fairly compression-oriented, so you can adjust it while monitoring network bandwidth and image quality.&lt;/p&gt;

&lt;p&gt;For example, if you want better quality, you could lower it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-global_quality:v 28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and compare the result.&lt;/p&gt;




&lt;h1&gt;
  
  
  Audio Is Opus at 192 kbps
&lt;/h1&gt;

&lt;p&gt;Audio is configured as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-c:a libopus -b:a 192k
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared with video, audio encoding has relatively low computational and bandwidth requirements, so we use Opus at 192 kbps.&lt;/p&gt;

&lt;p&gt;However, for monitoring applications where you only need to see the video, you can remove audio with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-an
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to simplify the setup further.&lt;/p&gt;




&lt;h1&gt;
  
  
  Use UDP for RTSP
&lt;/h1&gt;

&lt;p&gt;FFmpeg outputs to MediaMTX with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-f rtsp
-rtsp_transport udp
-muxdelay 0
rtsp://127.0.0.1:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For reducing latency, the key setting is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rtsp_transport udp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FFmpeg allows UDP, TCP, and other methods to be selected as the RTSP lower transport. With UDP, media is sent over UDP; with TCP, the data is interleaved inside the RTSP control channel.&lt;/p&gt;

&lt;p&gt;Unlike TCP, UDP does not have a mechanism like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Packet loss
↓
Wait for retransmission
↓
Subsequent processing also waits until the packet arrives
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, it is well suited to real-time applications where the priority is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Even if the image becomes slightly corrupted, do not wait for old video—show the current video instead.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Naturally, however, video may become corrupted in environments where packet loss is more likely, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wi-Fi&lt;/li&gt;
&lt;li&gt;Congested LANs&lt;/li&gt;
&lt;li&gt;Streaming over the internet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If reliability is more important, another option is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rtsp_transport tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Low latency and transmission reliability are a tradeoff.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;code&gt;muxdelay 0&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;We also specify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-muxdelay 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;muxdelay&lt;/code&gt; is a delay-related parameter used on FFmpeg’s output side. The official FFmpeg RTSP sending example also includes a case using &lt;code&gt;-muxdelay 0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here, we push it even further with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-muxdelay 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to configure the system so that:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As little time as possible is spent waiting to batch packets together.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Do Not Make MediaMTX Encode the Video
&lt;/h1&gt;

&lt;p&gt;An important part of this setup is that FFmpeg publishes once to the local MediaMTX instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FFmpeg
↓
rtsp://127.0.0.1:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clients then access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rtsp://x.x.x.x:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, MediaMTX acts as the relay point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FFmpeg
   ↓
MediaMTX
   ↓
Multiple clients
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, individual clients do not need direct access to the capture device or encoder.&lt;/p&gt;

&lt;p&gt;It is also easier to manage when distributing the stream to multiple devices.&lt;/p&gt;




&lt;h1&gt;
  
  
  Reduce Buffering on the ffplay Side as Well
&lt;/h1&gt;

&lt;p&gt;Even if the sender is optimized for low latency, there is little benefit if the receiving player buffers one second of video before starting playback.&lt;/p&gt;

&lt;p&gt;For that reason, ffplay is also configured as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffplay &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-fflags&lt;/span&gt; nobuffer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-flags&lt;/span&gt; low_delay &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-framedrop&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-rtsp_transport&lt;/span&gt; udp &lt;span class="se"&gt;\&lt;/span&gt;
  rtsp://x.x.x.x:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;code&gt;-fflags nobuffer&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As on the sending side, we specify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-fflags nobuffer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to reduce latency caused by buffering during input analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;-flags low_delay&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-flags low_delay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also configures decoding for low-latency operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;-framedrop&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-framedrop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is another important option for real-time applications.&lt;/p&gt;

&lt;p&gt;ffplay provides the &lt;code&gt;framedrop&lt;/code&gt; option, which allows video frames to be dropped when playback falls behind synchronization.&lt;/p&gt;

&lt;p&gt;For low-latency use, instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Faithfully displaying every single frame
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it is more important to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If processing falls behind, discard old frames
↓
Catch up to the current time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setting sacrifices some visual continuity in order to prevent latency from increasing.&lt;/p&gt;




&lt;h1&gt;
  
  
  In Low-Latency Streaming, “Dropping” Is Important
&lt;/h1&gt;

&lt;p&gt;Looking at all of these settings, there is a common philosophy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not retain old data any longer than necessary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In normal video playback, it is important to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Avoid packet loss
Avoid dropping frames
Keep playback smooth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, real-time video has different priorities.&lt;/p&gt;

&lt;p&gt;For example, when displaying a game screen or camera feed, it may be more useful to show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Video from around 100 ms ago, even if some frames are skipped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;than to perfectly display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Video from 3 seconds ago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For that reason, this setup reduces waiting time at every stage of the pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Small input queues
↓
No B-frames
↓
Reduced QSV asynchronous depth
↓
Reduced muxer waiting time
↓
UDP
↓
Reduced buffering in ffplay
↓
Drop frames if playback falls behind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Where Does Latency Occur?
&lt;/h1&gt;

&lt;p&gt;When optimizing for low latency, you need to look at the entire pipeline, not just the network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Capture
↓
Queue
↓
Filter
↓
Encode
↓
Mux
↓
Network
↓
Demux
↓
Decode
↓
Render
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, even if LAN ping is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Encoder: 100 ms
Player buffer: 500 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then improving the network further will not have much impact.&lt;/p&gt;

&lt;p&gt;In fact, with real-time video:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Buffers inside the encoder, decoder, and player can sometimes contribute more latency than the network itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is why this configuration uses so many low-latency options.&lt;/p&gt;




&lt;h1&gt;
  
  
  If You Want to Reduce Latency Even Further
&lt;/h1&gt;

&lt;p&gt;If latency is still noticeable with this configuration, there are several additional things you can try.&lt;/p&gt;

&lt;p&gt;First, ffplay’s RTSP reception uses a buffer for reordering UDP packets. The FFmpeg documentation states that packet reordering during UDP reception can be disabled by setting &lt;code&gt;max_delay&lt;/code&gt; to 0.&lt;/p&gt;

&lt;p&gt;For example, you can try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffplay &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-fflags&lt;/span&gt; nobuffer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-flags&lt;/span&gt; low_delay &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-framedrop&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-rtsp_transport&lt;/span&gt; udp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-max_delay&lt;/span&gt; 0 &lt;span class="se"&gt;\&lt;/span&gt;
  rtsp://x.x.x.x:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this further reduces tolerance for out-of-order packets, so video may become unstable depending on network quality.&lt;/p&gt;

&lt;p&gt;You can also reduce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-g 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-g 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, shorter GOPs generally reduce compression efficiency.&lt;/p&gt;

&lt;p&gt;Low-latency optimization always involves tradeoffs among:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Latency
Image quality
Bandwidth
Stability
CPU/GPU load
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  UDP Does Not Automatically Mean Low Latency
&lt;/h1&gt;

&lt;p&gt;One important point to keep in mind is that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UDP = always low latency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is not necessarily true.&lt;/p&gt;

&lt;p&gt;UDP can help avoid retransmission delays, but if network quality is poor, you may instead see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Packet loss
↓
Video corruption
↓
Wait until the next recoverable frame
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, the ideal environment is something like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wired LAN + UDP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;where packet loss is low and the network is stable.&lt;/p&gt;

&lt;p&gt;In a Wi-Fi environment, it is better to test both UDP and TCP and compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Actual latency
Video corruption
Stability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In this setup, we built the following pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HDMI
↓
V4L2 / ALSA
↓
FFmpeg
↓
Intel QSV H.264
↓
RTSP / UDP
↓
MediaMTX
↓
LAN
↓
ffplay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key to reducing latency is not simply using a fast encoder.&lt;/p&gt;

&lt;p&gt;What matters is removing, as much as possible, the buffers at each stage that exist to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Hold a little data just in case.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The main settings used here can be summarized as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-fflags nobuffer
    Reduce input-side buffering

-flags low_delay
    Configure codecs for low latency

-thread_queue_size
    Keep capture input queues small

-c:v h264_qsv
    Encode H.264 quickly with Intel QSV

-preset veryfast
    Prioritize encoding speed

-async_depth 1
    Reduce the asynchronous processing depth inside QSV

-bf 0
    Disable B-frames

-g 30
    About a one-second GOP at 30 fps

-rtsp_transport udp
    Favor avoiding retransmission delays

-muxdelay 0
    Reduce waiting time in the muxer

-framedrop
    Drop old frames if playback falls behind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important thing to understand about low-latency video streaming is that:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Deliver every frame reliably” and “deliver the current video” are different goals.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For recording, not losing frames is important.&lt;/p&gt;

&lt;p&gt;On the other hand, for real-time monitoring, remote control, game screens, camera surveillance, and similar applications, it is often more important to display the latest video even if a few frames are missing than to receive old video perfectly.&lt;/p&gt;

&lt;p&gt;By tuning the capture, encoding, network, and player stages around that principle, you can achieve very low-latency video transmission even with RTSP.&lt;/p&gt;

&lt;h1&gt;
  
  
  FFmpeg + MediaMTXで映像をできるだけ低遅延にネットワーク配信する
&lt;/h1&gt;

&lt;p&gt;HDMIキャプチャなどから取り込んだ映像をLAN内の別PCへ送りたいとき、意外と問題になるのが&lt;strong&gt;映像の遅延&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;普通にH.264へエンコードしてネットワーク配信すると、&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;キャプチャ&lt;/li&gt;
&lt;li&gt;FFmpeg内部のキュー&lt;/li&gt;
&lt;li&gt;エンコード&lt;/li&gt;
&lt;li&gt;マルチプレクサ&lt;/li&gt;
&lt;li&gt;ネットワーク&lt;/li&gt;
&lt;li&gt;プレイヤー側のバッファ&lt;/li&gt;
&lt;li&gt;デコード・表示&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;といった複数の段階で少しずつバッファリングされ、最終的には数百ms〜数秒の遅延になることがあります。&lt;/p&gt;

&lt;p&gt;そこで今回は、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;V4L2 + ALSA → FFmpeg → MediaMTX → RTSP/UDP → ffplay&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;という構成で、可能な限りバッファを減らした低遅延ストリーミングを構築します。&lt;/p&gt;

&lt;p&gt;FFmpegの現行ドキュメントでも、&lt;code&gt;nobuffer&lt;/code&gt; は入力解析時のバッファリングによる遅延を減らすためのオプション、&lt;code&gt;low_delay&lt;/code&gt; は低遅延動作を強制するフラグとして定義されています。&lt;/p&gt;




&lt;h2&gt;
  
  
  構成
&lt;/h2&gt;

&lt;p&gt;今回の構成は次のようになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HDMI入力
   ↓
キャプチャデバイス
 /dev/video0
   ↓
FFmpeg
  ├─ V4L2で映像入力
  ├─ ALSAで音声入力
  ├─ Intel QSVでH.264エンコード
  ↓
RTSP / UDP
   ↓
MediaMTX
   ↓
LAN
   ↓
ffplay
   ↓
画面表示
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MediaMTXには映像そのものをエンコードさせるのではなく、&lt;strong&gt;RTSPサーバーとして中継を担当させます&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;MediaMTXはRTSPを含むリアルタイム映像・音声ストリームのpublish/readに対応するメディアサーバーで、外部コマンドをhookとして起動することもできます。&lt;/p&gt;

&lt;p&gt;今回はその&lt;code&gt;runOnDemand&lt;/code&gt;機能を使います。&lt;/p&gt;




&lt;h1&gt;
  
  
  MediaMTXの設定
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;mediamtx.yml&lt;/code&gt;を次のようにしています。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hdmi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runOnDemand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
      &lt;span class="s"&gt;ffmpeg -hide_banner -y&lt;/span&gt;
      &lt;span class="s"&gt;-loglevel warning&lt;/span&gt;
      &lt;span class="s"&gt;-fflags nobuffer&lt;/span&gt;
      &lt;span class="s"&gt;-flags low_delay&lt;/span&gt;
      &lt;span class="s"&gt;-init_hw_device qsv=qsv&lt;/span&gt;
      &lt;span class="s"&gt;-filter_hw_device qsv&lt;/span&gt;
      &lt;span class="s"&gt;-thread_queue_size 4&lt;/span&gt;
      &lt;span class="s"&gt;-f alsa&lt;/span&gt;
      &lt;span class="s"&gt;-ac 2&lt;/span&gt;
      &lt;span class="s"&gt;-ar 48000&lt;/span&gt;
      &lt;span class="s"&gt;-i hw:1,0&lt;/span&gt;
      &lt;span class="s"&gt;-thread_queue_size 1&lt;/span&gt;
      &lt;span class="s"&gt;-f v4l2&lt;/span&gt;
      &lt;span class="s"&gt;-input_format yuyv422&lt;/span&gt;
      &lt;span class="s"&gt;-video_size 1920x1080&lt;/span&gt;
      &lt;span class="s"&gt;-framerate 30&lt;/span&gt;
      &lt;span class="s"&gt;-i /dev/video0&lt;/span&gt;
      &lt;span class="s"&gt;-map 1:v:0&lt;/span&gt;
      &lt;span class="s"&gt;-map 0:a:0&lt;/span&gt;
      &lt;span class="s"&gt;-vf 'hwupload=extra_hw_frames=0,vpp_qsv=format=nv12'&lt;/span&gt;
      &lt;span class="s"&gt;-c:v h264_qsv&lt;/span&gt;
      &lt;span class="s"&gt;-preset veryfast&lt;/span&gt;
      &lt;span class="s"&gt;-global_quality:v 35&lt;/span&gt;
      &lt;span class="s"&gt;-async_depth 1&lt;/span&gt;
      &lt;span class="s"&gt;-bf 0&lt;/span&gt;
      &lt;span class="s"&gt;-g 30&lt;/span&gt;
      &lt;span class="s"&gt;-c:a libopus -b:a 192k&lt;/span&gt;
      &lt;span class="s"&gt;-f rtsp&lt;/span&gt;
      &lt;span class="s"&gt;-rtsp_transport udp&lt;/span&gt;
      &lt;span class="s"&gt;-muxdelay 0&lt;/span&gt;
      &lt;span class="s"&gt;rtsp://127.0.0.1:8554/hdmi&lt;/span&gt;
    &lt;span class="na"&gt;runOnDemandRestart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
    &lt;span class="na"&gt;runOnDemandStartTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
    &lt;span class="na"&gt;runOnDemandCloseAfter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;一見するとオプションがかなり多いですが、低遅延化という観点ではいくつかのポイントに分けて考えると分かりやすくなります。&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;code&gt;runOnDemand&lt;/code&gt;で必要なときだけFFmpegを起動する
&lt;/h1&gt;

&lt;p&gt;まずMediaMTX側です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runOnDemand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
  &lt;span class="s"&gt;ffmpeg ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;runOnDemand&lt;/code&gt;は、クライアントからそのpathへのアクセスが発生したときに外部コマンドを起動する機能です。&lt;/p&gt;

&lt;p&gt;つまり、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;誰も見ていない
↓
FFmpeg停止

ffplayから /hdmi に接続
↓
MediaMTXがFFmpegを起動
↓
FFmpegが /hdmi にpublish
↓
再生開始
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という動作になります。&lt;/p&gt;

&lt;p&gt;MediaMTX公式ドキュメントでも、&lt;code&gt;runOnDemand&lt;/code&gt;に指定したコマンドはreaderからpathが要求されたタイミングで開始される仕組みになっています。&lt;/p&gt;

&lt;p&gt;常時FFmpegを動かしておく必要がないため、HDMI配信を必要なときだけ使いたい場合に便利です。&lt;/p&gt;

&lt;p&gt;さらに、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runOnDemandRestart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;としているので、FFmpegが何らかの理由で終了した場合にも再起動されます。現行のMediaMTX設定リファレンスにも、このオプションはコマンド終了時に再起動する設定として定義されています。&lt;/p&gt;




&lt;h1&gt;
  
  
  入力側のバッファを極力小さくする
&lt;/h1&gt;

&lt;p&gt;低遅延ストリーミングで重要なのが、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;「溜めてから処理する」のを避けること&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;です。&lt;/p&gt;

&lt;p&gt;そのためFFmpegの冒頭で、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-fflags nobuffer
-flags low_delay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を指定しています。&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;-fflags nobuffer&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-fflags nobuffer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;は入力ストリーム解析時に発生するバッファリングによる遅延を減らすための設定です。FFmpeg公式ドキュメントでも、初期入力解析時のバッファリングによるレイテンシーを削減するオプションとされています。&lt;/p&gt;

&lt;p&gt;リアルタイム入力では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;できるだけパケットを貯めない
↓
届いたデータをすぐ次の処理へ渡す
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という方向に設定しておくことが重要です。&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;-flags low_delay&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-flags low_delay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;も名前の通り、コーデック処理を低遅延方向へ設定するためのフラグです。FFmpegでは&lt;code&gt;low_delay&lt;/code&gt;が「Force low delay」と定義されています。&lt;/p&gt;




&lt;h1&gt;
  
  
  映像と音声を別デバイスから入力する
&lt;/h1&gt;

&lt;p&gt;今回の構成では音声をALSA、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-f alsa
-ac 2
-ar 48000
-i hw:1,0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;映像をV4L2、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-f v4l2
-input_format yuyv422
-video_size 1920x1080
-framerate 30
-i /dev/video0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;から取り込んでいます。&lt;/p&gt;

&lt;p&gt;したがってFFmpegから見ると入力は、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input 0 = ALSA
input 1 = V4L2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;になります。&lt;/p&gt;

&lt;p&gt;出力するストリームを、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-map 1:v:0
-map 0:a:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;と明示しています。&lt;/p&gt;

&lt;p&gt;つまり、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;映像 → input 1のvideo 0
音声 → input 0のaudio 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;です。&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;code&gt;thread_queue_size&lt;/code&gt;を小さくする
&lt;/h1&gt;

&lt;p&gt;入力にはそれぞれ、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-thread_queue_size 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-thread_queue_size 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を指定しています。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;thread_queue_size&lt;/code&gt;は、入力デバイスなどから読み込んだパケットをFFmpeg内部で何個までキューに保持するかを決める設定です。FFmpeg公式ドキュメントでも、入力の場合はデバイスやファイルから読み込む際のqueued packetsの最大数と説明されています。&lt;/p&gt;

&lt;p&gt;キューを大きくすると、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;多少処理が詰まる
↓
キューへ蓄積
↓
フレームを捨てず処理
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;しやすくなります。&lt;/p&gt;

&lt;p&gt;一方で低遅延用途では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;処理が追いつかない
↓
過去の映像がキューへ溜まる
↓
表示がどんどん現実時間から遅れる
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という問題になります。&lt;/p&gt;

&lt;p&gt;そのため今回はかなり小さい値にしています。&lt;/p&gt;

&lt;p&gt;これは、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;フレームを絶対に落とさないことより、現在時刻に近い映像を表示することを優先する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;という設計です。&lt;/p&gt;

&lt;p&gt;低遅延ストリーミングでは非常に重要な考え方です。&lt;/p&gt;




&lt;h1&gt;
  
  
  Intel Quick Sync Videoでエンコードする
&lt;/h1&gt;

&lt;p&gt;1920×1080 30fpsの映像をH.264へソフトウェアエンコードすると、CPU負荷によっては処理そのものが遅延原因になります。&lt;/p&gt;

&lt;p&gt;そこでIntel Quick Sync Video、いわゆるQSVを利用しています。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-init_hw_device qsv=qsv
-filter_hw_device qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;そして映像を、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-vf 'hwupload=extra_hw_frames=0,vpp_qsv=format=nv12'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;でQSV側へアップロードし、NV12へ変換します。&lt;/p&gt;

&lt;p&gt;エンコーダーは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-c:v h264_qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;です。&lt;/p&gt;

&lt;p&gt;これによってH.264エンコードをIntel GPUのハードウェアエンコーダーへ担当させます。&lt;/p&gt;




&lt;h1&gt;
  
  
  エンコーダーでも「先読み」を減らす
&lt;/h1&gt;

&lt;p&gt;ここからが低遅延化の重要部分です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-preset veryfast
-global_quality:v 35
-async_depth 1
-bf 0
-g 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;としています。&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;-preset veryfast&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-preset veryfast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;QSVのpresetは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;veryfast
faster
fast
medium
slow
slower
veryslow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という選択肢があり、FFmpegでは&lt;code&gt;veryfast&lt;/code&gt;側が速度優先、&lt;code&gt;veryslow&lt;/code&gt;側が画質優先として定義されています。&lt;/p&gt;

&lt;p&gt;ライブ配信ではエンコード品質を限界まで高めるより、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1フレームを素早くエンコードしてネットワークへ送る&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ことを優先します。&lt;/p&gt;

&lt;p&gt;そのため&lt;code&gt;veryfast&lt;/code&gt;を選択しています。&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;code&gt;async_depth 1&lt;/code&gt;が重要
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-async_depth 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;も低遅延化に効く設定です。&lt;/p&gt;

&lt;p&gt;QSVは複数フレームを非同期に処理することでスループットを高められます。&lt;/p&gt;

&lt;p&gt;ただし、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;複数フレームを並列処理
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;するということは、逆に言えばエンコーダー内部に複数フレームが存在することにもなります。&lt;/p&gt;

&lt;p&gt;FFmpegのQSVドキュメントでも&lt;code&gt;async_depth&lt;/code&gt;は非同期処理数に関係するパラメータとして定義されており、QSV decoderについては値を増やすほどレイテンシーも増えると明記されています。&lt;/p&gt;

&lt;p&gt;そこで、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-async_depth 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;まで下げています。&lt;/p&gt;

&lt;p&gt;狙っているのは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;フレーム入力
↓
エンコード
↓
すぐ出力
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という単純なパイプラインです。&lt;/p&gt;

&lt;p&gt;スループットよりlatencyを優先しています。&lt;/p&gt;




&lt;h1&gt;
  
  
  Bフレームを使わない
&lt;/h1&gt;

&lt;p&gt;低遅延H.264では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-bf 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;が特に重要です。&lt;/p&gt;

&lt;p&gt;Bフレームを使用すると、あるフレームをエンコード・デコードする際に未来側のフレームを参照することがあります。&lt;/p&gt;

&lt;p&gt;概念的には、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I P B B P
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;のような構造です。&lt;/p&gt;

&lt;p&gt;そのためエンコーダーやデコーダーでフレーム並び替えが必要になり、低遅延用途では不利になります。&lt;/p&gt;

&lt;p&gt;そこで、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-bf 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;としてBフレームを完全に無効化しています。&lt;/p&gt;

&lt;p&gt;圧縮効率は多少犠牲になりますが、リアルタイム用途ではこちらの方が扱いやすくなります。&lt;/p&gt;




&lt;h1&gt;
  
  
  GOPを1秒にする
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-g 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;もポイントです。&lt;/p&gt;

&lt;p&gt;今回は、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-framerate 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;なので30fpsです。&lt;/p&gt;

&lt;p&gt;したがって、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30フレーム ÷ 30fps = 1秒
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となり、おおよそ1秒ごとにGOPが区切られます。&lt;/p&gt;

&lt;p&gt;FFmpegでは&lt;code&gt;-g&lt;/code&gt;はGOP sizeを指定するパラメータです。&lt;/p&gt;

&lt;p&gt;GOPを短くすると圧縮効率は多少悪くなりますが、&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;再生開始&lt;/li&gt;
&lt;li&gt;パケットロス後の復帰&lt;/li&gt;
&lt;li&gt;ストリームへの途中参加&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;といった場面で扱いやすくなります。&lt;/p&gt;

&lt;p&gt;低遅延用途では圧縮率だけでなく、&lt;strong&gt;素早く現在の映像へ追いつけること&lt;/strong&gt;も重要です。&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;code&gt;global_quality:v 35&lt;/code&gt;
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-global_quality:v 35
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ではQSVの品質を指定しています。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;h264_qsv&lt;/code&gt;で&lt;code&gt;global_quality:v&lt;/code&gt;を指定すると、条件に応じてICQなどの品質ベースのレート制御が利用されます。FFmpegのQSVドキュメントではICQ時の範囲は1〜51で、&lt;strong&gt;1が最高品質&lt;/strong&gt;とされています。&lt;/p&gt;

&lt;p&gt;つまり、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;小さい値
↓
高画質・大きなデータ量

大きい値
↓
低画質・小さなデータ量
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という方向です。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;35&lt;/code&gt;はかなり圧縮寄りの設定なので、ネットワーク帯域や画質を見ながら調整できます。&lt;/p&gt;

&lt;p&gt;たとえば画質を上げるなら、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-global_quality:v 28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;などへ下げて比較すると分かりやすいでしょう。&lt;/p&gt;




&lt;h1&gt;
  
  
  音声はOpus 192kbps
&lt;/h1&gt;

&lt;p&gt;音声は、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-c:a libopus -b:a 192k
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;としています。&lt;/p&gt;

&lt;p&gt;映像に比べれば音声エンコードの負荷や帯域は小さいため、Opus 192kbpsとしています。&lt;/p&gt;

&lt;p&gt;ただし「映像だけ確認できればよい」という監視用途などでは音声を削除して、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-an
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;とすることで構成をさらに単純化できます。&lt;/p&gt;




&lt;h1&gt;
  
  
  RTSPはUDPを使う
&lt;/h1&gt;

&lt;p&gt;FFmpegからMediaMTXへの出力は、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-f rtsp
-rtsp_transport udp
-muxdelay 0
rtsp://127.0.0.1:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;です。&lt;/p&gt;

&lt;p&gt;低遅延化では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rtsp_transport udp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;がポイントです。&lt;/p&gt;

&lt;p&gt;FFmpegではRTSPのlower transportとしてUDPまたはTCPなどを選択できます。UDPの場合はUDP、TCPの場合はRTSP control channel内へインターリーブしてデータが流れます。&lt;/p&gt;

&lt;p&gt;UDPにはTCPのような、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;パケット消失
↓
再送待ち
↓
到着するまで後続処理も待つ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という仕組みがありません。&lt;/p&gt;

&lt;p&gt;そのため、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;多少映像が乱れてもよいので、古い映像を待たず現在の映像を出す&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;というリアルタイム用途と相性がよい方式です。&lt;/p&gt;

&lt;p&gt;ただし当然ながら、&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wi-Fi&lt;/li&gt;
&lt;li&gt;混雑したLAN&lt;/li&gt;
&lt;li&gt;インターネット越し&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;などパケットロスが発生しやすい環境では映像が乱れる可能性があります。&lt;/p&gt;

&lt;p&gt;逆に安定性を優先するなら、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rtsp_transport tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という選択肢もあります。&lt;/p&gt;

&lt;p&gt;低遅延と伝送の確実性はトレードオフです。&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;code&gt;muxdelay 0&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;さらに、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-muxdelay 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;としています。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;muxdelay&lt;/code&gt;はFFmpegの出力側で使用される遅延関連パラメータです。FFmpeg公式のRTSP送信例でも&lt;code&gt;-muxdelay 0.1&lt;/code&gt;を指定した例があります。&lt;/p&gt;

&lt;p&gt;今回はさらに攻めて、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-muxdelay 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;とし、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;パケットをまとめるために待つ時間を極力発生させない&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;方向へ寄せています。&lt;/p&gt;




&lt;h1&gt;
  
  
  MediaMTX自身にはエンコードさせない
&lt;/h1&gt;

&lt;p&gt;今回の構成で重要なのは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FFmpeg
↓
rtsp://127.0.0.1:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;として、一度localhostのMediaMTXへpublishしていることです。&lt;/p&gt;

&lt;p&gt;クライアントは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rtsp://x.x.x.x:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;へアクセスします。&lt;/p&gt;

&lt;p&gt;つまりMediaMTXは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FFmpeg
   ↓
MediaMTX
   ↓
複数クライアント
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という中継点になります。&lt;/p&gt;

&lt;p&gt;この構成にすると、キャプチャデバイスやエンコーダーを各クライアントが直接触る必要がありません。&lt;/p&gt;

&lt;p&gt;複数端末へ配信したい場合にも扱いやすくなります。&lt;/p&gt;




&lt;h1&gt;
  
  
  ffplay側でもバッファを削る
&lt;/h1&gt;

&lt;p&gt;送信側を低遅延化しても、受信側のプレイヤーが映像を1秒溜めてから再生していたら意味がありません。&lt;/p&gt;

&lt;p&gt;そこでffplay側も、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffplay &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-fflags&lt;/span&gt; nobuffer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-flags&lt;/span&gt; low_delay &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-framedrop&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-rtsp_transport&lt;/span&gt; udp &lt;span class="se"&gt;\&lt;/span&gt;
  rtsp://x.x.x.x:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;としています。&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;-fflags nobuffer&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;送信側と同様に、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-fflags nobuffer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を指定し、入力解析時のバッファリングによるレイテンシーを減らします。&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;-flags low_delay&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-flags low_delay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;でデコードも低遅延方向へ設定します。&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;-framedrop&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-framedrop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;もリアルタイム用途では重要です。&lt;/p&gt;

&lt;p&gt;ffplayでは、映像が同期から遅れた場合にvideo frameをdropできるオプションとして&lt;code&gt;framedrop&lt;/code&gt;が用意されています。&lt;/p&gt;

&lt;p&gt;低遅延用途では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;すべてのフレームを律儀に表示する
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;よりも、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;処理が遅れたら古いフレームを捨てる
↓
現在時刻へ追いつく
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ほうが重要です。&lt;/p&gt;

&lt;p&gt;これは映像品質を多少犠牲にしてでも遅延を増大させないための設定です。&lt;/p&gt;




&lt;h1&gt;
  
  
  低遅延配信では「捨てる」ことが重要
&lt;/h1&gt;

&lt;p&gt;ここまでの設定を見ると、共通した思想があります。&lt;/p&gt;

&lt;p&gt;それは、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;古いデータをなるべく保持しない&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ということです。&lt;/p&gt;

&lt;p&gt;通常の動画再生では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;パケットロスさせない
フレームを落とさない
映像を滑らかにする
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ことが重要です。&lt;/p&gt;

&lt;p&gt;しかしリアルタイム映像では事情が違います。&lt;/p&gt;

&lt;p&gt;例えばゲーム画面やカメラ映像を表示するとき、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3秒前の映像を完璧に表示
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;することより、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;多少フレームが飛んでも100ms前後の映像を表示
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;できるほうが有用な場合があります。&lt;/p&gt;

&lt;p&gt;そのため今回の設定では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;小さい入力キュー
↓
Bフレームなし
↓
QSVの非同期深度を削減
↓
muxerの待ち時間を削減
↓
UDP
↓
ffplayでもバッファ削減
↓
遅れたらframe drop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という形で、パイプラインの各段階から待ち時間を削っています。&lt;/p&gt;




&lt;h1&gt;
  
  
  遅延はどこで発生するのか
&lt;/h1&gt;

&lt;p&gt;低遅延化するときは、ネットワークだけを見るのではなく、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Capture
↓
Queue
↓
Filter
↓
Encode
↓
Mux
↓
Network
↓
Demux
↓
Decode
↓
Render
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;というパイプライン全体を見る必要があります。&lt;/p&gt;

&lt;p&gt;例えばLANのpingが、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;だったとしても、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;エンコーダー 100ms
プレイヤーバッファ 500ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;なら、ネットワークをいくら改善しても大きな効果はありません。&lt;/p&gt;

&lt;p&gt;むしろリアルタイム動画の場合、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ネットワークそのものよりエンコーダー・デコーダー・プレイヤー内部のバッファが大きな遅延源になる&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ことがあります。&lt;/p&gt;

&lt;p&gt;今回多数の低遅延オプションを指定しているのはそのためです。&lt;/p&gt;




&lt;h1&gt;
  
  
  さらに遅延を詰めたい場合
&lt;/h1&gt;

&lt;p&gt;この設定でもまだ遅延が気になる場合、いくつか試せるポイントがあります。&lt;/p&gt;

&lt;p&gt;まずffplayのRTSP受信では、UDPパケットの並び替え用バッファが存在します。FFmpegドキュメントでは、UDP受信時のpacket reorderingを&lt;code&gt;max_delay&lt;/code&gt;を0にすることで無効化できるとされています。&lt;/p&gt;

&lt;p&gt;例えば、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffplay &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-fflags&lt;/span&gt; nobuffer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-flags&lt;/span&gt; low_delay &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-framedrop&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-rtsp_transport&lt;/span&gt; udp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-max_delay&lt;/span&gt; 0 &lt;span class="se"&gt;\&lt;/span&gt;
  rtsp://x.x.x.x:8554/hdmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という設定も試せます。&lt;/p&gt;

&lt;p&gt;ただしこれはパケット順序の乱れに対する耐性をさらに削るため、ネットワーク品質によっては映像が不安定になります。&lt;/p&gt;

&lt;p&gt;また、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-g 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-g 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;などへ縮めることもできます。&lt;/p&gt;

&lt;p&gt;ただしGOPを短くするほど一般に圧縮効率は悪化します。&lt;/p&gt;

&lt;p&gt;低遅延化には常に、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;遅延
画質
帯域
安定性
CPU/GPU負荷
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;のトレードオフがあります。&lt;/p&gt;




&lt;h1&gt;
  
  
  UDPだから必ず低遅延、ではない
&lt;/h1&gt;

&lt;p&gt;注意したいのが、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UDP = 必ず低遅延
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ではないということです。&lt;/p&gt;

&lt;p&gt;UDPにすると再送待ちを避けやすい一方、ネットワーク品質が悪い場合は、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packet loss
↓
映像破損
↓
次の復旧可能なフレームまで待つ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ということもあります。&lt;/p&gt;

&lt;p&gt;したがって実際には、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;有線LAN + UDP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;のような、低ロスかつ安定したネットワークで使うのが理想的です。&lt;/p&gt;

&lt;p&gt;Wi-Fi環境ならUDPとTCPを両方試し、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;実際の遅延
映像の乱れ
安定性
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を比較したほうがよいでしょう。&lt;/p&gt;




&lt;h1&gt;
  
  
  まとめ
&lt;/h1&gt;

&lt;p&gt;今回の構成では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HDMI
↓
V4L2 / ALSA
↓
FFmpeg
↓
Intel QSV H.264
↓
RTSP / UDP
↓
MediaMTX
↓
LAN
↓
ffplay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;というパイプラインを作りました。&lt;/p&gt;

&lt;p&gt;低遅延化のポイントは、単に高速なエンコーダーを使用することではありません。&lt;/p&gt;

&lt;p&gt;各段階で発生する、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;「念のため少し溜めておく」バッファを可能な限り取り除くこと&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;が重要です。&lt;/p&gt;

&lt;p&gt;今回の主要な設定をまとめると、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-fflags nobuffer
    入力側のバッファリングを削減

-flags low_delay
    コーデックを低遅延方向へ

-thread_queue_size
    キャプチャ入力のキューを小さくする

-c:v h264_qsv
    Intel QSVで高速にH.264エンコード

-preset veryfast
    エンコード速度優先

-async_depth 1
    QSV内部の非同期処理深度を削減

-bf 0
    Bフレームを無効化

-g 30
    30fpsなら約1秒GOP

-rtsp_transport udp
    再送待ちを避ける方向へ

-muxdelay 0
    muxer側の待ち時間を削減

-framedrop
    再生が遅れたら古いフレームを捨てる
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となります。&lt;/p&gt;

&lt;p&gt;低遅延映像配信で大切なのは、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;「すべてのフレームを確実に届ける」ことと「今の映像を届ける」ことは別の目標&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;だと理解することです。&lt;/p&gt;

&lt;p&gt;録画ならフレームを失わないことが重要です。&lt;/p&gt;

&lt;p&gt;一方、リアルタイムモニター、遠隔操作、ゲーム画面、カメラ監視などでは、古い映像が完全に届くよりも、多少フレームが欠けても最新の映像が表示されることのほうが重要です。&lt;/p&gt;

&lt;p&gt;その方針でキャプチャ・エンコード・ネットワーク・プレイヤーのすべてを調整すると、RTSPでもかなり低遅延な映像伝送を構成できます。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automatically Renewing Tailscale HTTPS Certificates on PiKVM</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Mon, 10 Aug 2026 07:08:34 +0000</pubDate>
      <link>https://dev.to/vast-cow/automatically-renewing-tailscale-https-certificates-on-pikvm-58e9</link>
      <guid>https://dev.to/vast-cow/automatically-renewing-tailscale-https-certificates-on-pikvm-58e9</guid>
      <description>&lt;p&gt;It is appropriate to continue using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/kvmd/nginx/ssl/server.crt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/kvmd/nginx/ssl/server.key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in &lt;code&gt;/etc/kvmd/nginx/ssl.conf&lt;/code&gt;, with a systemd timer checking the certificate expiration and updating these two files only when necessary.&lt;/p&gt;

&lt;p&gt;The official PiKVM documentation also describes placing the Tailscale certificate in &lt;code&gt;/etc/kvmd/nginx/ssl/server.{crt,key}&lt;/code&gt;, setting the group to &lt;code&gt;kvmd-nginx&lt;/code&gt;, and then restarting &lt;code&gt;kvmd-nginx&lt;/code&gt;. (&lt;a href="[https://pikvm.github.io/pikvm/tailscale/](https://pikvm.github.io/pikvm/tailscale/)"&gt;Pikvm&lt;/a&gt;) Also, certificates obtained as files using &lt;code&gt;tailscale cert&lt;/code&gt; are not automatically renewed, so users need to implement their own renewal process. &lt;code&gt;--min-validity&lt;/code&gt; is also officially available in the current CLI. (&lt;a href="[https://tailscale.com/docs/reference/tailscale-cli](https://tailscale.com/docs/reference/tailscale-cli)"&gt;Tailscale&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;Normally, the setup looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tailscale
   │
   │ 100.x / MagicDNS
   ▼
PiKVM nginx :443
   │
   ├─ /etc/kvmd/nginx/ssl/server.crt
   └─ /etc/kvmd/nginx/ssl/server.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not use &lt;code&gt;tailscale serve&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale serve &lt;span class="nt"&gt;--https&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443 off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The certificate renewal process will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Timer runs once a day
        │
        ▼
Check current server.crt
        │
        ├─ FQDN is correct
        │  and at least 30 days remain
        │       → Do nothing
        │
        └─ Less than 30 days / no certificate / hostname mismatch
                │
                ▼
               rw
                │
                ▼
        tailscale cert
                │
                ▼
        Validate cert/key
                │
                ▼
        Replace nginx files
                │
                ▼
        nginx -t
                │
                ▼
        restart kvmd-nginx
                │
                ▼
               ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s Encrypt certificates are valid for 90 days, so attempting renewal starting 30 days before expiration provides plenty of margin. (&lt;a href="[https://tailscale.com/docs/how-to/set-up-https-certificates](https://tailscale.com/docs/how-to/set-up-https-certificates)"&gt;Tailscale&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Renewal Script
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;/usr/local/sbin/pikvm-tailscale-cert-renew&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-Eeuo&lt;/span&gt; pipefail

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/sbin:/usr/local/bin:/usr/bin

&lt;span class="nv"&gt;CERT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/kvmd/nginx/ssl/server.crt"&lt;/span&gt;
&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/kvmd/nginx/ssl/server.key"&lt;/span&gt;

&lt;span class="c"&gt;# 30 days&lt;/span&gt;
&lt;span class="nv"&gt;MIN_VALIDITY_SECONDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="nv"&gt;TS_MIN_VALIDITY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"720h"&lt;/span&gt;

&lt;span class="nv"&gt;TMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;MADE_RW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0


log&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"pikvm-tailscale-cert-renew: &lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


cleanup&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;

    &lt;span class="nb"&gt;trap&lt;/span&gt; - EXIT INT TERM

    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CERT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMP&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi

    if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; MADE_RW &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;sync

        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; ro&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: failed to restore read-only filesystem"&lt;/span&gt;
            &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
        &lt;span class="k"&gt;fi
    fi

    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;trap &lt;/span&gt;cleanup EXIT INT TERM


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Get the Tailscale FQDN&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;
    tailscale status &lt;span class="nt"&gt;--json&lt;/span&gt; |
        jq &lt;span class="nt"&gt;-er&lt;/span&gt; &lt;span class="s1"&gt;'.Self.DNSName | rtrimstr(".") | select(length &amp;gt; 0)'&lt;/span&gt;
&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

log &lt;span class="s2"&gt;"Tailscale DNS name: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Check the certificate currently used by nginx&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
cert_is_current&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1

    &lt;span class="c"&gt;# Check whether the hostname matches&lt;/span&gt;
    openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-checkhost&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1

    &lt;span class="c"&gt;# Check whether at least 30 days remain&lt;/span&gt;
    openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-checkend&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MIN_VALIDITY_SECONDS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1

    &lt;span class="k"&gt;return &lt;/span&gt;0
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;if &lt;/span&gt;cert_is_current&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"certificate is valid for more than 30 days; nothing to do"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;log &lt;span class="s2"&gt;"certificate renewal is required"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Use /tmp for the temporary directory.&lt;/span&gt;
&lt;span class="c"&gt;# The root filesystem is still RO at this point.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;TMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /tmp/pikvm-tailscale-cert.XXXXXX&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Switch the PiKVM root filesystem to RW only when necessary.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;ROOT_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;findmnt &lt;span class="nt"&gt;-no&lt;/span&gt; OPTIONS /&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;",&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ROOT_OPTS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;,rw,&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        log &lt;span class="s2"&gt;"root filesystem is already read-write"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        log &lt;span class="s2"&gt;"switching root filesystem to read-write"&lt;/span&gt;
        rw
        &lt;span class="nv"&gt;MADE_RW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
        &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Obtain the certificate from Tailscale.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# --min-validity=720h requests a certificate&lt;/span&gt;
&lt;span class="c"&gt;# that is valid for at least 30 days.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
log &lt;span class="s2"&gt;"requesting certificate for &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

tailscale cert &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--min-validity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TS_MIN_VALIDITY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cert-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--key-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Validate the obtained certificate&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;

&lt;span class="c"&gt;# hostname&lt;/span&gt;
openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-checkhost&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# expiration&lt;/span&gt;
openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-checkend&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MIN_VALIDITY_SECONDS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Verify that the certificate and private key have the same public key&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; cmp &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;
        openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-pubkey&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-noout&lt;/span&gt; |
        openssl pkey &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-pubin&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-outform&lt;/span&gt; DER 2&amp;gt;/dev/null
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;
        openssl pkey &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-pubout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-outform&lt;/span&gt; DER 2&amp;gt;/dev/null
    &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: certificate and private key do not match"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Back up the current certificate&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.crt"&lt;/span&gt;
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.key"&lt;/span&gt;
&lt;span class="k"&gt;fi


&lt;/span&gt;rollback&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    log &lt;span class="s2"&gt;"rolling back certificate"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.crt"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.crt"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi

    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.key"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.key"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Prepare the files for nginx, then rename them.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# nginx itself continues holding the old certificate until it is&lt;/span&gt;
&lt;span class="c"&gt;# reloaded/restarted, so even if the crt/key files briefly do not match&lt;/span&gt;
&lt;span class="c"&gt;# between the two renames, this does not affect the running nginx process.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; root &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-g&lt;/span&gt; kvmd-nginx &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-m&lt;/span&gt; 0644 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CERT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt;

&lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; root &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-g&lt;/span&gt; kvmd-nginx &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-m&lt;/span&gt; 0640 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt;

&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CERT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Validate using the actual nginx configuration generated by PiKVM&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; /run/kvmd/nginx.conf&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: nginx configuration test failed"&lt;/span&gt;
    rollback
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Restart according to the official PiKVM documentation.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; systemctl restart kvmd-nginx&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: kvmd-nginx restart failed"&lt;/span&gt;

    rollback

    &lt;span class="c"&gt;# Attempt recovery after restoring the old certificate&lt;/span&gt;
    nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; /run/kvmd/nginx.conf &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
    &lt;/span&gt;systemctl restart kvmd-nginx &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

    exit &lt;/span&gt;1
&lt;span class="k"&gt;fi


&lt;/span&gt;log &lt;span class="s2"&gt;"certificate successfully installed"&lt;/span&gt;

openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-subject&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-issuer&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-dates&lt;/span&gt;

&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this method, the normal daily operation consists only of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl x509 &lt;span class="nt"&gt;-checkhost&lt;/span&gt; ...
openssl x509 &lt;span class="nt"&gt;-checkend&lt;/span&gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so the &lt;strong&gt;root filesystem remains RO&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It switches to &lt;code&gt;rw&lt;/code&gt; only when fewer than 30 days remain.&lt;/p&gt;

&lt;p&gt;Additionally, because &lt;code&gt;tailscale cert --min-validity=720h&lt;/code&gt; is used, Tailscale is also instructed to “return a certificate that is valid for at least 30 days.” This flag is part of the current Tailscale CLI specification. (&lt;a href="[https://tailscale.com/docs/reference/tailscale-cli](https://tailscale.com/docs/reference/tailscale-cli)"&gt;Tailscale&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  2. systemd Service
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/etc/systemd/system/pikvm-tailscale-cert-renew.service&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Renew Tailscale TLS certificate for PiKVM nginx&lt;/span&gt;
&lt;span class="py"&gt;Wants&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target tailscaled.service&lt;/span&gt;
&lt;span class="py"&gt;Requires&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;tailscaled.service&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/sbin/pikvm-tailscale-cert-renew&lt;/span&gt;
&lt;span class="py"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5min&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no need to add &lt;code&gt;kvmd-nginx.service&lt;/code&gt; to &lt;code&gt;Requires=&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The reason is that even if &lt;code&gt;kvmd-nginx&lt;/code&gt; has stopped because of a broken certificate, this unit should still be able to repair the certificate independently and then run &lt;code&gt;systemctl restart kvmd-nginx&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. systemd Timer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/etc/systemd/system/pikvm-tailscale-cert-renew.timer&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Periodic Tailscale TLS certificate check for PiKVM&lt;/span&gt;

&lt;span class="nn"&gt;[Timer]&lt;/span&gt;
&lt;span class="py"&gt;OnBootSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;15min&lt;/span&gt;
&lt;span class="py"&gt;OnUnitActiveSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1d&lt;/span&gt;
&lt;span class="py"&gt;RandomizedDelaySec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;30min&lt;/span&gt;
&lt;span class="py"&gt;AccuracySec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1min&lt;/span&gt;
&lt;span class="py"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;pikvm-tailscale-cert-renew.service&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;timers.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Persistent=true&lt;/code&gt; is intentionally omitted here.&lt;/p&gt;

&lt;p&gt;Since this configuration starts renewing a 90-day certificate 30 days before expiration, missing a single check while the device is powered off is not a problem. The certificate will be checked roughly 15–45 minutes after boot, and then approximately once per day thereafter.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Installation
&lt;/h2&gt;

&lt;p&gt;Switch PiKVM to RW only while creating the configuration files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw

&lt;span class="nb"&gt;chmod &lt;/span&gt;755 /usr/local/sbin/pikvm-tailscale-cert-renew

systemctl daemon-reload

systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;pikvm-tailscale-cert-renew.timer

ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then completely disable &lt;code&gt;tailscale serve&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale serve &lt;span class="nt"&gt;--https&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443 off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PiKVM’s own nginx will listen on port 443.&lt;/p&gt;

&lt;p&gt;The official PiKVM documentation also uses the approach of updating &lt;code&gt;server.crt/server.key&lt;/code&gt; and running &lt;code&gt;systemctl restart kvmd-nginx&lt;/code&gt; when installing a Tailscale certificate directly into nginx. (&lt;a href="[https://pikvm.github.io/pikvm/tailscale/](https://pikvm.github.io/pikvm/tailscale/)"&gt;Pikvm&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Starting and Checking Status
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl start pikvm-tailscale-cert-renew.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status pikvm-tailscale-cert-renew.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-u&lt;/span&gt; pikvm-tailscale-cert-renew.service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-n&lt;/span&gt; 100 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-in&lt;/span&gt; /etc/kvmd/nginx/ssl/server.crt &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-subject&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-issuer&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-dates&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-ext&lt;/span&gt; subjectAltName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it succeeds and contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DNS:{hostname}.{tsnet}.ts.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then everything is OK.&lt;/p&gt;

&lt;p&gt;After that, start the timer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl start pikvm-tailscale-cert-renew.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl list-timers pikvm-tailscale-cert-renew.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Access URL
&lt;/h3&gt;

&lt;p&gt;With this configuration, the certificate name is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{hostname}.{tsnet}.ts.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so in the browser, always use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://{hostname}.{tsnet}.ts.net/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;https://{hostname}/&lt;/code&gt; or &lt;code&gt;[https://100.x.x.x/](https://100.x.x.x/)&lt;/code&gt;, the connection itself may reach nginx, but the certificate name will not match. Tailscale also explicitly states that HTTPS certificates are for fully qualified &lt;code&gt;*.ts.net&lt;/code&gt; names, not HTTPS certificates for bare hostnames. (&lt;a href="[https://tailscale.com/docs/how-to/set-up-https-certificates](https://tailscale.com/docs/how-to/set-up-https-certificates)"&gt;Tailscale&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;In other words, this approach &lt;strong&gt;completely eliminates Serve, handles port 443 using only PiKVM’s standard nginx, keeps the filesystem RO during normal operation, and switches it to RW only when the certificate actually needs to be renewed&lt;/strong&gt;. It also does not conflict with the automatic generation of &lt;code&gt;/run/kvmd/nginx.conf&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  PiKVM で Tailscale HTTPS 証明書を自動更新する
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;/etc/kvmd/nginx/ssl.conf&lt;/code&gt; では、引き続き&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/kvmd/nginx/ssl/server.crt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/kvmd/nginx/ssl/server.key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を使用し、systemd タイマーで証明書の有効期限を確認して、必要な場合にのみこの 2 ファイルを更新する構成が適切です。&lt;/p&gt;

&lt;p&gt;PiKVM の公式ドキュメントでも、Tailscale の証明書を &lt;code&gt;/etc/kvmd/nginx/ssl/server.{crt,key}&lt;/code&gt; に配置し、グループを &lt;code&gt;kvmd-nginx&lt;/code&gt; に設定したうえで &lt;code&gt;kvmd-nginx&lt;/code&gt; を再起動する方法が説明されています。(&lt;a href="[https://pikvm.github.io/pikvm/tailscale/](https://pikvm.github.io/pikvm/tailscale/)"&gt;Pikvm&lt;/a&gt;) また、&lt;code&gt;tailscale cert&lt;/code&gt; でファイルとして取得した証明書は自動更新されないため、ユーザー側で独自の更新処理を実装する必要があります。現在の CLI では &lt;code&gt;--min-validity&lt;/code&gt; も公式に利用できます。(&lt;a href="[https://tailscale.com/docs/reference/tailscale-cli](https://tailscale.com/docs/reference/tailscale-cli)"&gt;Tailscale&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  構成
&lt;/h3&gt;

&lt;p&gt;通常、構成は次のようになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tailscale
   │
   │ 100.x / MagicDNS
   ▼
PiKVM nginx :443
   │
   ├─ /etc/kvmd/nginx/ssl/server.crt
   └─ /etc/kvmd/nginx/ssl/server.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tailscale serve&lt;/code&gt; は使用しません。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale serve &lt;span class="nt"&gt;--https&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443 off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;証明書の更新処理は次のようになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;タイマーを 1 日 1 回実行
        │
        ▼
現在の server.crt を確認
        │
        ├─ FQDN が正しく、
        │  かつ有効期限が 30 日以上残っている
        │       → 何もしない
        │
        └─ 残り 30 日未満 / 証明書なし / ホスト名不一致
                │
                ▼
               rw
                │
                ▼
        tailscale cert
                │
                ▼
        証明書/鍵を検証
                │
                ▼
        nginx のファイルを置換
                │
                ▼
        nginx -t
                │
                ▼
        kvmd-nginx を再起動
                │
                ▼
               ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s Encrypt の証明書は 90 日間有効なので、有効期限の 30 日前から更新を試みれば十分な余裕があります。(&lt;a href="[https://tailscale.com/docs/how-to/set-up-https-certificates](https://tailscale.com/docs/how-to/set-up-https-certificates)"&gt;Tailscale&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  1. 更新スクリプト
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/usr/local/sbin/pikvm-tailscale-cert-renew&lt;/code&gt; を作成します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-Eeuo&lt;/span&gt; pipefail

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/sbin:/usr/local/bin:/usr/bin

&lt;span class="nv"&gt;CERT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/kvmd/nginx/ssl/server.crt"&lt;/span&gt;
&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/kvmd/nginx/ssl/server.key"&lt;/span&gt;

&lt;span class="c"&gt;# 30 日&lt;/span&gt;
&lt;span class="nv"&gt;MIN_VALIDITY_SECONDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="nv"&gt;TS_MIN_VALIDITY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"720h"&lt;/span&gt;

&lt;span class="nv"&gt;TMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;MADE_RW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0


log&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"pikvm-tailscale-cert-renew: &lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


cleanup&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;

    &lt;span class="nb"&gt;trap&lt;/span&gt; - EXIT INT TERM

    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CERT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMP&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi

    if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; MADE_RW &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;sync

        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; ro&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: 読み取り専用ファイルシステムへの復元に失敗しました"&lt;/span&gt;
            &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
        &lt;span class="k"&gt;fi
    fi

    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;trap &lt;/span&gt;cleanup EXIT INT TERM


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Tailscale の FQDN を取得&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;
    tailscale status &lt;span class="nt"&gt;--json&lt;/span&gt; |
        jq &lt;span class="nt"&gt;-er&lt;/span&gt; &lt;span class="s1"&gt;'.Self.DNSName | rtrimstr(".") | select(length &amp;gt; 0)'&lt;/span&gt;
&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

log &lt;span class="s2"&gt;"Tailscale DNS 名: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# nginx が現在使用している証明書を確認&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
cert_is_current&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1

    &lt;span class="c"&gt;# ホスト名が一致しているか確認&lt;/span&gt;
    openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-checkhost&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1

    &lt;span class="c"&gt;# 有効期限が 30 日以上残っているか確認&lt;/span&gt;
    openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-checkend&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MIN_VALIDITY_SECONDS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1

    &lt;span class="k"&gt;return &lt;/span&gt;0
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;if &lt;/span&gt;cert_is_current&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"証明書の有効期限は 30 日以上残っています。処理は不要です"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;log &lt;span class="s2"&gt;"証明書の更新が必要です"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# 一時ディレクトリには /tmp を使用。&lt;/span&gt;
&lt;span class="c"&gt;# この時点ではルートファイルシステムはまだ RO。&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;TMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /tmp/pikvm-tailscale-cert.XXXXXX&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# 必要な場合にのみ PiKVM のルートファイルシステムを RW に切り替える。&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;ROOT_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;findmnt &lt;span class="nt"&gt;-no&lt;/span&gt; OPTIONS /&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;",&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ROOT_OPTS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;,rw,&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        log &lt;span class="s2"&gt;"ルートファイルシステムはすでに読み書き可能です"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        log &lt;span class="s2"&gt;"ルートファイルシステムを読み書き可能に切り替えます"&lt;/span&gt;
        rw
        &lt;span class="nv"&gt;MADE_RW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
        &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Tailscale から証明書を取得。&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# --min-validity=720h により、少なくとも 30 日間&lt;/span&gt;
&lt;span class="c"&gt;# 有効な証明書を要求する。&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
log &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; の証明書を要求しています"&lt;/span&gt;

tailscale cert &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--min-validity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TS_MIN_VALIDITY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cert-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--key-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# 取得した証明書を検証&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;

&lt;span class="c"&gt;# ホスト名&lt;/span&gt;
openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-checkhost&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 有効期限&lt;/span&gt;
openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-checkend&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MIN_VALIDITY_SECONDS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 証明書と秘密鍵の公開鍵が同一であることを確認&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; cmp &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;
        openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-pubkey&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-noout&lt;/span&gt; |
        openssl pkey &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-pubin&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-outform&lt;/span&gt; DER 2&amp;gt;/dev/null
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;
        openssl pkey &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-pubout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-outform&lt;/span&gt; DER 2&amp;gt;/dev/null
    &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: 証明書と秘密鍵が一致しません"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# 現在の証明書をバックアップ&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.crt"&lt;/span&gt;
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.key"&lt;/span&gt;
&lt;span class="k"&gt;fi


&lt;/span&gt;rollback&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    log &lt;span class="s2"&gt;"証明書をロールバックしています"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.crt"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.crt"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi

    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.key"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/old.key"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# nginx 用のファイルを準備してからリネームする。&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# nginx 自体はリロード/再起動されるまで古い証明書を保持し続けるため、&lt;/span&gt;
&lt;span class="c"&gt;# 2 回のリネームの間に crt/key ファイルが一時的に一致しない状態になっても、&lt;/span&gt;
&lt;span class="c"&gt;# 稼働中の nginx プロセスには影響しない。&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; root &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-g&lt;/span&gt; kvmd-nginx &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-m&lt;/span&gt; 0644 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.crt"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CERT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt;

&lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; root &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-g&lt;/span&gt; kvmd-nginx &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-m&lt;/span&gt; 0640 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/server.key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt;

&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CERT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# PiKVM が生成した実際の nginx 設定を使用して検証&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; /run/kvmd/nginx.conf&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: nginx の設定テストに失敗しました"&lt;/span&gt;
    rollback
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;


&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# PiKVM 公式ドキュメントに従って再起動。&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; systemctl restart kvmd-nginx&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log &lt;span class="s2"&gt;"ERROR: kvmd-nginx の再起動に失敗しました"&lt;/span&gt;

    rollback

    &lt;span class="c"&gt;# 古い証明書を復元した後、復旧を試みる&lt;/span&gt;
    nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; /run/kvmd/nginx.conf &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
    &lt;/span&gt;systemctl restart kvmd-nginx &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

    exit &lt;/span&gt;1
&lt;span class="k"&gt;fi


&lt;/span&gt;log &lt;span class="s2"&gt;"証明書を正常にインストールしました"&lt;/span&gt;

openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-subject&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-issuer&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-dates&lt;/span&gt;

&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;この方法では、通常の日次処理で実行されるのは次の確認だけです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl x509 &lt;span class="nt"&gt;-checkhost&lt;/span&gt; ...
openssl x509 &lt;span class="nt"&gt;-checkend&lt;/span&gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;したがって、&lt;strong&gt;ルートファイルシステムは RO のまま維持されます&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rw&lt;/code&gt; に切り替わるのは、有効期限の残りが 30 日未満になった場合だけです。&lt;/p&gt;

&lt;p&gt;さらに、&lt;code&gt;tailscale cert --min-validity=720h&lt;/code&gt; を使用しているため、Tailscale に対しても「少なくとも 30 日間有効な証明書を返す」よう指定しています。このフラグは現在の Tailscale CLI 仕様に含まれています。(&lt;a href="[https://tailscale.com/docs/reference/tailscale-cli](https://tailscale.com/docs/reference/tailscale-cli)"&gt;Tailscale&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  2. systemd サービス
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/etc/systemd/system/pikvm-tailscale-cert-renew.service&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PiKVM nginx 用 Tailscale TLS 証明書を更新&lt;/span&gt;
&lt;span class="py"&gt;Wants&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target tailscaled.service&lt;/span&gt;
&lt;span class="py"&gt;Requires&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;tailscaled.service&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/sbin/pikvm-tailscale-cert-renew&lt;/span&gt;
&lt;span class="py"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5min&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Requires=&lt;/code&gt; に &lt;code&gt;kvmd-nginx.service&lt;/code&gt; を追加する必要はありません。&lt;/p&gt;

&lt;p&gt;理由は、証明書の破損によって &lt;code&gt;kvmd-nginx&lt;/code&gt; が停止していたとしても、このユニットは独立して証明書を修復し、その後 &lt;code&gt;systemctl restart kvmd-nginx&lt;/code&gt; を実行できるようにしておくべきだからです。&lt;/p&gt;




&lt;h2&gt;
  
  
  3. systemd タイマー
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/etc/systemd/system/pikvm-tailscale-cert-renew.timer&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PiKVM 用 Tailscale TLS 証明書の定期チェック&lt;/span&gt;

&lt;span class="nn"&gt;[Timer]&lt;/span&gt;
&lt;span class="py"&gt;OnBootSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;15min&lt;/span&gt;
&lt;span class="py"&gt;OnUnitActiveSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1d&lt;/span&gt;
&lt;span class="py"&gt;RandomizedDelaySec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;30min&lt;/span&gt;
&lt;span class="py"&gt;AccuracySec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1min&lt;/span&gt;
&lt;span class="py"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;pikvm-tailscale-cert-renew.service&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;timers.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ここでは意図的に &lt;code&gt;Persistent=true&lt;/code&gt; を省略しています。&lt;/p&gt;

&lt;p&gt;この構成では、90 日間有効な証明書について有効期限の 30 日前から更新を開始するため、デバイスの電源が切れている間に 1 回チェックできなくても問題ありません。起動後およそ 15～45 分以内に証明書がチェックされ、その後はおよそ 1 日に 1 回チェックされます。&lt;/p&gt;




&lt;h2&gt;
  
  
  4. インストール
&lt;/h2&gt;

&lt;p&gt;設定ファイルを作成している間だけ PiKVM を RW に切り替えます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw

&lt;span class="nb"&gt;chmod &lt;/span&gt;755 /usr/local/sbin/pikvm-tailscale-cert-renew

systemctl daemon-reload

systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;pikvm-tailscale-cert-renew.timer

ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、&lt;code&gt;tailscale serve&lt;/code&gt; を完全に無効化します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale serve &lt;span class="nt"&gt;--https&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443 off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PiKVM 自身の nginx がポート 443 で待ち受けます。&lt;/p&gt;

&lt;p&gt;PiKVM の公式ドキュメントでも、Tailscale の証明書を nginx に直接インストールする場合、&lt;code&gt;server.crt/server.key&lt;/code&gt; を更新して &lt;code&gt;systemctl restart kvmd-nginx&lt;/code&gt; を実行する方法が使用されています。(&lt;a href="[https://pikvm.github.io/pikvm/tailscale/](https://pikvm.github.io/pikvm/tailscale/)"&gt;Pikvm&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  5. 起動とステータス確認
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl start pikvm-tailscale-cert-renew.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status pikvm-tailscale-cert-renew.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-u&lt;/span&gt; pikvm-tailscale-cert-renew.service &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-n&lt;/span&gt; 100 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;証明書：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl x509 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-in&lt;/span&gt; /etc/kvmd/nginx/ssl/server.crt &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-subject&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-issuer&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-dates&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-ext&lt;/span&gt; subjectAltName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;成功し、次の内容が含まれていれば問題ありません。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DNS:{hostname}.{tsnet}.ts.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、タイマーを開始します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl start pikvm-tailscale-cert-renew.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl list-timers pikvm-tailscale-cert-renew.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  アクセス URL
&lt;/h3&gt;

&lt;p&gt;この構成では、証明書名は次のようになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{hostname}.{tsnet}.ts.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;したがって、ブラウザでは常に次を使用します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://{hostname}.{tsnet}.ts.net/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;https://{hostname}/&lt;/code&gt; または &lt;code&gt;https://100.x.x.x/&lt;/code&gt; の場合、接続自体は nginx に到達する可能性がありますが、証明書名は一致しません。Tailscale も、HTTPS 証明書は完全修飾された &lt;code&gt;*.ts.net&lt;/code&gt; 名を対象とするものであり、単純なホスト名に対する HTTPS 証明書ではないことを明示しています。(&lt;a href="[https://tailscale.com/docs/how-to/set-up-https-certificates](https://tailscale.com/docs/how-to/set-up-https-certificates)"&gt;Tailscale&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;つまり、この方法では &lt;strong&gt;Serve を完全に排除し、ポート 443 を PiKVM 標準の nginx のみで処理し、通常時はファイルシステムを RO のまま維持し、実際に証明書の更新が必要になった場合にのみ RW に切り替えることができます&lt;/strong&gt;。また、&lt;code&gt;/run/kvmd/nginx.conf&lt;/code&gt; の自動生成とも競合しません。&lt;/p&gt;

</description>
      <category>automation</category>
      <category>linux</category>
      <category>networking</category>
    </item>
    <item>
      <title>Setting Up and Troubleshooting FFmpeg + Intel QSV on Rocky Linux 9</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Fri, 07 Aug 2026 08:31:28 +0000</pubDate>
      <link>https://dev.to/vast-cow/setting-up-and-troubleshooting-ffmpeg-intel-qsv-on-rocky-linux-9-2n29</link>
      <guid>https://dev.to/vast-cow/setting-up-and-troubleshooting-ffmpeg-intel-qsv-on-rocky-linux-9-2n29</guid>
      <description>&lt;p&gt;If you want to accelerate FFmpeg H.264 / HEVC encoding using the integrated GPU in an Intel CPU, you can use Intel Quick Sync Video (QSV).&lt;/p&gt;

&lt;p&gt;However, on Linux—especially on RHEL-based distributions such as Rocky Linux 9—simply seeing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FFmpeg recognizes h264_qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not mean QSV will actually work.&lt;/p&gt;

&lt;p&gt;In fact, on Rocky Linux 9, I encountered errors like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[AVHWDeviceContext @ 0x562042595380] Failed to initialise VAAPI connection: -1 (unknown libva error).
[h264_qsv @ 0x56204258e140] Failed to create a VAAPI device.
Error initializing output stream 0:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even when explicitly specifying the Intel GPU with &lt;code&gt;-qsv_device&lt;/code&gt;, I got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[AVHWDeviceContext @ 0x55ac9a494500] Failed to initialise VAAPI connection: -1 (unknown libva error).
Device creation failed: -5.
Failed to set value '/dev/dri/renderD128' for option 'qsv_device': Input/output error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This article explains how to set up FFmpeg + QSV on Rocky Linux 9 and the order in which these types of errors should be isolated and diagnosed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the Layers Required for QSV to Work
&lt;/h2&gt;

&lt;p&gt;The first important point is that QSV is not a standalone FFmpeg feature.&lt;/p&gt;

&lt;p&gt;On Linux, access to the Intel GPU conceptually passes through several layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FFmpeg
↓
QSV
↓
Intel Media SDK / oneVPL
↓
Intel Media Driver
↓
VA-API / libva
↓
/dev/dri/renderD128
↓
i915 / xe
↓
Intel GPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, even if:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-encoders&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;h264_qsv
hevc_qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that alone does not mean the GPU is actually usable.&lt;/p&gt;

&lt;p&gt;For example, QSV will not work if there is a problem at any one of these points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux does not detect the Intel GPU&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nomodeset&lt;/code&gt; is configured&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;i915&lt;/code&gt; / &lt;code&gt;xe&lt;/code&gt; is not loaded&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/dev/dri/renderD128&lt;/code&gt; cannot be accessed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;libva&lt;/code&gt; is missing&lt;/li&gt;
&lt;li&gt;Intel Media Driver is missing or cannot be loaded&lt;/li&gt;
&lt;li&gt;The Media SDK / oneVPL runtime does not match&lt;/li&gt;
&lt;li&gt;FFmpeg itself was not built with QSV support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, the basic troubleshooting approach is to verify each layer from the bottom up.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Check Whether Linux Detects the Intel GPU
&lt;/h1&gt;

&lt;p&gt;First, check the PCI devices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lspci &lt;span class="nt"&gt;-nn&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'VGA|Display'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this environment, the result was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00:02.0 VGA compatible controller [0300]:
Intel Corporation GeminiLake [UHD Graphics 605] [8086:3184] (rev 03)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms that the Intel UHD Graphics 605, i.e. the Gemini Lake GPU, is detected.&lt;/p&gt;

&lt;p&gt;Next, check the kernel driver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lspci &lt;span class="nt"&gt;-nnk&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A4&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'VGA|Display'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result in this case was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00:02.0 VGA compatible controller [0300]: Intel Corporation GeminiLake [UHD Graphics 605] [8086:3184] (rev 03)
DeviceName: Onboard - Video
Subsystem: Elitegroup Computer Systems Device [1019:a94d]
Kernel driver in use: i915
Kernel modules: i915
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important line here is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kernel driver in use: i915
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemini Lake uses &lt;code&gt;i915&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On newer Intel GPUs, &lt;code&gt;xe&lt;/code&gt; may be used depending on the configuration.&lt;/p&gt;

&lt;p&gt;You can also verify this with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsmod | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'i915|xe'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  2. Remove &lt;code&gt;nomodeset&lt;/code&gt; If It Is Configured
&lt;/h1&gt;

&lt;p&gt;When using an Intel iGPU with QSV / VA-API, specifying &lt;code&gt;nomodeset&lt;/code&gt; in the kernel boot options can prevent the GPU driver from initializing correctly and can make QSV unusable.&lt;/p&gt;

&lt;p&gt;First, check the current kernel command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cmdline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nomodeset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;remove it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nomodeset&lt;/code&gt; disables Kernel Mode Setting (KMS). Because it interferes with the normal initialization of DRM/KMS drivers such as &lt;code&gt;i915&lt;/code&gt;, which are used with Intel GPUs, it can prevent &lt;code&gt;/dev/dri/renderD128&lt;/code&gt; from being created or cause VA-API initialization to fail even if a GPU device appears to exist.&lt;/p&gt;

&lt;p&gt;On Rocky Linux 9, check the GRUB configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL | &lt;span class="nb"&gt;grep &lt;/span&gt;args
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;nomodeset&lt;/code&gt; is configured, you can remove it from all kernel entries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--update-kernel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL &lt;span class="nt"&gt;--remove-args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"nomodeset"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After changing the setting, reboot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After rebooting, check again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cmdline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After confirming that &lt;code&gt;nomodeset&lt;/code&gt; is gone, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lspci &lt;span class="nt"&gt;-nnk&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A4&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'VGA|Display'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and confirm that it shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kernel driver in use: i915
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check the DRM devices as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/dri/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At a minimum, confirm that entries such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;card0
renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;have been created.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Check &lt;code&gt;/dev/dri/renderD128&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Next, check the DRM devices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/dri/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this environment, the output was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drwxr-xr-x. 2 root root         80 Aug  7 16:04 by-path
crw-rw----. 1 root video  226,   0 Aug  7 16:04 card0
crw-rw-rw-. 1 root render 226, 128 Aug  7 16:04 renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For server-side QSV and VA-API usage, the especially important device is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even on a server that is not running X11 or Wayland, hardware encoding is possible as long as &lt;code&gt;renderD128&lt;/code&gt; is accessible.&lt;/p&gt;

&lt;p&gt;In other words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No GUI = QSV cannot be used
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is not true.&lt;/p&gt;

&lt;p&gt;QSV can also be used on headless servers.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Check Permissions on renderD128
&lt;/h1&gt;

&lt;p&gt;A typical device node looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crw-rw---- 1 root render ... /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that case, add the user running FFmpeg to the &lt;code&gt;render&lt;/code&gt; group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; render &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the environment, membership in &lt;code&gt;video&lt;/code&gt; may also be required, so this is also acceptable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; video,render &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After logging in again, verify with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If FFmpeg is launched from a systemd service, the required permissions must be granted not to the login user, but to &lt;strong&gt;the user running the service&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The same point matters when using Jellyfin, Plex, or custom transcoding workflows combined with MediaMTX.&lt;/p&gt;

&lt;p&gt;In this environment, the device permissions were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crw-rw-rw-. 1 root render ... renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so a simple Unix permission problem was unlikely.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Enable EPEL and RPM Fusion
&lt;/h1&gt;

&lt;p&gt;The standard Rocky Linux 9 repositories may not contain all packages needed for FFmpeg and the Intel Media Driver stack.&lt;/p&gt;

&lt;p&gt;First, add EPEL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; epel-release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add RPM Fusion Free / Nonfree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
https://download1.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm &lt;span class="se"&gt;\&lt;/span&gt;
https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-9.noarch.rpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf update &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  6. Install Intel Media Driver
&lt;/h1&gt;

&lt;p&gt;For relatively recent Intel GPUs, Intel Media Driver—the &lt;code&gt;iHD&lt;/code&gt; driver—is used as the VA-API driver.&lt;/p&gt;

&lt;p&gt;With Rocky Linux 9 + RPM Fusion, install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Intel Media Driver provides the VA-API backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/lib64/dri/iHD_drv_video.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Intel UHD Graphics 605 / Gemini Lake used here is also supported by Intel Media Driver.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Install libva and vainfo
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;vainfo&lt;/code&gt; is extremely useful for verifying VA-API operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libva libva-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vainfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, on a server without a GUI, it is more reliable to specify the DRM device explicitly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is working correctly, the output should look roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libva info: VA-API version ...
libva info: Trying to open /usr/lib64/dri/iHD_drv_video.so
libva info: Found init function __vaDriverInit_...
libva info: va_openDriver() returns 0
vainfo: Driver version: Intel iHD driver ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  8. Explicitly Set &lt;code&gt;LIBVA_DRIVER_NAME=iHD&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;If automatic detection does not work correctly, you can explicitly specify the VA-API driver to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD &lt;span class="se"&gt;\&lt;/span&gt;
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to set it persistently, you can also use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  9. Intel Media SDK vs. oneVPL
&lt;/h1&gt;

&lt;p&gt;Several names appear around QSV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intel Media SDK
libmfx
oneVPL
libvpl
intel-vpl-gpu-rt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Intel's newer software stack has transitioned from the legacy Intel Media SDK to oneVPL.&lt;/p&gt;

&lt;p&gt;However, when deciding what to install for FFmpeg on Rocky Linux 9, it is important to &lt;strong&gt;check which API stack your FFmpeg build was compiled against&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For the RPM Fusion FFmpeg 5.1.10 used here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;showed that it had been built with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--enable-libmfx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when checking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qa&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'libva|intel-media|libmfx|vpl'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the result was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libva-2.22.0-1.el9.x86_64
intel-mediasdk-21.3.5-1.el9.x86_64
libva-utils-2.11.1-1.el9.x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this environment, FFmpeg uses QSV through &lt;code&gt;libmfx&lt;/code&gt;, i.e. Intel Media SDK.&lt;/p&gt;

&lt;p&gt;Therefore, it is safer &lt;strong&gt;not to assume that "Rocky 9 always requires libvpl + intel-vpl-gpu-rt."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, check the configure options shown by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  10. Install FFmpeg
&lt;/h1&gt;

&lt;p&gt;Install FFmpeg from RPM Fusion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ffmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  11. Check Whether FFmpeg Supports QSV
&lt;/h1&gt;

&lt;p&gt;First, check the list of hardware acceleration methods.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-hwaccels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the FFmpeg build used here, the result was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hardware acceleration methods:
vdpau
cuda
vaapi
qsv
drm
opencl
vulkan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you can confirm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vaapi
qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, check the QSV encoders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-hide_banner&lt;/span&gt; &lt;span class="nt"&gt;-encoders&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'qsv|vaapi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this environment, the output was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;V..... h264_qsv     H.264 / AVC ... (Intel Quick Sync Video acceleration)
V....D h264_vaapi   H.264/AVC (VAAPI)
V..... hevc_qsv     HEVC (Intel Quick Sync Video acceleration)
V....D hevc_vaapi   H.265/HEVC (VAAPI)
V..... mjpeg_qsv    MJPEG (Intel Quick Sync Video acceleration)
V....D mjpeg_vaapi  MJPEG (VAAPI)
V..... mpeg2_qsv    MPEG-2 video (Intel Quick Sync Video acceleration)
V....D mpeg2_vaapi  MPEG-2 (VAAPI)
V....D vp8_vaapi    VP8 (VAAPI)
V....D vp9_vaapi    VP9 (VAAPI)
V..... vp9_qsv      VP9 video (Intel Quick Sync Video acceleration)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Seeing &lt;code&gt;h264_qsv&lt;/code&gt; in the list and actually being able to use the GPU are two different things.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  12. Get &lt;code&gt;vainfo&lt;/code&gt; Working First
&lt;/h1&gt;

&lt;p&gt;When troubleshooting QSV, it is usually faster to get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;working first rather than repeatedly changing FFmpeg options.&lt;/p&gt;

&lt;p&gt;In this environment, it returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libva info: VA-API version 1.22.0
libva info: Trying to open /usr/lib64/dri/iHD_drv_video.so
libva info: va_openDriver() returns -1
libva info: Trying to open /usr/lib64/dri/i965_drv_video.so
libva info: va_openDriver() returns -1
vaInitialize failed with error code -1 (unknown libva error),exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the GPU was detected, &lt;code&gt;i915&lt;/code&gt; was in use, and &lt;code&gt;/dev/dri/renderD128&lt;/code&gt; existed, this narrowed the problem down to the &lt;code&gt;libva&lt;/code&gt; / Intel Media Driver area.&lt;/p&gt;




&lt;h1&gt;
  
  
  13. Check Which RPM Provides &lt;code&gt;iHD_drv_video.so&lt;/code&gt;
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qf&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to check which RPM provides the file.&lt;/p&gt;

&lt;p&gt;If you do not know which RPM contains it, you can also search with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dnf provides &lt;span class="s1"&gt;'*/iHD_drv_video.so'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected provider is an &lt;code&gt;intel-media-driver&lt;/code&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important point is that &lt;code&gt;intel-mediasdk&lt;/code&gt; and &lt;code&gt;intel-media-driver&lt;/code&gt; are different packages.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. Check the Driver's Library Dependencies
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ldd /usr/lib64/dri/iHD_drv_video.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this output contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then a required dependency is missing.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Check Package Sources and Versions
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qi&lt;/span&gt; libva libva-utils intel-mediasdk intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dnf repoquery &lt;span class="nt"&gt;--installed&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{name} %{version}-%{release} %{repoid}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
libva libva-utils intel-mediasdk intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  16. Explicitly Test with iHD
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD &lt;span class="se"&gt;\&lt;/span&gt;
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it is working correctly, the output should include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trying to open /usr/lib64/dri/iHD_drv_video.so
Found init function ...
va_openDriver() returns 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  17. Run a Standalone QSV Test
&lt;/h1&gt;

&lt;p&gt;Once &lt;code&gt;vainfo&lt;/code&gt; works correctly, test QSV encoding using a generated test pattern that is unrelated to any input video.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-f&lt;/span&gt; lavfi &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;testsrc2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1280x720:rate&lt;span class="o"&gt;=&lt;/span&gt;30 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-t&lt;/span&gt; 5 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_qsv &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-global_quality&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-f&lt;/span&gt; null -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this succeeds, you can conclude that the H.264 QSV encoding path is functioning.&lt;/p&gt;




&lt;h1&gt;
  
  
  18. Isolate the Problem with VAAPI Encoding
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-vaapi_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-f&lt;/span&gt; lavfi &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;testsrc2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1280x720:rate&lt;span class="o"&gt;=&lt;/span&gt;30 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s1"&gt;'format=nv12,hwupload'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-t&lt;/span&gt; 5 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_vaapi &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-f&lt;/span&gt; null -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;VAAPI&lt;/th&gt;
&lt;th&gt;QSV&lt;/th&gt;
&lt;th&gt;Possible Cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NG&lt;/td&gt;
&lt;td&gt;NG&lt;/td&gt;
&lt;td&gt;VA-API / Intel Media Driver / GPU device side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;NG&lt;/td&gt;
&lt;td&gt;Media SDK / oneVPL / QSV runtime side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;GPU stack is healthy. Investigate the original FFmpeg command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NG&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;An unusual configuration that is normally uncommon&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  19. Encode H.264 with QSV
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_qsv &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-global_quality&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  20. Encode HEVC / H.265 with QSV
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt;:v hevc_qsv &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-global_quality&lt;/span&gt; 25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  21. QSV Decode + QSV Encode
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-hwaccel&lt;/span&gt; qsv &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-hwaccel_output_format&lt;/span&gt; qsv &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_qsv &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-global_quality&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For troubleshooting, it is easier to isolate problems by starting with CPU decode + QSV encode.&lt;/p&gt;




&lt;h1&gt;
  
  
  22. The &lt;code&gt;yuv420p&lt;/code&gt; → &lt;code&gt;nv12&lt;/code&gt; Warning Is Not a Fatal Error
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incompatible pixel format 'yuv420p' for codec 'h264_qsv',
auto-selecting format 'nv12'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is unrelated to the VA-API error discussed here.&lt;/p&gt;

&lt;p&gt;If necessary, you can explicitly specify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; nv12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  23. ALSA &lt;code&gt;Thread message queue blocking&lt;/code&gt; Is Also a Separate Issue
&lt;/h1&gt;

&lt;p&gt;If you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[alsa] Thread message queue blocking;
consider raising the thread_queue_size option
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;specify something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-thread_queue_size&lt;/span&gt; 1024
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before the ALSA input.&lt;/p&gt;




&lt;h1&gt;
  
  
  24. Example Rocky Linux 9 Setup
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; epel-release
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
https://download1.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm &lt;span class="se"&gt;\&lt;/span&gt;
https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-9.noarch.rpm
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
libva &lt;span class="se"&gt;\&lt;/span&gt;
libva-utils &lt;span class="se"&gt;\&lt;/span&gt;
intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In an environment like this one where &lt;code&gt;ffmpeg -version&lt;/code&gt; includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--enable-libmfx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this package is also a candidate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; intel-mediasdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Gemini Lake + RPM Fusion FFmpeg 5.1.x, a straightforward starting configuration is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
libva &lt;span class="se"&gt;\&lt;/span&gt;
libva-utils &lt;span class="se"&gt;\&lt;/span&gt;
intel-media-driver &lt;span class="se"&gt;\&lt;/span&gt;
intel-mediasdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  25. Final Verification Checklist
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 0. nomodeset&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cmdline

&lt;span class="c"&gt;# 1. GPU / kernel driver&lt;/span&gt;
lspci &lt;span class="nt"&gt;-nnk&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A4&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'VGA|Display'&lt;/span&gt;

&lt;span class="c"&gt;# 2. Kernel module&lt;/span&gt;
lsmod | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'i915|xe'&lt;/span&gt;

&lt;span class="c"&gt;# 3. DRM&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/dri/

&lt;span class="c"&gt;# 4. Intel Media Driver&lt;/span&gt;
rpm &lt;span class="nt"&gt;-q&lt;/span&gt; intel-media-driver

&lt;span class="c"&gt;# 5. iHD driver&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so
rpm &lt;span class="nt"&gt;-qf&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so

&lt;span class="c"&gt;# 6. VA-API&lt;/span&gt;
&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD &lt;span class="se"&gt;\&lt;/span&gt;
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128

&lt;span class="c"&gt;# 7. FFmpeg HW acceleration&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-hwaccels&lt;/span&gt;

&lt;span class="c"&gt;# 8. QSV encoders&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-hide_banner&lt;/span&gt; &lt;span class="nt"&gt;-encoders&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;_qsv

&lt;span class="c"&gt;# 9. QSV decoders&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-hide_banner&lt;/span&gt; &lt;span class="nt"&gt;-decoders&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;_qsv

&lt;span class="c"&gt;# 10. QSV encode test&lt;/span&gt;
ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-f&lt;/span&gt; lavfi &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;testsrc2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1280x720:rate&lt;span class="o"&gt;=&lt;/span&gt;30 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-t&lt;/span&gt; 5 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_qsv &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-global_quality&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-f&lt;/span&gt; null -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verification order is easiest to understand as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Presence of nomodeset
↓
Intel GPU
↓
i915 / xe
↓
/dev/dri/renderD128
↓
VA-API / libva
↓
Intel Media Driver
↓
Media SDK / oneVPL
↓
FFmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  What We Learned with Gemini Lake / UHD Graphics 605
&lt;/h1&gt;

&lt;p&gt;On the actual system used here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intel Corporation GeminiLake [UHD Graphics 605]
Kernel driver in use: i915
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and &lt;code&gt;/dev/dri/renderD128&lt;/code&gt; was also present.&lt;/p&gt;

&lt;p&gt;In addition, FFmpeg had been built with &lt;code&gt;--enable-libmfx&lt;/code&gt;, recognized &lt;code&gt;qsv&lt;/code&gt; / &lt;code&gt;vaapi&lt;/code&gt;, and could list encoders such as &lt;code&gt;h264_qsv&lt;/code&gt;, &lt;code&gt;hevc_qsv&lt;/code&gt;, and &lt;code&gt;vp9_qsv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trying to open /usr/lib64/dri/iHD_drv_video.so
va_openDriver() returns -1
Trying to open /usr/lib64/dri/i965_drv_video.so
va_openDriver() returns -1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The QSV test also failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failed to initialise VAAPI connection
Device creation failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows that the problem is not with FFmpeg encoding options, but with the VA-API / Intel Media Driver layer.&lt;/p&gt;

&lt;p&gt;In this situation, investigate the Intel Media Driver installation and dependencies in this order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qf&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so
ldd /usr/lib64/dri/iHD_drv_video.so
&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD &lt;span class="se"&gt;\&lt;/span&gt;
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;When using FFmpeg + Intel QSV on Rocky Linux 9, seeing &lt;code&gt;h264_qsv&lt;/code&gt; in &lt;code&gt;ffmpeg -encoders&lt;/code&gt; does not mean the setup is complete.&lt;/p&gt;

&lt;p&gt;The required layers are roughly as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Disable &lt;code&gt;nomodeset&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Important&lt;/td&gt;
&lt;td&gt;Allows DRM/KMS to initialize correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intel GPU&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;i915&lt;/code&gt; / &lt;code&gt;xe&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;td&gt;Kernel GPU driver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/dev/dri/renderD128&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;td&gt;DRM render node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;libva&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Important in Linux QSV environments&lt;/td&gt;
&lt;td&gt;VA-API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;intel-media-driver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Important for supported Intel GPUs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iHD_drv_video.so&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;intel-mediasdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;For libmfx-based setups&lt;/td&gt;
&lt;td&gt;Legacy QSV runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;libvpl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;For oneVPL-based setups&lt;/td&gt;
&lt;td&gt;oneVPL dispatcher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;intel-vpl-gpu-rt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;For supported newer-generation GPUs&lt;/td&gt;
&lt;td&gt;oneVPL GPU implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QSV-enabled FFmpeg&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;h264_qsv&lt;/code&gt; / &lt;code&gt;hevc_qsv&lt;/code&gt;, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In particular, if you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failed to initialise VAAPI connection
Failed to create a VAAPI device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the fastest approach is to check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cmdline
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before changing encoding options.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;nomodeset&lt;/code&gt; is still present, remove it. If &lt;code&gt;vainfo&lt;/code&gt; does not work correctly, fix the VA-API / Intel Media Driver problem first.&lt;/p&gt;

&lt;p&gt;When building a QSV environment on Rocky Linux 9, keep the following layers in mind:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nomodeset
↓
GPU
↓
Kernel driver
↓
DRM
↓
VA-API
↓
Intel Media Driver
↓
Media SDK / oneVPL
↓
FFmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most reliable setup method is to &lt;strong&gt;verify proper operation one layer at a time, from the bottom up&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Rocky Linux 9 で FFmpeg + Intel QSV を使うためのセットアップとトラブルシューティング
&lt;/h1&gt;

&lt;p&gt;Intel CPU の内蔵 GPU を使って FFmpeg の H.264 / HEVC エンコードを高速化したい場合、Intel Quick Sync Video（QSV）が利用できます。&lt;/p&gt;

&lt;p&gt;しかし Linux、特に Rocky Linux 9 のような RHEL 系ディストリビューションでは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FFmpeg が h264_qsv を認識している
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;だけでは QSV は動きません。&lt;/p&gt;

&lt;p&gt;実際、今回 Rocky Linux 9 上で次のようなエラーに遭遇しました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[AVHWDeviceContext @ 0x562042595380] Failed to initialise VAAPI connection: -1 (unknown libva error).
[h264_qsv @ 0x56204258e140] Failed to create a VAAPI device.
Error initializing output stream 0:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;さらに &lt;code&gt;-qsv_device&lt;/code&gt; で Intel GPU を明示しても、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[AVHWDeviceContext @ 0x55ac9a494500] Failed to initialise VAAPI connection: -1 (unknown libva error).
Device creation failed: -5.
Failed to set value '/dev/dri/renderD128' for option 'qsv_device': Input/output error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となりました。&lt;/p&gt;

&lt;p&gt;この記事では、Rocky Linux 9 で FFmpeg + QSV をセットアップする方法と、こうしたエラーをどの順番で切り分けるべきかをまとめます。&lt;/p&gt;




&lt;h2&gt;
  
  
  QSV が動くまでのレイヤを理解する
&lt;/h2&gt;

&lt;p&gt;最初に重要なのは、QSV は FFmpeg 単体の機能ではないということです。&lt;/p&gt;

&lt;p&gt;Linux では概念的に次のような複数のレイヤを通って Intel GPU にアクセスします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FFmpeg
  ↓
QSV
  ↓
Intel Media SDK / oneVPL
  ↓
Intel Media Driver
  ↓
VA-API / libva
  ↓
/dev/dri/renderD128
  ↓
i915 / xe
  ↓
Intel GPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;そのため、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-encoders&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;で&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;h264_qsv
hevc_qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;が表示されても、それだけでは GPU が実際に使えることを意味しません。&lt;/p&gt;

&lt;p&gt;例えば、&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intel GPU を Linux が認識していない&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nomodeset&lt;/code&gt; が設定されている&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;i915&lt;/code&gt; / &lt;code&gt;xe&lt;/code&gt; がロードされていない&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/dev/dri/renderD128&lt;/code&gt; にアクセスできない&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;libva&lt;/code&gt; がない&lt;/li&gt;
&lt;li&gt;Intel Media Driver がない、またはロードできない&lt;/li&gt;
&lt;li&gt;Media SDK / oneVPL runtime が合っていない&lt;/li&gt;
&lt;li&gt;FFmpeg 自体が QSV 対応でビルドされていない&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;といったどこか一箇所でも問題があると、QSV は動きません。&lt;/p&gt;

&lt;p&gt;したがって、下から順番に確認していくのがトラブルシューティングの基本です。&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Intel GPU を Linux が認識しているか確認する
&lt;/h1&gt;

&lt;p&gt;まず PCI デバイスを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lspci &lt;span class="nt"&gt;-nn&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'VGA|Display'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;今回の環境では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00:02.0 VGA compatible controller [0300]:
Intel Corporation GeminiLake [UHD Graphics 605] [8086:3184] (rev 03)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となりました。&lt;/p&gt;

&lt;p&gt;Intel UHD Graphics 605、つまり Gemini Lake の GPU が認識されています。&lt;/p&gt;

&lt;p&gt;次に、カーネルドライバを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lspci &lt;span class="nt"&gt;-nnk&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A4&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'VGA|Display'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;今回の結果は、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00:02.0 VGA compatible controller [0300]: Intel Corporation GeminiLake [UHD Graphics 605] [8086:3184] (rev 03)
        DeviceName: Onboard - Video
        Subsystem: Elitegroup Computer Systems Device [1019:a94d]
        Kernel driver in use: i915
        Kernel modules: i915
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;でした。&lt;/p&gt;

&lt;p&gt;ここで重要なのは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kernel driver in use: i915
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;です。&lt;/p&gt;

&lt;p&gt;Gemini Lake では &lt;code&gt;i915&lt;/code&gt; が使われます。&lt;/p&gt;

&lt;p&gt;最近の Intel GPU では構成によって &lt;code&gt;xe&lt;/code&gt; が使われることもあります。&lt;/p&gt;

&lt;p&gt;念のため、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsmod | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'i915|xe'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;でも確認できます。&lt;/p&gt;




&lt;h1&gt;
  
  
  2. &lt;code&gt;nomodeset&lt;/code&gt; を設定している場合は外す
&lt;/h1&gt;

&lt;p&gt;Intel iGPU を QSV / VA-API で使用する場合、カーネルの起動オプションに &lt;code&gt;nomodeset&lt;/code&gt; を指定していると、GPU ドライバが正常に初期化されず、QSV が使えない原因になることがあります。&lt;/p&gt;

&lt;p&gt;まず現在のカーネルコマンドラインを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cmdline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ここに、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nomodeset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;が含まれている場合は削除します。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nomodeset&lt;/code&gt; は Kernel Mode Setting（KMS）を無効化するオプションです。Intel GPU で使用する &lt;code&gt;i915&lt;/code&gt; などの DRM/KMS ドライバの正常な初期化を妨げるため、&lt;code&gt;/dev/dri/renderD128&lt;/code&gt; が生成されない、あるいは GPU デバイスが存在していても VA-API の初期化に失敗する原因になり得ます。&lt;/p&gt;

&lt;p&gt;Rocky Linux 9 では GRUB の設定を確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL | &lt;span class="nb"&gt;grep &lt;/span&gt;args
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nomodeset&lt;/code&gt; が設定されている場合は、全カーネルエントリから削除できます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--update-kernel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL &lt;span class="nt"&gt;--remove-args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"nomodeset"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;設定後、再起動します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再起動後、もう一度確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cmdline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nomodeset&lt;/code&gt; が消えていることを確認した上で、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lspci &lt;span class="nt"&gt;-nnk&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A4&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'VGA|Display'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を実行し、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kernel driver in use: i915
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となっていることを確認します。&lt;/p&gt;

&lt;p&gt;続いて DRM デバイスも確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/dri/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;最低限、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;card0
renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;などが生成されていることを確認します。&lt;/p&gt;




&lt;h1&gt;
  
  
  3. &lt;code&gt;/dev/dri/renderD128&lt;/code&gt; を確認する
&lt;/h1&gt;

&lt;p&gt;次に DRM デバイスを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/dri/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;今回の環境では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drwxr-xr-x. 2 root root         80 Aug  7 16:04 by-path
crw-rw----. 1 root video  226,   0 Aug  7 16:04 card0
crw-rw-rw-. 1 root render 226, 128 Aug  7 16:04 renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となっていました。&lt;/p&gt;

&lt;p&gt;QSV や VA-API のサーバ用途で特に重要なのが、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;です。&lt;/p&gt;

&lt;p&gt;X11 や Wayland を起動していないサーバでも、&lt;code&gt;renderD128&lt;/code&gt; にアクセスできればハードウェアエンコードできます。&lt;/p&gt;

&lt;p&gt;つまり、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GUI がない = QSV が使えない
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ではありません。&lt;/p&gt;

&lt;p&gt;ヘッドレスサーバでも利用可能です。&lt;/p&gt;




&lt;h1&gt;
  
  
  4. renderD128 の権限を確認する
&lt;/h1&gt;

&lt;p&gt;典型的には次のようになっています。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crw-rw---- 1 root render ... /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;この場合、FFmpeg を実行するユーザーを &lt;code&gt;render&lt;/code&gt; グループへ追加します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; render &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;環境によっては &lt;code&gt;video&lt;/code&gt; も必要になるため、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; video,render &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;としても構いません。&lt;/p&gt;

&lt;p&gt;再ログイン後、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;で確認します。&lt;/p&gt;

&lt;p&gt;systemd サービスから FFmpeg を起動する場合には、ログインユーザーではなく&lt;strong&gt;サービスを実行しているユーザー&lt;/strong&gt;に権限が必要です。&lt;/p&gt;

&lt;p&gt;Jellyfin、Plex、MediaMTX と組み合わせた独自トランスコード処理などでも、この点には注意が必要です。&lt;/p&gt;

&lt;p&gt;今回の環境では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crw-rw-rw-. 1 root render ... renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;だったため、単純な Unix パーミッション不足である可能性は低い状態でした。&lt;/p&gt;




&lt;h1&gt;
  
  
  5. EPEL と RPM Fusion を有効にする
&lt;/h1&gt;

&lt;p&gt;Rocky Linux 9 の標準リポジトリだけでは FFmpeg や Intel Media Driver 周辺のパッケージが不足する場合があります。&lt;/p&gt;

&lt;p&gt;まず EPEL を追加します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; epel-release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;続いて RPM Fusion Free / Nonfree を追加します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://download1.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm &lt;span class="se"&gt;\&lt;/span&gt;
  https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-9.noarch.rpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後更新しておきます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf update &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  6. Intel Media Driver をインストールする
&lt;/h1&gt;

&lt;p&gt;Intel の比較的新しい GPU では、VA-API ドライバとして Intel Media Driver、つまり &lt;code&gt;iHD&lt;/code&gt; ドライバを使用します。&lt;/p&gt;

&lt;p&gt;Rocky Linux 9 + RPM Fusion なら、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;をインストールします。&lt;/p&gt;

&lt;p&gt;Intel Media Driver は VA-API バックエンドとなる、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/lib64/dri/iHD_drv_video.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を提供します。&lt;/p&gt;

&lt;p&gt;今回使用した Intel UHD Graphics 605 / Gemini Lake も Intel Media Driver の対象です。&lt;/p&gt;




&lt;h1&gt;
  
  
  7. libva と vainfo をインストールする
&lt;/h1&gt;

&lt;p&gt;VA-API の動作確認には &lt;code&gt;vainfo&lt;/code&gt; が非常に便利です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libva libva-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;インストール後、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vainfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を実行します。&lt;/p&gt;

&lt;p&gt;ただし GUI のないサーバでは、DRM デバイスを明示したほうが確実です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;正常なら、概ね次のようになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libva info: VA-API version ...
libva info: Trying to open /usr/lib64/dri/iHD_drv_video.so
libva info: Found init function __vaDriverInit_...
libva info: va_openDriver() returns 0

vainfo: Driver version: Intel iHD driver ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  8. &lt;code&gt;LIBVA_DRIVER_NAME=iHD&lt;/code&gt; で明示する
&lt;/h1&gt;

&lt;p&gt;自動判定がうまくいかない場合には、使用する VA-API ドライバを明示できます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD &lt;span class="se"&gt;\&lt;/span&gt;
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;恒久的に指定したければ、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;としても構いません。&lt;/p&gt;




&lt;h1&gt;
  
  
  9. Intel Media SDK と oneVPL の違い
&lt;/h1&gt;

&lt;p&gt;QSV 周りでは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intel Media SDK
libmfx
oneVPL
libvpl
intel-vpl-gpu-rt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;といった名前が登場します。&lt;/p&gt;

&lt;p&gt;Intel の新しいソフトウェアスタックは、従来の Intel Media SDK から oneVPL へ移行しています。&lt;/p&gt;

&lt;p&gt;ただし Rocky Linux 9 の FFmpeg で何を入れるべきかは、&lt;strong&gt;使用している FFmpeg がどちらに対してビルドされているか確認することが重要&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;今回使用した RPM Fusion の FFmpeg 5.1.10 は、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を見ると、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--enable-libmfx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;でビルドされていました。&lt;/p&gt;

&lt;p&gt;そして実際に、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qa&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'libva|intel-media|libmfx|vpl'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を確認すると、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libva-2.22.0-1.el9.x86_64
intel-mediasdk-21.3.5-1.el9.x86_64
libva-utils-2.11.1-1.el9.x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となっていました。&lt;/p&gt;

&lt;p&gt;この環境では FFmpeg が &lt;code&gt;libmfx&lt;/code&gt;、つまり Intel Media SDK 経由の QSV を使用する構成です。&lt;/p&gt;

&lt;p&gt;したがって、&lt;strong&gt;「Rocky 9 なら必ず libvpl + intel-vpl-gpu-rt をインストールする」と考えないほうが安全&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;まず、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;の configure オプションを確認してください。&lt;/p&gt;




&lt;h1&gt;
  
  
  10. FFmpeg をインストールする
&lt;/h1&gt;

&lt;p&gt;RPM Fusion から FFmpeg を入れます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ffmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  11. FFmpeg が QSV 対応か確認する
&lt;/h1&gt;

&lt;p&gt;まずハードウェアアクセラレーション一覧を確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-hwaccels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;今回の FFmpeg では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hardware acceleration methods:
vdpau
cuda
vaapi
qsv
drm
opencl
vulkan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となっていました。&lt;/p&gt;

&lt;p&gt;ここで、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vaapi
qsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;が確認できます。&lt;/p&gt;

&lt;p&gt;次に QSV エンコーダを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-hide_banner&lt;/span&gt; &lt;span class="nt"&gt;-encoders&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'qsv|vaapi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;今回の環境では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;V..... h264_qsv     H.264 / AVC ... (Intel Quick Sync Video acceleration)
V....D h264_vaapi   H.264/AVC (VAAPI)
V..... hevc_qsv     HEVC (Intel Quick Sync Video acceleration)
V....D hevc_vaapi   H.265/HEVC (VAAPI)
V..... mjpeg_qsv    MJPEG (Intel Quick Sync Video acceleration)
V....D mjpeg_vaapi  MJPEG (VAAPI)
V..... mpeg2_qsv    MPEG-2 video (Intel Quick Sync Video acceleration)
V....D mpeg2_vaapi  MPEG-2 (VAAPI)
V....D vp8_vaapi    VP8 (VAAPI)
V....D vp9_vaapi    VP9 (VAAPI)
V..... vp9_qsv      VP9 video (Intel Quick Sync Video acceleration)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となりました。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;h264_qsv&lt;/code&gt; が一覧にあることと、実際に GPU が使用できることは別問題です。&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  12. まず &lt;code&gt;vainfo&lt;/code&gt; を正常にする
&lt;/h1&gt;

&lt;p&gt;QSV のトラブルシューティングでは、いきなり FFmpeg のオプションを変更し続けるより、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を最初に正常化したほうが早いです。&lt;/p&gt;

&lt;p&gt;今回の環境では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libva info: VA-API version 1.22.0
libva info: Trying to open /usr/lib64/dri/iHD_drv_video.so
libva info: va_openDriver() returns -1
libva info: Trying to open /usr/lib64/dri/i965_drv_video.so
libva info: va_openDriver() returns -1
vaInitialize failed with error code -1 (unknown libva error),exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となっていました。&lt;/p&gt;

&lt;p&gt;GPU は認識され、&lt;code&gt;i915&lt;/code&gt; も使用され、&lt;code&gt;/dev/dri/renderD128&lt;/code&gt; も存在しているため、問題は &lt;code&gt;libva&lt;/code&gt; / Intel Media Driver 付近にあると切り分けられます。&lt;/p&gt;




&lt;h1&gt;
  
  
  13. &lt;code&gt;iHD_drv_video.so&lt;/code&gt; がどの RPM に含まれるか確認する
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;さらに、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qf&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;で、どの RPM が提供しているファイルか確認できます。&lt;/p&gt;

&lt;p&gt;RPM が分からなければ、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dnf provides &lt;span class="s1"&gt;'*/iHD_drv_video.so'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;でも検索できます。&lt;/p&gt;

&lt;p&gt;期待するのは &lt;code&gt;intel-media-driver&lt;/code&gt; 系の RPM です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ここで注意したいのが &lt;code&gt;intel-mediasdk&lt;/code&gt; と &lt;code&gt;intel-media-driver&lt;/code&gt; は別物だということです。&lt;/p&gt;




&lt;h1&gt;
  
  
  14. ドライバの依存ライブラリを確認する
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ldd /usr/lib64/dri/iHD_drv_video.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ここに、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;があれば、その依存ライブラリが不足しています。&lt;/p&gt;




&lt;h1&gt;
  
  
  15. パッケージの出所とバージョンも確認する
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qi&lt;/span&gt; libva libva-utils intel-mediasdk intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;または、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dnf repoquery &lt;span class="nt"&gt;--installed&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{name} %{version}-%{release} %{repoid}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  libva libva-utils intel-mediasdk intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を使用します。&lt;/p&gt;




&lt;h1&gt;
  
  
  16. iHD を明示してテストする
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD &lt;span class="se"&gt;\&lt;/span&gt;
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;正常なら、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trying to open /usr/lib64/dri/iHD_drv_video.so
Found init function ...
va_openDriver() returns 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となります。&lt;/p&gt;




&lt;h1&gt;
  
  
  17. QSV 単体テストをする
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;vainfo&lt;/code&gt; が正常になったら、入力動画とは無関係なテスト映像で QSV エンコードを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; lavfi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;testsrc2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1280x720:rate&lt;span class="o"&gt;=&lt;/span&gt;30 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; 5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_qsv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-global_quality&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; null -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これが成功すれば H.264 QSV エンコード経路が動作していると判断できます。&lt;/p&gt;




&lt;h1&gt;
  
  
  18. VAAPI エンコードでも切り分ける
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vaapi_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; lavfi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;testsrc2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1280x720:rate&lt;span class="o"&gt;=&lt;/span&gt;30 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s1"&gt;'format=nv12,hwupload'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; 5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_vaapi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; null -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;VAAPI&lt;/th&gt;
&lt;th&gt;QSV&lt;/th&gt;
&lt;th&gt;考えられる原因&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NG&lt;/td&gt;
&lt;td&gt;NG&lt;/td&gt;
&lt;td&gt;VA-API / Intel Media Driver / GPU デバイス側&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;NG&lt;/td&gt;
&lt;td&gt;Media SDK / oneVPL / QSV runtime 側&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;GPU スタックは正常。元の FFmpeg コマンドを調査&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NG&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;通常はあまりない特殊な構成&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  19. QSV で H.264 をエンコードする
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_qsv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-global_quality&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  20. HEVC / H.265 を QSV でエンコードする
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v hevc_qsv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-global_quality&lt;/span&gt; 25 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  21. QSV decode + QSV encode
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-hwaccel&lt;/span&gt; qsv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-hwaccel_output_format&lt;/span&gt; qsv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_qsv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-global_quality&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;トラブルシューティングでは、まず CPU decode + QSV encode から始めるほうが切り分けやすくなります。&lt;/p&gt;




&lt;h1&gt;
  
  
  22. &lt;code&gt;yuv420p&lt;/code&gt; → &lt;code&gt;nv12&lt;/code&gt; の警告は致命的エラーではない
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incompatible pixel format 'yuv420p' for codec 'h264_qsv',
auto-selecting format 'nv12'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これは今回の VA-API エラーとは別問題です。&lt;/p&gt;

&lt;p&gt;必要なら、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; nv12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を指定できます。&lt;/p&gt;




&lt;h1&gt;
  
  
  23. ALSA の &lt;code&gt;Thread message queue blocking&lt;/code&gt; も別問題
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[alsa] Thread message queue blocking;
consider raising the thread_queue_size option
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;が出る場合は ALSA 入力の前に、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-thread_queue_size&lt;/span&gt; 1024
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;などを指定します。&lt;/p&gt;




&lt;h1&gt;
  
  
  24. Rocky Linux 9 のセットアップ例
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; epel-release

&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://download1.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm &lt;span class="se"&gt;\&lt;/span&gt;
  https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-9.noarch.rpm

&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  libva &lt;span class="se"&gt;\&lt;/span&gt;
  libva-utils &lt;span class="se"&gt;\&lt;/span&gt;
  intel-media-driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;今回のように &lt;code&gt;ffmpeg -version&lt;/code&gt; に、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--enable-libmfx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;がある環境では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; intel-mediasdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;も候補になります。&lt;/p&gt;

&lt;p&gt;Gemini Lake + RPM Fusion FFmpeg 5.1 系であれば、概ね、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  libva &lt;span class="se"&gt;\&lt;/span&gt;
  libva-utils &lt;span class="se"&gt;\&lt;/span&gt;
  intel-media-driver &lt;span class="se"&gt;\&lt;/span&gt;
  intel-mediasdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という構成から始めるのが分かりやすいでしょう。&lt;/p&gt;




&lt;h1&gt;
  
  
  25. 最終確認チェックリスト
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 0. nomodeset&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cmdline

&lt;span class="c"&gt;# 1. GPU / kernel driver&lt;/span&gt;
lspci &lt;span class="nt"&gt;-nnk&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A4&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'VGA|Display'&lt;/span&gt;

&lt;span class="c"&gt;# 2. Kernel module&lt;/span&gt;
lsmod | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'i915|xe'&lt;/span&gt;

&lt;span class="c"&gt;# 3. DRM&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/dri/

&lt;span class="c"&gt;# 4. Intel Media Driver&lt;/span&gt;
rpm &lt;span class="nt"&gt;-q&lt;/span&gt; intel-media-driver

&lt;span class="c"&gt;# 5. iHD driver&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so
rpm &lt;span class="nt"&gt;-qf&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so

&lt;span class="c"&gt;# 6. VA-API&lt;/span&gt;
&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD &lt;span class="se"&gt;\&lt;/span&gt;
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128

&lt;span class="c"&gt;# 7. FFmpeg HW acceleration&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-hwaccels&lt;/span&gt;

&lt;span class="c"&gt;# 8. QSV encoders&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-hide_banner&lt;/span&gt; &lt;span class="nt"&gt;-encoders&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;_qsv

&lt;span class="c"&gt;# 9. QSV decoders&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-hide_banner&lt;/span&gt; &lt;span class="nt"&gt;-decoders&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;_qsv

&lt;span class="c"&gt;# 10. QSV encode test&lt;/span&gt;
ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-qsv_device&lt;/span&gt; /dev/dri/renderD128 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; lavfi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;testsrc2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1280x720:rate&lt;span class="o"&gt;=&lt;/span&gt;30 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; 5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v h264_qsv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-global_quality&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; null -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認順序は次のように考えると分かりやすくなります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nomodeset の有無
↓
Intel GPU
↓
i915 / xe
↓
/dev/dri/renderD128
↓
VA-API / libva
↓
Intel Media Driver
↓
Media SDK / oneVPL
↓
FFmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  今回の Gemini Lake / UHD Graphics 605 で分かったこと
&lt;/h1&gt;

&lt;p&gt;今回の実機では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intel Corporation GeminiLake [UHD Graphics 605]
Kernel driver in use: i915
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;であり、&lt;code&gt;/dev/dri/renderD128&lt;/code&gt; も存在していました。&lt;/p&gt;

&lt;p&gt;さらに FFmpeg は &lt;code&gt;--enable-libmfx&lt;/code&gt; 付きで、&lt;code&gt;qsv&lt;/code&gt; / &lt;code&gt;vaapi&lt;/code&gt; を認識し、&lt;code&gt;h264_qsv&lt;/code&gt;、&lt;code&gt;hevc_qsv&lt;/code&gt;、&lt;code&gt;vp9_qsv&lt;/code&gt; なども列挙できました。&lt;/p&gt;

&lt;p&gt;しかし、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;では、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trying to open /usr/lib64/dri/iHD_drv_video.so
va_openDriver() returns -1

Trying to open /usr/lib64/dri/i965_drv_video.so
va_openDriver() returns -1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となっていました。&lt;/p&gt;

&lt;p&gt;QSV テストでも、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failed to initialise VAAPI connection
Device creation failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;となりました。&lt;/p&gt;

&lt;p&gt;このことから、問題は FFmpeg のエンコードオプションではなく、VA-API / Intel Media Driver 層にあると切り分けられます。&lt;/p&gt;

&lt;p&gt;このような場合は、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qf&lt;/span&gt; /usr/lib64/dri/iHD_drv_video.so
ldd /usr/lib64/dri/iHD_drv_video.so

&lt;span class="nv"&gt;LIBVA_DRIVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iHD &lt;span class="se"&gt;\&lt;/span&gt;
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;の順で Intel Media Driver の導入状態・依存関係を調べます。&lt;/p&gt;




&lt;h1&gt;
  
  
  まとめ
&lt;/h1&gt;

&lt;p&gt;Rocky Linux 9 で FFmpeg + Intel QSV を使う場合、単に &lt;code&gt;ffmpeg -encoders&lt;/code&gt; に &lt;code&gt;h264_qsv&lt;/code&gt; が表示されれば完了、というわけではありません。&lt;/p&gt;

&lt;p&gt;必要になるレイヤは概ね次の通りです。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;コンポーネント&lt;/th&gt;
&lt;th&gt;必要性&lt;/th&gt;
&lt;th&gt;役割&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;nomodeset&lt;/code&gt; 無効化&lt;/td&gt;
&lt;td&gt;重要&lt;/td&gt;
&lt;td&gt;DRM/KMS を正常に初期化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intel GPU&lt;/td&gt;
&lt;td&gt;必須&lt;/td&gt;
&lt;td&gt;ハードウェア&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;i915&lt;/code&gt; / &lt;code&gt;xe&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;必須&lt;/td&gt;
&lt;td&gt;Kernel GPU driver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/dev/dri/renderD128&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;必須&lt;/td&gt;
&lt;td&gt;DRM render node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;libva&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Linux QSV 環境で重要&lt;/td&gt;
&lt;td&gt;VA-API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;intel-media-driver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;対応 Intel GPU で重要&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iHD_drv_video.so&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;intel-mediasdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;libmfx 構成&lt;/td&gt;
&lt;td&gt;Legacy QSV runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;libvpl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;oneVPL 構成&lt;/td&gt;
&lt;td&gt;oneVPL dispatcher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;intel-vpl-gpu-rt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;対応する新世代 GPU&lt;/td&gt;
&lt;td&gt;oneVPL GPU implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QSV 対応 FFmpeg&lt;/td&gt;
&lt;td&gt;必須&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;h264_qsv&lt;/code&gt; / &lt;code&gt;hevc_qsv&lt;/code&gt; 等&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;特に、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failed to initialise VAAPI connection
Failed to create a VAAPI device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;が出た場合には、エンコードオプションを変更する前に、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cmdline
vainfo &lt;span class="nt"&gt;--display&lt;/span&gt; drm &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/dri/renderD128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;を確認するのが近道です。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nomodeset&lt;/code&gt; が残っていれば外し、&lt;code&gt;vainfo&lt;/code&gt; が正常に動かなければ VA-API / Intel Media Driver の問題を先に解決します。&lt;/p&gt;

&lt;p&gt;Rocky Linux 9 で QSV を構築するときは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nomodeset
↓
GPU
↓
Kernel driver
↓
DRM
↓
VA-API
↓
Intel Media Driver
↓
Media SDK / oneVPL
↓
FFmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;というレイヤを意識して、&lt;strong&gt;下から一段ずつ正常動作を確認する&lt;/strong&gt;のが最も確実なセットアップ方法です。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Launching MSYS2 Inside a WSL tmux Session</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:47:59 +0000</pubDate>
      <link>https://dev.to/vast-cow/launching-msys2-inside-a-wsl-tmux-session-n6m</link>
      <guid>https://dev.to/vast-cow/launching-msys2-inside-a-wsl-tmux-session-n6m</guid>
      <description>&lt;p&gt;This setup allows you to start a WSL &lt;code&gt;tmux&lt;/code&gt; session from MSYS2 and then launch an interactive MSYS2 shell inside that &lt;code&gt;tmux&lt;/code&gt; session. It combines the convenience of running &lt;code&gt;tmux&lt;/code&gt; in WSL with the familiar MSYS2 environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Purpose
&lt;/h2&gt;

&lt;p&gt;The main goal is to keep &lt;code&gt;tmux&lt;/code&gt; running in WSL while using MSYS2 as the interactive shell inside each &lt;code&gt;tmux&lt;/code&gt; pane or window. This approach lets you manage terminal sessions with WSL &lt;code&gt;tmux&lt;/code&gt; while continuing to work with MSYS2 tools.&lt;/p&gt;

&lt;p&gt;The launcher script starts WSL, connects to a dedicated &lt;code&gt;tmux&lt;/code&gt; server, and passes the required environment variables so that MSYS2 starts correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launcher Script
&lt;/h2&gt;

&lt;p&gt;The launcher script performs the following tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exports the environment variables required by both WSL and MSYS2.&lt;/li&gt;
&lt;li&gt;Prevents automatic path conversion where it is not desired.&lt;/li&gt;
&lt;li&gt;Starts &lt;code&gt;tmux&lt;/code&gt; inside the target WSL distribution.&lt;/li&gt;
&lt;li&gt;Uses a dedicated &lt;code&gt;tmux&lt;/code&gt; socket and configuration file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this script, you can launch or attach to the &lt;code&gt;tmux&lt;/code&gt; session directly from MSYS2.&lt;/p&gt;

&lt;h2&gt;
  
  
  tmux Configuration
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;tmux&lt;/code&gt; configuration changes the default command so that every new pane starts an MSYS2 shell instead of a Linux shell.&lt;/p&gt;

&lt;p&gt;Before launching MSYS2, the configuration clears the &lt;code&gt;PWD&lt;/code&gt; environment variable and updates &lt;code&gt;WSLENV&lt;/code&gt; so that the necessary environment variables are available inside the MSYS2 process. It then starts an interactive MSYS2 Bash session.&lt;/p&gt;

&lt;p&gt;The configuration also adds &lt;code&gt;MSYSTEM&lt;/code&gt; and &lt;code&gt;WSLENV&lt;/code&gt; to &lt;code&gt;tmux&lt;/code&gt;'s &lt;code&gt;update-environment&lt;/code&gt; list. This ensures that newly created panes inherit the correct environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Save the launcher script and make it executable.&lt;/li&gt;
&lt;li&gt;Place the &lt;code&gt;tmux&lt;/code&gt; configuration in the specified configuration file.&lt;/li&gt;
&lt;li&gt;Run the launcher script from MSYS2.&lt;/li&gt;
&lt;li&gt;A WSL &lt;code&gt;tmux&lt;/code&gt; session will start, and each new pane or window will automatically open an MSYS2 shell.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This setup provides a simple way to use WSL &lt;code&gt;tmux&lt;/code&gt; as the session manager while working in the MSYS2 environment.&lt;/p&gt;

&lt;p&gt;Please write a simple article &lt;strong&gt;in English&lt;/strong&gt; about the content below labeled “---- content ----”.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write the title as a level-1 heading using &lt;code&gt;#&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You may use &lt;code&gt;##&lt;/code&gt;, &lt;code&gt;###&lt;/code&gt;, and &lt;code&gt;####&lt;/code&gt; to denote sections, subsections, and sub-subsections.&lt;/li&gt;
&lt;li&gt;Do not include any questions asking for additional suggestions.&lt;/li&gt;
&lt;li&gt;Focus on the purpose and how to use it, and keep technical explanations to a minimum.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;wrapper executed on msys2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;WSLENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MSYSTEM&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WSLENV&lt;/span&gt;:+:&lt;span class="nv"&gt;$WSLENV&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MSYS2_ARG_CONV_EXCL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MSYS2_ENV_CONV_EXCL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"WSLENV;TMUX;TMUX_PANE&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MSYS2_ENV_CONV_EXCL&lt;/span&gt;:+&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nv"&gt;$MSYS2_ENV_CONV_EXCL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;exec &lt;/span&gt;wsl.exe &lt;span class="nt"&gt;-d&lt;/span&gt; tmux &lt;span class="nt"&gt;--exec&lt;/span&gt; tmux &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-L&lt;/span&gt; msys2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-f&lt;/span&gt; /path/to/tmux-msys2.conf &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tmux-msys2.conf inside WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;set&lt;/span&gt;-&lt;span class="n"&gt;option&lt;/span&gt; -&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;-&lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;unset&lt;/span&gt; &lt;span class="n"&gt;PWD&lt;/span&gt;
&lt;span class="n"&gt;WSLENV&lt;/span&gt;=&lt;span class="s2"&gt;"MSYSTEM:PWD/p:$WSLENV"&lt;/span&gt;
&lt;span class="n"&gt;exec&lt;/span&gt; /&lt;span class="n"&gt;mnt&lt;/span&gt;/&lt;span class="n"&gt;c&lt;/span&gt;/&lt;span class="n"&gt;msys64&lt;/span&gt;/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;bash&lt;/span&gt;.&lt;span class="n"&gt;exe&lt;/span&gt; --&lt;span class="n"&gt;login&lt;/span&gt; -&lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;-&lt;span class="n"&gt;option&lt;/span&gt; -&lt;span class="n"&gt;ag&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;-&lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="n"&gt;MSYSTEM&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;-&lt;span class="n"&gt;option&lt;/span&gt; -&lt;span class="n"&gt;ag&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;-&lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="n"&gt;WSLENV&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Keeping Rocky Linux 9 Up to Date with `dnf-automatic`</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Tue, 04 Aug 2026 06:50:08 +0000</pubDate>
      <link>https://dev.to/vast-cow/keeping-rocky-linux-9-up-to-date-with-dnf-automatic-1i0o</link>
      <guid>https://dev.to/vast-cow/keeping-rocky-linux-9-up-to-date-with-dnf-automatic-1i0o</guid>
      <description>&lt;p&gt;Keeping your system updated is one of the most important steps for maintaining security, stability, and performance. On &lt;strong&gt;Rocky Linux 9.6&lt;/strong&gt;, the &lt;code&gt;dnf-automatic&lt;/code&gt; package can periodically check for available updates and either notify administrators, download packages, or install updates automatically.&lt;/p&gt;

&lt;p&gt;This guide covers installation, configuration, automatic updates, and a notification-only configuration that uses MOTD without downloading or installing packages.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Install &lt;code&gt;dnf-automatic&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;dnf-automatic&lt;/code&gt; tool is not installed by default. Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; dnf-automatic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Configure &lt;code&gt;dnf-automatic&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The main configuration file is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/dnf/automatic.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open it with your preferred text editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/dnf/automatic.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key options
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Upgrade type
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;upgrade_type&lt;/code&gt; setting controls which updates are detected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[commands]&lt;/span&gt;
&lt;span class="py"&gt;upgrade_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available values include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;default&lt;/code&gt; — all available updates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;security&lt;/code&gt; — only updates associated with security advisories&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Download updates
&lt;/h4&gt;

&lt;p&gt;To allow automatic package downloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prevent automatic package downloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Apply updates automatically
&lt;/h4&gt;

&lt;p&gt;To install available updates automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prevent automatic installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;apply_updates = yes&lt;/code&gt; is configured, packages must also be downloaded.&lt;/p&gt;

&lt;h4&gt;
  
  
  Emitters
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;[emitters]&lt;/code&gt; section controls how results are reported. Available mechanisms include standard output, email, custom commands, and MOTD.&lt;/p&gt;

&lt;p&gt;For example, to report results through MOTD:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[emitters]&lt;/span&gt;
&lt;span class="py"&gt;emit_via&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;motd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MOTD emitter writes the update report to &lt;code&gt;/etc/motd&lt;/code&gt;, allowing administrators to see it when logging in through SSH or a local console.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Enable Automatic Updates
&lt;/h2&gt;

&lt;p&gt;The generic &lt;code&gt;dnf-automatic.timer&lt;/code&gt; follows the behavior configured in &lt;code&gt;/etc/dnf/automatic.conf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, to download and install security updates automatically, configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[commands]&lt;/span&gt;
&lt;span class="py"&gt;upgrade_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enable and start the timer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This causes &lt;code&gt;dnf-automatic&lt;/code&gt; to check for and process updates according to the configuration file.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Notification Only with MOTD
&lt;/h2&gt;

&lt;p&gt;To notify administrators about available updates without downloading or installing any packages, configure &lt;code&gt;dnf-automatic&lt;/code&gt; to use the MOTD emitter.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/dnf/automatic.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/dnf/automatic.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the following settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[commands]&lt;/span&gt;
&lt;span class="py"&gt;upgrade_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;

&lt;span class="nn"&gt;[emitters]&lt;/span&gt;
&lt;span class="py"&gt;emit_via&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;motd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks repository metadata for available security updates&lt;/li&gt;
&lt;li&gt;Does not download update packages&lt;/li&gt;
&lt;li&gt;Does not install update packages&lt;/li&gt;
&lt;li&gt;Writes the result to &lt;code&gt;/etc/motd&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enable the notification-only timer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;dnf-automatic-notifyonly.timer&lt;/code&gt; unit overrides the download and installation behavior and performs notification only. The explicit &lt;code&gt;download_updates = no&lt;/code&gt; and &lt;code&gt;apply_updates = no&lt;/code&gt; settings are retained in the configuration for clarity and as a safe default when the generic timer is used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disable download and installation timers
&lt;/h3&gt;

&lt;p&gt;To ensure that no other &lt;code&gt;dnf-automatic&lt;/code&gt; timer downloads or installs packages, disable the generic, download, and installation timers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic.timer
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-download.timer
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-install.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enable only the notification timer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the enabled timers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl list-timers &lt;span class="nt"&gt;--all&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;dnf-automatic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only &lt;code&gt;dnf-automatic-notifyonly.timer&lt;/code&gt; should be enabled for the notification-only configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the MOTD output
&lt;/h3&gt;

&lt;p&gt;Run the service manually to test the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start dnf-automatic-notifyonly.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Display the generated MOTD:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/motd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The message will also be displayed during the next SSH or console login, provided the system's login configuration displays &lt;code&gt;/etc/motd&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Checking for updates still requires refreshing or reading repository metadata. The notification-only configuration prevents RPM packages from being downloaded; it does not eliminate repository metadata traffic.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Other Timer Modes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dnf-automatic&lt;/code&gt; provides several systemd timers for different operating modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Follow &lt;code&gt;/etc/dnf/automatic.conf&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Notify only
&lt;/h3&gt;

&lt;p&gt;Checks for updates and reports the result without downloading or installing packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Download without installation
&lt;/h3&gt;

&lt;p&gt;Downloads available packages but does not install them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-download.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Download and install
&lt;/h3&gt;

&lt;p&gt;Downloads and installs available updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-install.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specialized timer units override the corresponding download and installation settings in &lt;code&gt;/etc/dnf/automatic.conf&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Verification and Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Check the notification-only timer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check when it will run next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl list-timers &lt;span class="nt"&gt;--all&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;dnf-automatic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the service logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; dnf-automatic-notifyonly.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check recent logs for all &lt;code&gt;dnf-automatic&lt;/code&gt; units:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s1"&gt;'dnf-automatic*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm which timers are enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl is-enabled dnf-automatic.timer
systemctl is-enabled dnf-automatic-notifyonly.timer
systemctl is-enabled dnf-automatic-download.timer
systemctl is-enabled dnf-automatic-install.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a notification-only system, the expected result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;disabled
enabled
disabled
disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Rocky Linux 9.6 can use &lt;code&gt;dnf-automatic&lt;/code&gt; in several modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic installation for systems that should remain patched without manual intervention&lt;/li&gt;
&lt;li&gt;Automatic download for systems where installation is performed separately&lt;/li&gt;
&lt;li&gt;Notification only for controlled environments where administrators review updates before taking action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a notification-only configuration with no package downloads, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[commands]&lt;/span&gt;
&lt;span class="py"&gt;upgrade_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;

&lt;span class="nn"&gt;[emitters]&lt;/span&gt;
&lt;span class="py"&gt;emit_via&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;motd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration reports available security updates through MOTD while leaving package download and installation under manual control.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;code&gt;dnf-automatic&lt;/code&gt; を使用して Rocky Linux 9 を最新の状態に保つ
&lt;/h1&gt;

&lt;p&gt;システムを最新の状態に維持することは、セキュリティ、安定性、およびパフォーマンスを確保するために最も重要な作業の一つです。&lt;strong&gt;Rocky Linux 9.6&lt;/strong&gt; では、&lt;code&gt;dnf-automatic&lt;/code&gt; パッケージを使用することで、利用可能な更新を定期的に確認し、管理者への通知、パッケージのダウンロード、または更新の自動インストールを実行できます。&lt;/p&gt;

&lt;p&gt;このガイドでは、インストール、設定、自動更新の構成、およびパッケージのダウンロードやインストールを行わずに MOTD を使用して通知のみを行う構成について説明します。&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;dnf-automatic&lt;/code&gt; をインストールする
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dnf-automatic&lt;/code&gt; はデフォルトではインストールされていません。次のコマンドでインストールします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; dnf-automatic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. &lt;code&gt;dnf-automatic&lt;/code&gt; を設定する
&lt;/h2&gt;

&lt;p&gt;メインの設定ファイルは次の場所にあります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/dnf/automatic.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;お好みのテキストエディタで開きます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/dnf/automatic.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  主な設定項目
&lt;/h3&gt;

&lt;h4&gt;
  
  
  更新の種類
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;upgrade_type&lt;/code&gt; は、どの種類の更新を検出するかを指定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[commands]&lt;/span&gt;
&lt;span class="py"&gt;upgrade_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;指定できる値は次のとおりです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;default&lt;/code&gt; — 利用可能なすべての更新&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;security&lt;/code&gt; — セキュリティアドバイザリに関連付けられた更新のみ&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  更新パッケージのダウンロード
&lt;/h4&gt;

&lt;p&gt;パッケージを自動的にダウンロードする場合は、次のように設定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;自動ダウンロードを無効にする場合は、次のように設定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  更新の自動適用
&lt;/h4&gt;

&lt;p&gt;利用可能な更新を自動的にインストールする場合は、次のように設定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;自動インストールを無効にする場合は、次のように設定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;apply_updates = yes&lt;/code&gt; を設定する場合は、パッケージのダウンロードも有効になっている必要があります。&lt;/p&gt;

&lt;h4&gt;
  
  
  Emitters（通知方法）
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;[emitters]&lt;/code&gt; セクションでは、結果の通知方法を指定します。利用できる通知方法には、標準出力、メール、カスタムコマンド、および MOTD があります。&lt;/p&gt;

&lt;p&gt;たとえば、MOTD を使用して通知する場合は次のように設定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[emitters]&lt;/span&gt;
&lt;span class="py"&gt;emit_via&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;motd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MOTD エミッターは更新結果を &lt;code&gt;/etc/motd&lt;/code&gt; に書き込み、SSH やローカルコンソールでログインした際に管理者が確認できるようにします。&lt;/p&gt;




&lt;h2&gt;
  
  
  3. 自動更新を有効にする
&lt;/h2&gt;

&lt;p&gt;汎用の &lt;code&gt;dnf-automatic.timer&lt;/code&gt; は、&lt;code&gt;/etc/dnf/automatic.conf&lt;/code&gt; に設定された内容に従って動作します。&lt;/p&gt;

&lt;p&gt;たとえば、セキュリティ更新を自動的にダウンロードしてインストールする場合は、次のように設定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[commands]&lt;/span&gt;
&lt;span class="py"&gt;upgrade_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、タイマーを有効化して起動します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これにより、&lt;code&gt;dnf-automatic&lt;/code&gt; は設定ファイルに従って更新を確認し、必要な処理を実行します。&lt;/p&gt;




&lt;h2&gt;
  
  
  4. MOTD を使用した通知のみの構成
&lt;/h2&gt;

&lt;p&gt;パッケージをダウンロードまたはインストールせず、利用可能な更新だけを管理者へ通知する場合は、&lt;code&gt;dnf-automatic&lt;/code&gt; を MOTD エミッターで構成します。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/dnf/automatic.conf&lt;/code&gt; を編集します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/dnf/automatic.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;次のように設定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[commands]&lt;/span&gt;
&lt;span class="py"&gt;upgrade_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;

&lt;span class="nn"&gt;[emitters]&lt;/span&gt;
&lt;span class="py"&gt;emit_via&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;motd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;この構成では次の動作を行います。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;利用可能なセキュリティ更新を確認する&lt;/li&gt;
&lt;li&gt;更新パッケージはダウンロードしない&lt;/li&gt;
&lt;li&gt;更新パッケージはインストールしない&lt;/li&gt;
&lt;li&gt;結果を &lt;code&gt;/etc/motd&lt;/code&gt; に書き込む&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通知専用タイマーを有効にします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dnf-automatic-notifyonly.timer&lt;/code&gt; は、ダウンロードおよびインストール動作を上書きし、通知のみを実行します。&lt;code&gt;download_updates = no&lt;/code&gt; および &lt;code&gt;apply_updates = no&lt;/code&gt; を設定ファイルに明示的に記述しておくことで、設定内容が分かりやすくなり、汎用タイマーを使用した場合でも安全なデフォルトとして機能します。&lt;/p&gt;

&lt;h3&gt;
  
  
  ダウンロードおよびインストール用タイマーを無効にする
&lt;/h3&gt;

&lt;p&gt;他の &lt;code&gt;dnf-automatic&lt;/code&gt; タイマーによってパッケージがダウンロードまたはインストールされないようにするため、汎用タイマー、ダウンロードタイマー、およびインストールタイマーを無効にします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic.timer
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-download.timer
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-install.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、通知専用タイマーのみを有効にします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;有効になっているタイマーを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl list-timers &lt;span class="nt"&gt;--all&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;dnf-automatic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;通知専用構成では、&lt;code&gt;dnf-automatic-notifyonly.timer&lt;/code&gt; のみが有効になっていることを確認してください。&lt;/p&gt;

&lt;h3&gt;
  
  
  MOTD の出力を確認する
&lt;/h3&gt;

&lt;p&gt;設定をテストするためにサービスを手動で実行します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start dnf-automatic-notifyonly.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;生成された MOTD を表示します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/motd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;システムのログイン設定で &lt;code&gt;/etc/motd&lt;/code&gt; が表示されるようになっていれば、次回 SSH またはローカルコンソールでログインした際にも同じ内容が表示されます。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注:&lt;/strong&gt; 更新の確認では、リポジトリメタデータの更新または読み込みが必要です。通知専用構成では RPM パッケージのダウンロードは行われませんが、リポジトリメタデータの通信自体は発生します。&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. その他のタイマーモード
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dnf-automatic&lt;/code&gt; には、用途に応じた複数の systemd タイマーが用意されています。&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;/etc/dnf/automatic.conf&lt;/code&gt; の設定に従う
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  通知のみ
&lt;/h3&gt;

&lt;p&gt;更新を確認し、ダウンロードやインストールを行わずに結果だけを通知します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ダウンロードのみ
&lt;/h3&gt;

&lt;p&gt;利用可能な更新パッケージをダウンロードしますが、インストールは行いません。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-download.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ダウンロードとインストール
&lt;/h3&gt;

&lt;p&gt;更新パッケージをダウンロードし、そのままインストールします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-install.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これらの専用タイマーは、&lt;code&gt;/etc/dnf/automatic.conf&lt;/code&gt; に設定されたダウンロードおよびインストールに関する項目を必要に応じて上書きして動作します。&lt;/p&gt;




&lt;h2&gt;
  
  
  6. 動作確認とトラブルシューティング
&lt;/h2&gt;

&lt;p&gt;通知専用タイマーの状態を確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;次回の実行予定を確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl list-timers &lt;span class="nt"&gt;--all&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;dnf-automatic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;サービスログを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; dnf-automatic-notifyonly.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dnf-automatic&lt;/code&gt; に関連するすべてのログを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s1"&gt;'dnf-automatic*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;各タイマーが有効かどうかを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl is-enabled dnf-automatic.timer
systemctl is-enabled dnf-automatic-notifyonly.timer
systemctl is-enabled dnf-automatic-download.timer
systemctl is-enabled dnf-automatic-install.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;通知専用構成では、期待される結果は次のとおりです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;disabled
enabled
disabled
disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;Rocky Linux 9.6 では、&lt;code&gt;dnf-automatic&lt;/code&gt; を次のような用途に応じて利用できます。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;手動操作なしで常に最新の状態を維持したいシステム向けの自動インストール&lt;/li&gt;
&lt;li&gt;インストールは別途実施し、事前にパッケージだけを取得したい環境向けの自動ダウンロード&lt;/li&gt;
&lt;li&gt;更新内容を管理者が確認したうえで適用する運用向けの通知のみ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;パッケージをダウンロードせず、通知のみを行う構成は次のとおりです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[commands]&lt;/span&gt;
&lt;span class="py"&gt;upgrade_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="py"&gt;download_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;span class="py"&gt;apply_updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;

&lt;span class="nn"&gt;[emitters]&lt;/span&gt;
&lt;span class="py"&gt;emit_via&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;motd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;有効にするタイマーは次のものだけです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;この構成では、利用可能なセキュリティ更新が MOTD を通じて通知され、パッケージのダウンロードおよびインストールは手動管理のまま維持されます。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Install UEK and bcache-tools on Rocky Linux 9</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Sun, 02 Aug 2026 06:11:33 +0000</pubDate>
      <link>https://dev.to/vast-cow/how-to-install-uek-and-bcache-tools-on-rocky-linux-9-22np</link>
      <guid>https://dev.to/vast-cow/how-to-install-uek-and-bcache-tools-on-rocky-linux-9-22np</guid>
      <description>&lt;h2&gt;
  
  
  Strategy
&lt;/h2&gt;

&lt;p&gt;Keep Rocky Linux 9 BaseOS/AppStream unchanged, and only retrieve the following from the Oracle Linux side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UEK itself: &lt;code&gt;kernel-uek&lt;/code&gt; and its dependent subpackages&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bcache-tools&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Do not install Oracle Linux userland, &lt;code&gt;oraclelinux-release-el9&lt;/code&gt;, Oracle's &lt;code&gt;glibc&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Oracle Linux Yum Server officially provides instructions for use from RHEL-compatible distributions, but the configuration of running UEK on Rocky Linux is not considered officially supported by either Oracle or Rocky. (&lt;a href="https://yum.oracle.com/getting-started.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The following assumes &lt;strong&gt;x86_64&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  UEK R7 or R8
&lt;/h2&gt;

&lt;p&gt;As of August 2026, the following are available for Oracle Linux 9:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Series&lt;/th&gt;
&lt;th&gt;Kernel Series&lt;/th&gt;
&lt;th&gt;Selection Criteria&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UEK R7&lt;/td&gt;
&lt;td&gt;5.15&lt;/td&gt;
&lt;td&gt;Relatively conservative in mixed configuration with Rocky 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UEK R8&lt;/td&gt;
&lt;td&gt;6.12&lt;/td&gt;
&lt;td&gt;Prioritize newer hardware/features&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Oracle's current UEK R8 repository contains 6.12 series &lt;code&gt;kernel-uek&lt;/code&gt;, &lt;code&gt;kernel-uek-core&lt;/code&gt;, and various modules packages. (&lt;a href="https://yum.oracle.com/repo/OracleLinux/OL9/UEKR8/x86_64/index.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;br&gt;
UEK R7 is 5.15 series. (&lt;a href="https://yum.oracle.com/repo/OracleLinux/OL9/UEKR7/x86_64/index.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Here, to slightly reduce mixed configuration risk, we'll use &lt;strong&gt;UEK R7&lt;/strong&gt; as an example. To switch to R8, replace &lt;code&gt;UEKR7&lt;/code&gt; with &lt;code&gt;UEKR8&lt;/code&gt; in the URLs.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Pre-check
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/rocky-release
&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;
findmnt /boot
findmnt /boot/efi 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;mokutil &lt;span class="nt"&gt;--sb-state&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do not remove the existing Rocky kernel. It serves as recovery if UEK fails to boot.&lt;/p&gt;

&lt;p&gt;Before working, update Rocky to its normal state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade &lt;span class="nt"&gt;--refresh&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reboot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Register Oracle Linux 9 Signing Key
&lt;/h2&gt;

&lt;p&gt;Place Oracle's official OL9 key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://yum.oracle.com/RPM-GPG-KEY-oracle-ol9 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the fingerprint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpg &lt;span class="nt"&gt;--show-keys&lt;/span&gt; &lt;span class="nt"&gt;--with-fingerprint&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm it matches at least the following fingerprints published by Oracle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3E6D 826D 3FBA B389 C2F3 8E34 BC4D 06A0 8D8B 756F
9822 3175 9C74 6706 5D0C E9B2 A7DD 0708 8B4E FBE6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OL9 key acquisition source and fingerprints are published by Oracle. (&lt;a href="https://yum.oracle.com/faq.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Also import into the RPM database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;rpm &lt;span class="nt"&gt;--import&lt;/span&gt; /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Create a Minimally Restricted Oracle Repository
&lt;/h2&gt;

&lt;p&gt;Do not install &lt;code&gt;oraclelinux-release-el9&lt;/code&gt;; create your own repo file with just two entries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/yum.repos.d/oracle-uek-minimal.repo &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[oracle-uek-r7-minimal]
name=Oracle Linux 9 UEK R7 - restricted
baseurl=https://yum.oracle.com/repo/OracleLinux/OL9/UEKR7/&lt;/span&gt;&lt;span class="nv"&gt;$basearch&lt;/span&gt;&lt;span class="sh"&gt;/
enabled=0
gpgcheck=1
repo_gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
includepkgs=kernel-uek,kernel-uek-core,kernel-uek-modules,kernel-uek-modules-extra
metadata_expire=6h
skip_if_unavailable=0

[oracle-baseos-bcache-minimal]
name=Oracle Linux 9 BaseOS - bcache-tools only
baseurl=https://yum.oracle.com/repo/OracleLinux/OL9/baseos/latest/&lt;/span&gt;&lt;span class="nv"&gt;$basearch&lt;/span&gt;&lt;span class="sh"&gt;/
enabled=0
gpgcheck=1
repo_gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
includepkgs=bcache-tools
metadata_expire=6h
skip_if_unavailable=0
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two important points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;enabled=0&lt;/code&gt;: Normal &lt;code&gt;dnf upgrade&lt;/code&gt; won't use Oracle repositories&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;includepkgs=&lt;/code&gt;: Prevents packages other than explicitly listed ones from being obtained from Oracle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Oracle Linux 9 BaseOS URL is the same as what Oracle recommends for RHEL-compatible environments. (&lt;a href="https://yum.oracle.com/getting-started.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  If Using UEK R8
&lt;/h3&gt;

&lt;p&gt;Instead of R7, use the following UEK entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[oracle-uek-r8-minimal]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Oracle Linux 9 UEK R8 - restricted&lt;/span&gt;
&lt;span class="py"&gt;baseurl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://yum.oracle.com/repo/OracleLinux/OL9/UEKR8/$basearch/&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;gpgcheck&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;repo_gpgcheck&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;gpgkey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9&lt;/span&gt;
&lt;span class="py"&gt;includepkgs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;kernel-uek,kernel-uek-core,kernel-uek-modules-core,kernel-uek-modules,kernel-uek-modules-extra&lt;/span&gt;
&lt;span class="py"&gt;metadata_expire&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;6h&lt;/span&gt;
&lt;span class="py"&gt;skip_if_unavailable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;R8 has different package splitting from R7, including &lt;code&gt;kernel-uek-modules-core&lt;/code&gt;. (&lt;a href="https://yum.oracle.com/repo/OracleLinux/OL9/UEKR8/x86_64/index.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Verify Packages Visible from Oracle
&lt;/h2&gt;

&lt;p&gt;First, check candidates without installing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf clean metadata

&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'oracle-*'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  repoquery &lt;span class="nt"&gt;--available&lt;/span&gt; &lt;span class="s1"&gt;'kernel-uek*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also check &lt;code&gt;bcache-tools&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'oracle-*'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  repoquery &lt;span class="nt"&gt;--available&lt;/span&gt; &lt;span class="nt"&gt;--info&lt;/span&gt; bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Oracle Linux 9, &lt;code&gt;bcache-tools&lt;/code&gt; is provided as an Oracle BaseOS additional package. (&lt;a href="https://docs.oracle.com/en/operating-systems/oracle-linux/9/relnotes9.1/ol-PackageChangesfromtheUpstreamRelease.html" rel="noopener noreferrer"&gt;Oracle Docs&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify Sources
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf repoquery &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--available&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{name}-%{evr}.%{arch} &amp;lt;- %{repoid}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Pre-check the Transaction
&lt;/h2&gt;

&lt;p&gt;First, use &lt;code&gt;--assumeno&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--assumeno&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check these points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only &lt;code&gt;kernel-uek*&lt;/code&gt; and &lt;code&gt;bcache-tools&lt;/code&gt; come from Oracle&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;glibc&lt;/code&gt;, &lt;code&gt;systemd&lt;/code&gt;, &lt;code&gt;dracut&lt;/code&gt;, &lt;code&gt;grub2&lt;/code&gt;, etc. are not replaced with Oracle versions&lt;/li&gt;
&lt;li&gt;Rocky BaseOS/AppStream packages are not removed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--allowerasing&lt;/code&gt; is not required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Abort if any unexpected Oracle packages appear.&lt;/p&gt;

&lt;p&gt;For a stricter check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--assumeno&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Install UEK and bcache-tools
&lt;/h2&gt;

&lt;p&gt;If the pre-check passes, execute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;kernel-uek&lt;/code&gt; meta package installs &lt;code&gt;kernel-uek-core&lt;/code&gt; and modules as dependencies. The UEK R7 repository contains these at the same version. (&lt;a href="https://yum.oracle.com/repo/OracleLinux/OL9/UEKR7/x86_64/index.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qa&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^(kernel-uek|bcache-tools)'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also verify the vendor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{NAME} %{VERSION}-%{RELEASE} | %{VENDOR}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check all Oracle-origin packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qa&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{NAME} %{VERSION}-%{RELEASE} | %{VENDOR}\n'&lt;/span&gt; |
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; oracle |
&lt;span class="nb"&gt;sort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm there are no unintended Oracle userland packages.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Verify initramfs and bcache Module
&lt;/h2&gt;

&lt;p&gt;Installing UEK usually generates initramfs.&lt;/p&gt;

&lt;p&gt;List installed UEK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-q&lt;/span&gt; kernel-uek-core
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; /boot/vmlinuz-&lt;span class="k"&gt;*&lt;/span&gt;uek /boot/initramfs-&lt;span class="k"&gt;*&lt;/span&gt;uek.img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check if bcache module exists in the UEK kernel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;UEK_VER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;rpm &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{VERSION}-%{RELEASE}.%{ARCH}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek-core | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-V&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$UEK_VER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
modinfo &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$UEK_VER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; bcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;modinfo&lt;/code&gt; returns information, the UEK has the bcache module.&lt;/p&gt;

&lt;p&gt;To explicitly include it in initramfs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dracut &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--add-drivers&lt;/span&gt; bcache &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"/boot/initramfs-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UEK_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.img"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$UEK_VER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, if the root filesystem is not on bcache, forcing inclusion in initramfs at boot is usually unnecessary. You can &lt;code&gt;modprobe bcache&lt;/code&gt; after boot.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Verify Registration in GRUB
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL |
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^(index|kernel|title)='&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Identify the UEK entry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL |
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-B2&lt;/span&gt; &lt;span class="nt"&gt;-A3&lt;/span&gt; &lt;span class="s1"&gt;'el9uek'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's safer not to set UEK as default initially; instead, select UEK from the GRUB menu once for booting.&lt;/p&gt;

&lt;p&gt;To make the GRUB menu easier to display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grub2-editenv - &lt;span class="nb"&gt;unset &lt;/span&gt;menu_auto_hide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Test Boot with UEK
&lt;/h2&gt;

&lt;p&gt;After rebooting, select the entry containing &lt;code&gt;el9uek&lt;/code&gt; from GRUB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After booting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5.15.0-...el9uek.x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check bcache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;modprobe bcache
lsmod | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'^bcache'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make-bcache &lt;span class="nt"&gt;--version&lt;/span&gt;
bcache-super-show &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also check kernel logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; warning
&lt;span class="nb"&gt;sudo &lt;/span&gt;dmesg &lt;span class="nt"&gt;-T&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'bcache|error|failed|firmware'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify network, storage, console, and SELinux.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr
findmnt
getenforce
systemctl &lt;span class="nt"&gt;--failed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. Make UEK Default if No Issues
&lt;/h2&gt;

&lt;p&gt;To make the currently booting UEK the default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--set-default&lt;/span&gt; &lt;span class="s2"&gt;"/boot/vmlinuz-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--default-kernel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, specify the latest installed UEK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;UEK_KERNEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; /boot/vmlinuz-&lt;span class="k"&gt;*&lt;/span&gt;el9uek&lt;span class="k"&gt;*&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-V&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--set-default&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$UEK_KERNEL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--default-kernel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the Rocky standard kernel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-q&lt;/span&gt; kernel-core
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; /boot/vmlinuz-&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  11. Update Method
&lt;/h2&gt;

&lt;p&gt;Oracle repositories remain disabled, so normal updates target only Rocky.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only enable Oracle repositories explicitly when updating UEK and &lt;code&gt;bcache-tools&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'kernel-uek*'&lt;/span&gt; bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be more cautious, pre-check each time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade &lt;span class="nt"&gt;--assumeno&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'kernel-uek*'&lt;/span&gt; bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Avoid &lt;code&gt;dnf upgrade --enablerepo=oracle-...&lt;/code&gt; Alone
&lt;/h3&gt;

&lt;p&gt;It's safer not to run unspecified updates like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Not recommended&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Currently restricted by &lt;code&gt;includepkgs&lt;/code&gt;, but explicitly specifying update targets prevents accidents.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Secure Boot Considerations
&lt;/h2&gt;

&lt;p&gt;When Secure Boot is enabled, the main issue is not RPM signatures but whether &lt;strong&gt;Rocky's shim/firmware trusts Oracle's kernel signature&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mokutil &lt;span class="nt"&gt;--sb-state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;SecureBoot enabled&lt;/code&gt;, UEK may fail to boot with errors like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verification failed
Security Violation
Bad shim signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this mixed configuration, it's practical to test with one of the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Disable Secure Boot during verification&lt;/li&gt;
&lt;li&gt;Properly register the Oracle kernel signing certificate in MOK&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The latter requires certificate acquisition, verification, and MOK enrollment, which is not resolved by simply registering the Oracle RPM GPG key. RPM package signing keys and UEFI Secure Boot kernel signing certificates are different.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Minimum Precautions Before Using bcache
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;make-bcache&lt;/code&gt; destroys existing data on target devices. Verify device names thoroughly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsblk &lt;span class="nt"&gt;-o&lt;/span&gt; NAME,SIZE,MODEL,SERIAL,TYPE,FSTYPE,MOUNTPOINTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# SSD cache side&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;make-bcache &lt;span class="nt"&gt;--cache&lt;/span&gt; /dev/nvme0n1p1

&lt;span class="c"&gt;# HDD backend side&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;make-bcache &lt;span class="nt"&gt;--bdev&lt;/span&gt; /dev/sdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes the bcache superblock to the specified devices. Before substituting actual device names, ensure backups and console access.&lt;/p&gt;

&lt;p&gt;According to Linux kernel documentation, bcache supports writethrough and writeback, with writeback disabled by default. (&lt;a href="https://docs.kernel.org/admin-guide/bcache.html" rel="noopener noreferrer"&gt;Linux Kernel Documentation&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For initial testing, using writethrough is more appropriate to minimize write loss risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rollback
&lt;/h2&gt;

&lt;p&gt;If UEK fails to boot, select the Rocky standard kernel from GRUB.&lt;/p&gt;

&lt;p&gt;Return the Rocky kernel to default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ROCKY_KERNEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; /boot/vmlinuz-&lt;span class="k"&gt;*&lt;/span&gt;el9_&lt;span class="k"&gt;*&lt;/span&gt; 2&amp;gt;/dev/null |
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; el9uek |
  &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-V&lt;/span&gt; |
  &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--set-default&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROCKY_KERNEL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--default-kernel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove UEK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf remove &lt;span class="s1"&gt;'kernel-uek*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;bcache-tools&lt;/code&gt; is also unnecessary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf remove bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disable or remove the repo file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/yum.repos.d/oracle-uek-minimal.repo &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/yum.repos.d/oracle-uek-minimal.repo.disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recommended Final Configuration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rocky BaseOS/AppStream: always enabled&lt;/li&gt;
&lt;li&gt;Oracle UEK repo: &lt;code&gt;enabled=0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Oracle BaseOS repo: &lt;code&gt;enabled=0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Oracle side &lt;code&gt;includepkgs&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kernel-uek&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kernel-uek-core&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Modules packages required for the UEK series&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bcache-tools&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rocky standard kernel: always keep at least one generation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UEK updates: manually execute by specifying package names&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secure Boot: pre-verification required&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DKMS/kmod products: individually verify UEK ABI compatibility&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method effectively limits Oracle-origin packages to the kernel set and &lt;code&gt;bcache-tools&lt;/code&gt;, preventing Oracle-ization of the Rocky userland.&lt;/p&gt;

&lt;h1&gt;
  
  
  Rocky Linux 9へUEKを導入する手順（Oracleリポジトリ最小利用）
&lt;/h1&gt;

&lt;h2&gt;
  
  
  方針
&lt;/h2&gt;

&lt;p&gt;Rocky Linux 9のBaseOS/AppStreamはそのまま維持し、Oracle Linux側からは次だけ取得します。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UEK本体: &lt;code&gt;kernel-uek&lt;/code&gt;と、その依存サブパッケージ&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bcache-tools&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Oracle Linuxの通常ユーザーランド、&lt;code&gt;oraclelinux-release-el9&lt;/code&gt;、Oracle版&lt;code&gt;glibc&lt;/code&gt;などは導入しない&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oracle Linux yum serverはRHEL互換ディストリビューションからの利用方法を公式に案内していますが、Rocky Linux上でUEKを動かす構成自体はOracle/Rocky双方の正式サポート対象とは考えないでください。(&lt;a href="https://yum.oracle.com/getting-started.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;以下は &lt;strong&gt;x86_64&lt;/strong&gt; 前提です。&lt;/p&gt;

&lt;h2&gt;
  
  
  UEK R7かR8か
&lt;/h2&gt;

&lt;p&gt;2026年8月時点では、Oracle Linux 9向けに以下があります。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;系列&lt;/th&gt;
&lt;th&gt;カーネル系列&lt;/th&gt;
&lt;th&gt;選択基準&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UEK R7&lt;/td&gt;
&lt;td&gt;5.15&lt;/td&gt;
&lt;td&gt;Rocky 9との混成構成では比較的保守的&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UEK R8&lt;/td&gt;
&lt;td&gt;6.12&lt;/td&gt;
&lt;td&gt;新しいハードウェア・機能を優先&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Oracleの現行UEK R8リポジトリには、6.12系の&lt;code&gt;kernel-uek&lt;/code&gt;、&lt;code&gt;kernel-uek-core&lt;/code&gt;、各種modulesパッケージが収録されています。(&lt;a href="https://yum.oracle.com/repo/OracleLinux/OL9/UEKR8/x86_64/index.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;br&gt;
UEK R7は5.15系です。(&lt;a href="https://yum.oracle.com/repo/OracleLinux/OL9/UEKR7/x86_64/index.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;ここでは、混成リスクを少し抑えるため &lt;strong&gt;UEK R7&lt;/strong&gt; を例にします。R8へ変更する場合はURL中の&lt;code&gt;UEKR7&lt;/code&gt;を&lt;code&gt;UEKR8&lt;/code&gt;へ置き換えます。&lt;/p&gt;


&lt;h2&gt;
  
  
  1. 事前確認
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/rocky-release
&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;
findmnt /boot
findmnt /boot/efi 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;mokutil &lt;span class="nt"&gt;--sb-state&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;既存Rockyカーネルは削除しません。UEKが起動できない場合の復旧用です。&lt;/p&gt;

&lt;p&gt;作業前に、Rocky側を通常の状態へ更新します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade &lt;span class="nt"&gt;--refresh&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再起動後:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Oracle Linux 9署名鍵を登録
&lt;/h2&gt;

&lt;p&gt;Oracle公式のOL9用鍵を配置します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://yum.oracle.com/RPM-GPG-KEY-oracle-ol9 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;フィンガープリントを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpg &lt;span class="nt"&gt;--show-keys&lt;/span&gt; &lt;span class="nt"&gt;--with-fingerprint&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;少なくとも、Oracle公式掲載の次のフィンガープリントと一致することを確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3E6D 826D 3FBA B389 C2F3 8E34 BC4D 06A0 8D8B 756F
9822 3175 9C74 6706 5D0C E9B2 A7DD 0708 8B4E FBE6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oracleが公開しているOL9鍵の取得先とフィンガープリントです。(&lt;a href="https://yum.oracle.com/faq.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;RPMデータベースにも登録します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;rpm &lt;span class="nt"&gt;--import&lt;/span&gt; /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. 最小限に制限したOracleリポジトリを作成
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;oraclelinux-release-el9&lt;/code&gt;はインストールせず、自前のrepoファイルを2エントリだけ作ります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/yum.repos.d/oracle-uek-minimal.repo &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[oracle-uek-r7-minimal]
name=Oracle Linux 9 UEK R7 - restricted
baseurl=https://yum.oracle.com/repo/OracleLinux/OL9/UEKR7/&lt;/span&gt;&lt;span class="nv"&gt;$basearch&lt;/span&gt;&lt;span class="sh"&gt;/
enabled=0
gpgcheck=1
repo_gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
includepkgs=kernel-uek,kernel-uek-core,kernel-uek-modules,kernel-uek-modules-extra
metadata_expire=6h
skip_if_unavailable=0

[oracle-baseos-bcache-minimal]
name=Oracle Linux 9 BaseOS - bcache-tools only
baseurl=https://yum.oracle.com/repo/OracleLinux/OL9/baseos/latest/&lt;/span&gt;&lt;span class="nv"&gt;$basearch&lt;/span&gt;&lt;span class="sh"&gt;/
enabled=0
gpgcheck=1
repo_gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9
includepkgs=bcache-tools
metadata_expire=6h
skip_if_unavailable=0
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;重要なのは次の2点です。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;enabled=0&lt;/code&gt;: 通常の&lt;code&gt;dnf upgrade&lt;/code&gt;ではOracleリポジトリを使わない&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;includepkgs=&lt;/code&gt;: 明示したパッケージ以外をOracleから取得できないようにする&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oracle Linux 9のBaseOS URLは、OracleがRHEL互換環境向けに案内しているものと同じです。(&lt;a href="https://yum.oracle.com/getting-started.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  UEK R8を使う場合
&lt;/h3&gt;

&lt;p&gt;R7の代わりに、UEKエントリを次のようにします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[oracle-uek-r8-minimal]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Oracle Linux 9 UEK R8 - restricted&lt;/span&gt;
&lt;span class="py"&gt;baseurl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://yum.oracle.com/repo/OracleLinux/OL9/UEKR8/$basearch/&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;gpgcheck&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;repo_gpgcheck&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;gpgkey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle-ol9&lt;/span&gt;
&lt;span class="py"&gt;includepkgs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;kernel-uek,kernel-uek-core,kernel-uek-modules-core,kernel-uek-modules,kernel-uek-modules-extra&lt;/span&gt;
&lt;span class="py"&gt;metadata_expire&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;6h&lt;/span&gt;
&lt;span class="py"&gt;skip_if_unavailable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;R8ではパッケージ分割がR7と異なり、&lt;code&gt;kernel-uek-modules-core&lt;/code&gt;が存在します。(&lt;a href="https://yum.oracle.com/repo/OracleLinux/OL9/UEKR8/x86_64/index.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Oracleから見えるパッケージを確認
&lt;/h2&gt;

&lt;p&gt;まずインストールせず、候補だけ確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf clean metadata

&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'oracle-*'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  repoquery &lt;span class="nt"&gt;--available&lt;/span&gt; &lt;span class="s1"&gt;'kernel-uek*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bcache-tools&lt;/code&gt;も確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'oracle-*'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  repoquery &lt;span class="nt"&gt;--available&lt;/span&gt; &lt;span class="nt"&gt;--info&lt;/span&gt; bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oracle Linux 9では&lt;code&gt;bcache-tools&lt;/code&gt;がOracleによるBaseOS追加パッケージとして提供されています。(&lt;a href="https://docs.oracle.com/en/operating-systems/oracle-linux/9/relnotes9.1/ol-PackageChangesfromtheUpstreamRelease.html" rel="noopener noreferrer"&gt;Oracle Docs&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  取得元を確認
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf repoquery &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--available&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{name}-%{evr}.%{arch} &amp;lt;- %{repoid}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. トランザクションを事前確認
&lt;/h2&gt;

&lt;p&gt;最初は&lt;code&gt;--assumeno&lt;/code&gt;を付けます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--assumeno&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認すべき点:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Oracleから入るものが&lt;code&gt;kernel-uek*&lt;/code&gt;と&lt;code&gt;bcache-tools&lt;/code&gt;だけ&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;glibc&lt;/code&gt;、&lt;code&gt;systemd&lt;/code&gt;、&lt;code&gt;dracut&lt;/code&gt;、&lt;code&gt;grub2&lt;/code&gt;などがOracle版へ置換されない&lt;/li&gt;
&lt;li&gt;RockyのBaseOS/AppStreamパッケージが削除されない&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--allowerasing&lt;/code&gt;を要求されない&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;想定外のOracleパッケージが表示された場合は中止します。&lt;/p&gt;

&lt;p&gt;より厳密に確認するには:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--assumeno&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. UEKとbcache-toolsをインストール
&lt;/h2&gt;

&lt;p&gt;事前確認に問題がなければ実行します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kernel-uek&lt;/code&gt;メタパッケージが、対応する&lt;code&gt;kernel-uek-core&lt;/code&gt;とmodulesを依存関係として導入します。UEK R7リポジトリにはこれらが同一バージョンで収録されています。(&lt;a href="https://yum.oracle.com/repo/OracleLinux/OL9/UEKR7/x86_64/index.html" rel="noopener noreferrer"&gt;Oracle Linux Yum Server&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;確認:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qa&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^(kernel-uek|bcache-tools)'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ベンダーも確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{NAME} %{VERSION}-%{RELEASE} | %{VENDOR}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oracle由来パッケージ全体の確認:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-qa&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{NAME} %{VERSION}-%{RELEASE} | %{VENDOR}\n'&lt;/span&gt; |
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; oracle |
&lt;span class="nb"&gt;sort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ここで、意図しないOracle版ユーザーランドがないことを確認します。&lt;/p&gt;




&lt;h2&gt;
  
  
  7. initramfsとbcacheモジュールを確認
&lt;/h2&gt;

&lt;p&gt;UEKのインストールで通常はinitramfsが生成されます。&lt;/p&gt;

&lt;p&gt;インストール済みUEK一覧:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-q&lt;/span&gt; kernel-uek-core
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; /boot/vmlinuz-&lt;span class="k"&gt;*&lt;/span&gt;uek /boot/initramfs-&lt;span class="k"&gt;*&lt;/span&gt;uek.img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UEKカーネル内にbcacheモジュールがあるか確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;UEK_VER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;rpm &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;--qf&lt;/span&gt; &lt;span class="s1"&gt;'%{VERSION}-%{RELEASE}.%{ARCH}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  kernel-uek-core | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-V&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$UEK_VER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
modinfo &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$UEK_VER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; bcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;modinfo&lt;/code&gt;が情報を返せば、そのUEKにbcacheモジュールがあります。&lt;/p&gt;

&lt;p&gt;initramfsへ明示的に入れる場合:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dracut &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--add-drivers&lt;/span&gt; bcache &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"/boot/initramfs-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UEK_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.img"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$UEK_VER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ただし、ルートファイルシステムをbcache上に置かないのであれば、通常は起動時initramfsへの強制追加は不要です。起動後に&lt;code&gt;modprobe bcache&lt;/code&gt;できます。&lt;/p&gt;




&lt;h2&gt;
  
  
  8. GRUBへ登録されているか確認
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL |
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^(index|kernel|title)='&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UEKエントリを特定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL |
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-B2&lt;/span&gt; &lt;span class="nt"&gt;-A3&lt;/span&gt; &lt;span class="s1"&gt;'el9uek'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;最初からデフォルトにはせず、まず一度だけGRUBメニューからUEKを選択して起動するのが安全です。&lt;/p&gt;

&lt;p&gt;GRUBメニューを表示しやすくする場合:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grub2-editenv - &lt;span class="nb"&gt;unset &lt;/span&gt;menu_auto_hide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. UEKでテスト起動
&lt;/h2&gt;

&lt;p&gt;再起動後、GRUBから&lt;code&gt;el9uek&lt;/code&gt;を含むエントリを選択します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;起動後:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;想定例:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5.15.0-...el9uek.x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;bcacheを確認:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;modprobe bcache
lsmod | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'^bcache'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ツール確認:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make-bcache &lt;span class="nt"&gt;--version&lt;/span&gt;
bcache-super-show &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;カーネルログも確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; warning
&lt;span class="nb"&gt;sudo &lt;/span&gt;dmesg &lt;span class="nt"&gt;-T&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'bcache|error|failed|firmware'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ネットワーク、ストレージ、コンソール、SELinuxも確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr
findmnt
getenforce
systemctl &lt;span class="nt"&gt;--failed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. 問題なければUEKをデフォルトにする
&lt;/h2&gt;

&lt;p&gt;現在起動中のUEKをデフォルトにするなら:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--set-default&lt;/span&gt; &lt;span class="s2"&gt;"/boot/vmlinuz-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--default-kernel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;または、インストール済み最新UEKを指定:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;UEK_KERNEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; /boot/vmlinuz-&lt;span class="k"&gt;*&lt;/span&gt;el9uek&lt;span class="k"&gt;*&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-V&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--set-default&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$UEK_KERNEL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--default-kernel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rocky標準カーネルは残してください。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rpm &lt;span class="nt"&gt;-q&lt;/span&gt; kernel-core
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; /boot/vmlinuz-&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  11. 更新方法
&lt;/h2&gt;

&lt;p&gt;Oracleリポジトリは無効のままなので、通常の更新はRockyだけが対象です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UEKと&lt;code&gt;bcache-tools&lt;/code&gt;を更新するときだけ明示的に有効化します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'kernel-uek*'&lt;/span&gt; bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;より慎重にするなら、毎回事前確認します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade &lt;span class="nt"&gt;--assumeno&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-baseos-bcache-minimal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'kernel-uek*'&lt;/span&gt; bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;dnf upgrade --enablerepo=oracle-...&lt;/code&gt;だけは避ける
&lt;/h3&gt;

&lt;p&gt;次のような無指定更新は実行しない方が安全です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 非推奨&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade &lt;span class="nt"&gt;--enablerepo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oracle-uek-r7-minimal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;現状は&lt;code&gt;includepkgs&lt;/code&gt;で制限されていますが、更新対象を明示する方が事故を防げます。&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Secure Bootの注意
&lt;/h2&gt;

&lt;p&gt;Secure Bootが有効な場合、最大の問題はRPM署名ではなく、&lt;strong&gt;Rockyのshim/ファームウェアがOracleのカーネル署名を信頼するか&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;確認:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mokutil &lt;span class="nt"&gt;--sb-state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SecureBoot enabled&lt;/code&gt;の場合、UEKが次のようなエラーで起動できない可能性があります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verification failed
Security Violation
Bad shim signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;この混成構成では、まず以下のどちらかで検証するのが現実的です。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;検証段階ではSecure Bootを無効化する&lt;/li&gt;
&lt;li&gt;Oracleカーネルの署名証明書をMOKへ適切に登録する&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;後者は証明書の取得・検証・MOK登録が必要で、単純にOracle RPM GPG鍵を登録するだけでは解決しません。RPMパッケージ署名鍵とUEFI Secure Boot用カーネル署名証明書は別物です。&lt;/p&gt;




&lt;h2&gt;
  
  
  13. bcacheを使い始める前の最低限の注意
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;make-bcache&lt;/code&gt;は対象デバイスの既存データを破壊します。デバイス名を十分に確認してください。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsblk &lt;span class="nt"&gt;-o&lt;/span&gt; NAME,SIZE,MODEL,SERIAL,TYPE,FSTYPE,MOUNTPOINTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;例として:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# SSDキャッシュ側&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;make-bcache &lt;span class="nt"&gt;--cache&lt;/span&gt; /dev/nvme0n1p1

&lt;span class="c"&gt;# HDDバックエンド側&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;make-bcache &lt;span class="nt"&gt;--bdev&lt;/span&gt; /dev/sdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;この操作は指定デバイスへbcacheスーパーブロックを書き込みます。実デバイス名へ読み替える前に、バックアップとコンソールアクセスを確保してください。&lt;/p&gt;

&lt;p&gt;Linuxカーネル文書によると、bcacheはwritethroughとwritebackをサポートし、writebackは既定で無効です。(&lt;a href="https://docs.kernel.org/admin-guide/bcache.html" rel="noopener noreferrer"&gt;Linuxカーネルドキュメント&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;初期検証では、書き込み損失リスクを抑えるためwritethroughのまま使う方が妥当です。&lt;/p&gt;




&lt;h2&gt;
  
  
  ロールバック
&lt;/h2&gt;

&lt;p&gt;UEKで起動できない場合は、GRUBからRocky標準カーネルを選択します。&lt;/p&gt;

&lt;p&gt;Rockyカーネルをデフォルトへ戻す:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ROCKY_KERNEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; /boot/vmlinuz-&lt;span class="k"&gt;*&lt;/span&gt;el9_&lt;span class="k"&gt;*&lt;/span&gt; 2&amp;gt;/dev/null |
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; el9uek |
  &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-V&lt;/span&gt; |
  &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--set-default&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROCKY_KERNEL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;grubby &lt;span class="nt"&gt;--default-kernel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UEKを削除:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf remove &lt;span class="s1"&gt;'kernel-uek*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bcache-tools&lt;/code&gt;も不要なら:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf remove bcache-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;repoファイルを無効化または削除:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/yum.repos.d/oracle-uek-minimal.repo &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/yum.repos.d/oracle-uek-minimal.repo.disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  推奨する最終構成
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rocky BaseOS/AppStream: 常時有効&lt;/li&gt;
&lt;li&gt;Oracle UEK repo: &lt;code&gt;enabled=0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Oracle BaseOS repo: &lt;code&gt;enabled=0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Oracle側の&lt;code&gt;includepkgs&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kernel-uek&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kernel-uek-core&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;UEK系列に必要なmodulesパッケージ&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bcache-tools&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rocky標準カーネル: 最低1世代を常に保持&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UEK更新: パッケージ名を指定して手動実行&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secure Boot: 事前検証必須&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DKMS/kmod製品: UEK ABI対応を個別確認&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;この方法なら、Oracle由来パッケージをカーネル一式と&lt;code&gt;bcache-tools&lt;/code&gt;へ実質的に限定し、RockyユーザーランドのOracle化を防げます。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Exposing Supermicro X11DPI (ATEN IPMI) with Tailscale Serve: Use `socat` or Nginx `stream` Instead of an HTTP Reverse Proxy</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Fri, 31 Jul 2026 11:01:26 +0000</pubDate>
      <link>https://dev.to/vast-cow/exposing-supermicro-x11dpi-aten-ipmi-with-tailscale-serve-use-socat-or-nginx-stream-instead-4kg4</link>
      <guid>https://dev.to/vast-cow/exposing-supermicro-x11dpi-aten-ipmi-with-tailscale-serve-use-socat-or-nginx-stream-instead-4kg4</guid>
      <description>&lt;p&gt;While trying to expose the IPMI interface (ATEN-based) on a Supermicro X11 series motherboard using Tailscale Services (&lt;code&gt;tailscale serve --service&lt;/code&gt;), I ran into an unexpected pitfall.&lt;/p&gt;

&lt;p&gt;The conclusion is straightforward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When exposing ATEN IPMI through Tailscale Serve, a TCP-level proxy (such as &lt;code&gt;socat&lt;/code&gt; or Nginx &lt;code&gt;stream&lt;/code&gt;) should be your first choice instead of an HTTP reverse proxy.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I Wanted to Do
&lt;/h2&gt;

&lt;p&gt;I wanted to expose the IPMI interface on my LAN under a service name like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://x11dpi-ipmi.&amp;lt;tailnet&amp;gt;.ts.net/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Tailscale Services, this can be configured as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale serve &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;svc:x11dpi-ipmi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--https&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443 &lt;span class="se"&gt;\&lt;/span&gt;
  https+insecure://x.x.x.x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the HTML5 KVM console did not work correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First Suspect: WebSockets
&lt;/h2&gt;

&lt;p&gt;The HTML5 KVM console uses WebSockets.&lt;/p&gt;

&lt;p&gt;My initial assumptions were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maybe Tailscale Serve doesn't fully support WebSockets.&lt;/li&gt;
&lt;li&gt;Maybe the &lt;code&gt;Upgrade&lt;/code&gt; header isn't being forwarded correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, my own &lt;code&gt;aiohttp&lt;/code&gt; WebSocket server worked perfectly through the same setup.&lt;/p&gt;

&lt;p&gt;That meant there was nothing inherently wrong with the combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tailscale&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;li&gt;The browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Actual Cause
&lt;/h2&gt;

&lt;p&gt;After inserting Nginx as an HTTP reverse proxy for debugging, I found this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream sent invalid header: "\x20..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;resulting in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;502 Bad Gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request flow looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
    ↓
Tailscale
    ↓
Nginx HTTP Proxy
    ↓
ATEN IPMI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This indicates that &lt;strong&gt;Nginx rejects the HTTP response headers returned by the ATEN IPMI firmware as invalid.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Simple GET requests work, but CGI requests such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /cgi/ipmi.cgi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;fail.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;op=UID_SUPPORT.XML
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returns &lt;strong&gt;502 Bad Gateway&lt;/strong&gt; when sent via POST.&lt;/p&gt;

&lt;p&gt;Meanwhile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /cgi/ipmi.cgi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;works without issue.&lt;/p&gt;

&lt;p&gt;In other words, this is an &lt;strong&gt;HTTP protocol compatibility issue&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is Not a WebSocket Problem
&lt;/h2&gt;

&lt;p&gt;Since only the HTML5 KVM console initially appeared to fail, WebSockets seemed like the obvious culprit.&lt;/p&gt;

&lt;p&gt;In reality, however:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The HTTP parser fails while processing the CGI POST response.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The browser never reaches the stage where WebSocket communication becomes relevant because HTTP communication with the IPMI interface has already failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 1: &lt;code&gt;socat&lt;/code&gt; (Recommended)
&lt;/h2&gt;

&lt;p&gt;Instead of interpreting HTTP at all, simply forward TCP traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tailscale
    ↓ TLS termination
TCP
    ↓
socat
    ↓ TLS
IPMI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;socat &lt;span class="se"&gt;\&lt;/span&gt;
  TCP4-LISTEN:8082,bind&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1,reuseaddr,fork &lt;span class="se"&gt;\&lt;/span&gt;
  OPENSSL:x.x.x.x:443,verify&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure Tailscale Serve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale serve &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;svc:x11dpi-ipmi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tls-terminated-tcp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443 &lt;span class="se"&gt;\&lt;/span&gt;
  tcp://127.0.0.1:8082
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, everything worked correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CGI&lt;/li&gt;
&lt;li&gt;Cookies&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;li&gt;HTML5 KVM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because no HTTP headers are parsed, ATEN's non-standard HTTP implementation is passed through unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 2: Nginx &lt;code&gt;stream&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Nginx can achieve the same result by using the &lt;code&gt;stream&lt;/code&gt; module instead of the HTTP module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="nf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8082&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_ssl&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_ssl_verify&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="nf"&gt;x.x.x.x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;stream&lt;/code&gt; operates as a TCP proxy, it never parses HTTP headers.&lt;/p&gt;

&lt;p&gt;As a result, it avoids compatibility issues with the ATEN IPMI firmware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Don't Recommend an HTTP Reverse Proxy
&lt;/h2&gt;

&lt;p&gt;The conventional approach would be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;https://x.x.x.x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, with ATEN IPMI this can result in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream sent invalid header
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happens &lt;strong&gt;before&lt;/strong&gt; considerations such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Host&lt;/code&gt; headers&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Origin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The HTTP response itself is rejected by Nginx's parser.&lt;/p&gt;

&lt;p&gt;Adjusting buffer sizes or disabling buffering (for example, &lt;code&gt;proxy_buffering off&lt;/code&gt;) does not resolve the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;socat&lt;/code&gt; Works
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;socat&lt;/code&gt; does not understand HTTP.&lt;/p&gt;

&lt;p&gt;It simply forwards TCP streams.&lt;/p&gt;

&lt;p&gt;Therefore, even if the ATEN IPMI firmware returns unconventional HTTP responses, &lt;code&gt;socat&lt;/code&gt; passes them through unchanged.&lt;/p&gt;

&lt;p&gt;Modern browsers are apparently tolerant enough to process those responses successfully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When exposing an ATEN-based Supermicro X11 IPMI interface through Tailscale Services, my recommended order is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;socat&lt;/code&gt; + &lt;code&gt;tailscale serve --tls-terminated-tcp&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx &lt;code&gt;stream&lt;/code&gt; + &lt;code&gt;tailscale serve --tls-terminated-tcp&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx HTTP reverse proxy (not recommended)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For most web applications, an HTTP reverse proxy is the default choice.&lt;/p&gt;

&lt;p&gt;ATEN IPMI is an exception.&lt;/p&gt;

&lt;p&gt;A TCP-level proxy that forwards traffic without inspecting HTTP is significantly more reliable than an HTTP-aware reverse proxy.&lt;/p&gt;

&lt;p&gt;If you encounter errors such as &lt;code&gt;upstream sent invalid header&lt;/code&gt; or &lt;code&gt;502 Bad Gateway&lt;/code&gt;, don't assume the problem is with WebSockets or Tailscale. First, check whether you're routing the traffic through an HTTP reverse proxy.&lt;/p&gt;

&lt;h1&gt;
  
  
  Supermicro X11DPI (ATEN IPMI) を Tailscale Serve で公開するなら、Nginx HTTP Proxy ではなく socat / Nginx stream を使う
&lt;/h1&gt;

&lt;p&gt;Supermicro X11世代のIPMI（ATENベース）を Tailscale Services (&lt;code&gt;tailscale serve --service&lt;/code&gt;) で公開しようとしたところ、意外な落とし穴にはまりました。&lt;/p&gt;

&lt;p&gt;結論から言うと、&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ATEN IPMIを Tailscale Serve 経由で公開する場合は、HTTPリバースプロキシではなく TCPレベルで中継する構成（socat または Nginx stream）が第一選択です。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  やりたかったこと
&lt;/h2&gt;

&lt;p&gt;LAN内のIPMIを&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://x11dpi-ipmi.&amp;lt;tailnet&amp;gt;.ts.net/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;のようなサービス名で公開したい。&lt;/p&gt;

&lt;p&gt;Tailscale Services を使えば、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale serve &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;svc:x11dpi-ipmi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--https&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443 &lt;span class="se"&gt;\&lt;/span&gt;
  https+insecure://x.x.x.x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;のような構成が作れます。&lt;/p&gt;

&lt;p&gt;しかし、実際にはHTML5 KVMが正常に動作しませんでした。&lt;/p&gt;

&lt;h2&gt;
  
  
  最初に疑ったのはWebSocket
&lt;/h2&gt;

&lt;p&gt;HTML5 KVMはWebSocketを使用しています。&lt;/p&gt;

&lt;p&gt;そのため、&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tailscale ServeがWebSocketに対応していないのでは？&lt;/li&gt;
&lt;li&gt;Upgradeヘッダーが落ちているのでは？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;と考えました。&lt;/p&gt;

&lt;p&gt;しかし、自作の &lt;code&gt;aiohttp&lt;/code&gt; WebSocketサーバーでは正常に動作しました。&lt;/p&gt;

&lt;p&gt;つまり、&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tailscale&lt;/li&gt;
&lt;li&gt;WebSocket&lt;/li&gt;
&lt;li&gt;ブラウザ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;の組み合わせ自体には問題がありません。&lt;/p&gt;

&lt;h2&gt;
  
  
  実際の原因
&lt;/h2&gt;

&lt;p&gt;NginxをHTTPリバースプロキシとして挟いて調査すると、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream sent invalid header: "\x20..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;というエラーが出ました。&lt;/p&gt;

&lt;p&gt;つまり、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;502 Bad Gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;になっています。&lt;/p&gt;

&lt;p&gt;これは&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ブラウザ
    ↓
Tailscale
    ↓
Nginx HTTP Proxy
    ↓
ATEN IPMI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という構成で、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ATEN IPMIが返すHTTPレスポンスヘッダーをNginxが不正と判断して拒否している&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ことを意味します。&lt;/p&gt;

&lt;p&gt;GETでは問題なくても、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /cgi/ipmi.cgi
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;のようなCGIでは失敗します。&lt;/p&gt;

&lt;p&gt;例えば、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;op=UID_SUPPORT.XML
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;をPOSTすると502になります。&lt;/p&gt;

&lt;p&gt;一方、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /cgi/ipmi.cgi
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;は正常に返ります。&lt;/p&gt;

&lt;p&gt;つまり、&lt;/p&gt;

&lt;p&gt;HTTPレベルの互換性問題です。&lt;/p&gt;

&lt;h2&gt;
  
  
  これはWebSocketの問題ではない
&lt;/h2&gt;

&lt;p&gt;最初はHTML5 KVMだけが失敗するのでWebSocketを疑いました。&lt;/p&gt;

&lt;p&gt;しかし実際には、&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CGIのPOSTレスポンスの時点でHTTPパーサが失敗していました。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;そのため、&lt;/p&gt;

&lt;p&gt;HTML5 KVM以前にIPMIとのHTTP通信自体が成立していません。&lt;/p&gt;

&lt;h2&gt;
  
  
  解決策1: socat（おすすめ）
&lt;/h2&gt;

&lt;p&gt;HTTPを一切解釈せず、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tailscale
    ↓ TLS終端
TCP
    ↓
socat
    ↓ TLS
IPMI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;という構成にします。&lt;/p&gt;

&lt;p&gt;例えば&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;socat &lt;span class="se"&gt;\&lt;/span&gt;
  TCP4-LISTEN:8082,bind&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1,reuseaddr,fork &lt;span class="se"&gt;\&lt;/span&gt;
  OPENSSL:x.x.x.x:443,verify&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;そして&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale serve &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;svc:x11dpi-ipmi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tls-terminated-tcp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443 &lt;span class="se"&gt;\&lt;/span&gt;
  tcp://127.0.0.1:8082
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;とします。&lt;/p&gt;

&lt;p&gt;この構成では、&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CGI&lt;/li&gt;
&lt;li&gt;Cookie&lt;/li&gt;
&lt;li&gt;WebSocket&lt;/li&gt;
&lt;li&gt;HTML5 KVM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;すべて正常に動作しました。&lt;/p&gt;

&lt;p&gt;HTTPヘッダーを解析しないため、ATEN独自の実装にも影響されません。&lt;/p&gt;

&lt;h2&gt;
  
  
  解決策2: Nginx stream
&lt;/h2&gt;

&lt;p&gt;NginxでもHTTPではなくstreamモジュールを使えば同様です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="nf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8082&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_ssl&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_ssl_verify&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="nf"&gt;x.x.x.x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;streamはTCPプロキシなので、&lt;/p&gt;

&lt;p&gt;HTTPヘッダーを解析しません。&lt;/p&gt;

&lt;p&gt;そのためATEN IPMIとの相性問題を回避できます。&lt;/p&gt;

&lt;h2&gt;
  
  
  Nginx HTTP Proxyはおすすめしない
&lt;/h2&gt;

&lt;p&gt;一般的には&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;https://x.x.x.x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;というHTTPリバースプロキシを書きたくなります。&lt;/p&gt;

&lt;p&gt;しかしATEN IPMIでは、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream sent invalid header
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;になるケースがあります。&lt;/p&gt;

&lt;p&gt;これは&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hostヘッダー&lt;/li&gt;
&lt;li&gt;Origin&lt;/li&gt;
&lt;li&gt;WebSocket&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以前に、&lt;/p&gt;

&lt;p&gt;HTTPレスポンス自体がNginxに拒否されるためです。&lt;/p&gt;

&lt;p&gt;バッファサイズや &lt;code&gt;proxy_buffering off&lt;/code&gt; などでは改善しません。&lt;/p&gt;

&lt;h2&gt;
  
  
  なぜsocatが動くのか
&lt;/h2&gt;

&lt;p&gt;socatはHTTPを理解していません。&lt;/p&gt;

&lt;p&gt;単なるTCP中継です。&lt;/p&gt;

&lt;p&gt;そのため、&lt;/p&gt;

&lt;p&gt;ATEN IPMIが多少変わったHTTPレスポンスを返しても、&lt;/p&gt;

&lt;p&gt;そのままブラウザへ転送します。&lt;/p&gt;

&lt;p&gt;結果として、&lt;/p&gt;

&lt;p&gt;ブラウザは問題なく処理できます。&lt;/p&gt;

&lt;h2&gt;
  
  
  結論
&lt;/h2&gt;

&lt;p&gt;ATENベースのSupermicro X11 IPMIをTailscale Servicesで公開する場合のおすすめ順位は次の通りです。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;socat + &lt;code&gt;tailscale serve --tls-terminated-tcp&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx stream + &lt;code&gt;tailscale serve --tls-terminated-tcp&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx HTTP Proxy（非推奨）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;一般的なWebアプリではHTTPリバースプロキシが第一選択ですが、ATEN IPMIでは事情が異なります。&lt;/p&gt;

&lt;p&gt;HTTPを解析するプロキシよりも、TCPレベルでそのまま中継する構成の方が安定して動作します。&lt;/p&gt;

&lt;p&gt;もし &lt;code&gt;upstream sent invalid header&lt;/code&gt; や &lt;code&gt;502 Bad Gateway&lt;/code&gt; に遭遇した場合は、WebSocketやTailscaleを疑う前に、HTTPプロキシを経由していないか確認してみてください。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why NVIDIA Open-Sourced Its Linux GPU Kernel Modules</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Fri, 31 Jul 2026 03:20:24 +0000</pubDate>
      <link>https://dev.to/vast-cow/why-nvidia-open-sourced-its-linux-gpu-kernel-modules-48dd</link>
      <guid>https://dev.to/vast-cow/why-nvidia-open-sourced-its-linux-gpu-kernel-modules-48dd</guid>
      <description>&lt;p&gt;The biggest reason NVIDIA began providing GPL-licensed kernel modules is that &lt;strong&gt;its driver architecture evolved to the point where Linux integration, distribution, and maintenance could be greatly simplified while keeping the GPU's critical intellectual property in firmware and user-space components&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To be precise, NVIDIA did &lt;strong&gt;not&lt;/strong&gt; open-source its entire driver stack. The components that became open are primarily the following Linux kernel modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nvidia.ko&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nvidia-drm.ko&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nvidia-uvm.ko&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nvidia-modeset.ko&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;User-space components such as CUDA, OpenGL, Vulkan, and the GSP firmware remain proprietary. (&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  1. To make integration with Linux distributions easier
&lt;/h2&gt;

&lt;p&gt;Previously, NVIDIA's proprietary kernel modules had to be built, signed, and distributed separately from the Linux kernel. A DKMS-based workflow, which rebuilds modules after every kernel update, commonly led to problems such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kernel modules failing to build after kernel updates&lt;/li&gt;
&lt;li&gt;Unsigned modules being blocked by Secure Boot&lt;/li&gt;
&lt;li&gt;Linux distributions having difficulty maintaining the driver as an official package&lt;/li&gt;
&lt;li&gt;Increased complexity when integrating with custom kernels or cloud environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By publishing the source code, distributions such as Ubuntu, Red Hat, and SUSE can integrate NVIDIA's kernel modules into their own packaging, signing, and update infrastructure much more easily. NVIDIA itself cites tighter OS integration and simpler signing and distribution as key motivations. (&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  2. To improve debugging and security review
&lt;/h2&gt;

&lt;p&gt;Kernel modules interact with deep parts of the operating system, including memory management, interrupts, inter-process synchronization, PCI Express, and display subsystems.&lt;/p&gt;

&lt;p&gt;With the source code available, Linux distribution developers and enterprise users can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trace where execution stops inside the kernel&lt;/li&gt;
&lt;li&gt;Analyze interactions between GPU events and workloads&lt;/li&gt;
&lt;li&gt;Fix incompatibilities with custom kernels&lt;/li&gt;
&lt;li&gt;Review security-related issues&lt;/li&gt;
&lt;li&gt;Submit patches to NVIDIA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NVIDIA states that community and partner review can improve both driver quality and security. (&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Because functionality moved into the GSP
&lt;/h2&gt;

&lt;p&gt;The key technical turning point is the &lt;strong&gt;GSP (GPU System Processor)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Previously, much of the GPU control logic ran inside the host CPU's kernel driver. On newer GPUs, however, the internal GSP firmware performs critical tasks such as hardware initialization and management.&lt;/p&gt;

&lt;p&gt;Conceptually, the architecture changed as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Previous:

Linux kernel module
        ↓ Direct low-level hardware control
GPU

Current:

Linux kernel module
        ↓ Commands / RPC
GSP firmware
        ↓
GPU hardware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows hardware-specific complexity and sensitive implementation details to remain inside signed firmware while exposing only the Linux-facing kernel interface. NVIDIA explicitly identifies the gradual adoption of the GSP driver architecture as the technical foundation that made the initial open-source kernel modules possible. (&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;In other words, NVIDIA did not simply change corporate policy—it &lt;strong&gt;redesigned the driver architecture in advance so that the kernel-facing portion could be released as open source&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. To simplify AI, cloud, and data center deployments
&lt;/h2&gt;

&lt;p&gt;Today, NVIDIA GPUs are used not only in desktop systems but also in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes clusters&lt;/li&gt;
&lt;li&gt;AI training servers&lt;/li&gt;
&lt;li&gt;Cloud virtual machines&lt;/li&gt;
&lt;li&gt;Grace Hopper systems&lt;/li&gt;
&lt;li&gt;Confidential Computing environments&lt;/li&gt;
&lt;li&gt;Unified memory architectures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these environments, it is essential to integrate GPU drivers into OS images and container platforms automatically and securely.&lt;/p&gt;

&lt;p&gt;The open kernel modules also introduced support for technologies such as HMM, Confidential Computing, and coherent memory on the Grace platform. On some newer platforms, including Grace Hopper and Blackwell, the open kernel modules are mandatory. (&lt;a href="https://developer.nvidia.com/blog/nvidia-transitions-fully-towards-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This provides NVIDIA with a clear business advantage by reducing the operational cost of deploying AI and cloud infrastructure on Linux.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Better collaboration with the Linux ecosystem, including Nouveau
&lt;/h2&gt;

&lt;p&gt;The release of the source code, together with the introduction of the GSP firmware, also makes it easier to improve &lt;strong&gt;Nouveau&lt;/strong&gt;, the open-source NVIDIA driver included in the Linux ecosystem, particularly for features such as power management and clock management.&lt;/p&gt;

&lt;p&gt;NVIDIA explains that Nouveau developers can use the published code as a reference and can also utilize the same GSP firmware. (&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;However, NVIDIA's open kernel modules have &lt;strong&gt;not&lt;/strong&gt; been fully merged into the upstream Linux kernel. NVIDIA explains that its driver codebase is shared across multiple operating systems and does not currently conform to Linux kernel upstream coding conventions, preventing direct inclusion. (&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is it dual-licensed under "MIT/GPL"?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MIT/GPL&lt;/code&gt; designation means the code is &lt;strong&gt;dual-licensed&lt;/strong&gt;, allowing it to be used under either &lt;strong&gt;GPLv2&lt;/strong&gt; or the &lt;strong&gt;MIT License&lt;/strong&gt;. (&lt;a href="https://docs.nvidia.com/datacenter/tesla/driver-installation-guide/kernel-modules.html" rel="noopener noreferrer"&gt;NVIDIA Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Broadly speaking, the purposes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPLv2:&lt;/strong&gt; Ensures compatibility with the Linux kernel's licensing model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MIT:&lt;/strong&gt; Allows more permissive reuse, porting, and redistribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because NVIDIA shares portions of its codebase across Linux, other operating systems, multiple GPU families, and Jetson platforms, allowing an MIT option in addition to GPL is a practical choice. However, NVIDIA has not officially stated that this is the sole motivation; this conclusion is inferred from the published code structure and licensing model. (&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  In summary
&lt;/h2&gt;

&lt;p&gt;Rather than suddenly adopting a company-wide policy of making everything open source, NVIDIA's actual approach is better summarized as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;By moving hardware control into the GSP firmware and open-sourcing only the Linux kernel interface, NVIDIA simplified distribution, signing, maintenance, and cloud deployment while keeping its core GPU intellectual property proprietary.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Regarding the previous question, NVIDIA's current official policy is that &lt;strong&gt;open GPU kernel modules are the default and recommended option for supported Turing-generation and newer GPUs&lt;/strong&gt;. This became the default starting with the R560 driver series. Maxwell, Pascal, and Volta GPUs still require the proprietary driver. (&lt;a href="https://developer.nvidia.com/blog/nvidia-transitions-fully-towards-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;NVIDIAがGPL系のモジュールを提供するようになった最大の理由は、&lt;strong&gt;Linuxとの統合・配布・保守を容易にしつつ、GPUの重要な知的財産はファームウェアやユーザー空間に残せる技術構成が整ったから&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;なお、正確には「NVIDIAドライバー全体をGPL化した」のではありません。オープンになったのは主に以下のLinuxカーネルモジュールです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nvidia.ko&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nvidia-drm.ko&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nvidia-uvm.ko&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nvidia-modeset.ko&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CUDA、OpenGL、Vulkanなどのユーザー空間コンポーネントとGSPファームウェアは、引き続きプロプライエタリです。(&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Linuxディストリビューションに組み込みやすくするため
&lt;/h2&gt;

&lt;p&gt;従来のプロプライエタリモジュールは、Linuxカーネルとは別にビルド・署名・配布する必要がありました。カーネル更新のたびにDKMSで再ビルドする構成では、以下が問題になりやすくなります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;カーネル更新後にモジュールがビルドできない&lt;/li&gt;
&lt;li&gt;Secure Bootで署名されていないモジュールをロードできない&lt;/li&gt;
&lt;li&gt;ディストリビューション側が正式パッケージとして管理しにくい&lt;/li&gt;
&lt;li&gt;カスタムカーネルやクラウド環境への組み込みが複雑&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ソースを公開することで、Ubuntu、Red Hat、SUSEなどがNVIDIAモジュールを自分たちのパッケージング、署名、更新機構に統合しやすくなりました。NVIDIA自身も「OSとのより緊密な統合」「署名と配布の容易化」を目的として挙げています。(&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 障害解析とセキュリティレビューを改善するため
&lt;/h2&gt;

&lt;p&gt;カーネルモジュールは、メモリ管理、割り込み、プロセス間同期、PCI Express、ディスプレイなど、OSの深い部分に関わります。&lt;/p&gt;

&lt;p&gt;ソースが見えることで、ディストリビューション開発者や企業ユーザーは、次のような調査が可能になります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;カーネル内のどこで停止しているか追跡する&lt;/li&gt;
&lt;li&gt;GPUイベントとワークロードの相互作用を解析する&lt;/li&gt;
&lt;li&gt;カスタムカーネルとの非互換性を修正する&lt;/li&gt;
&lt;li&gt;セキュリティ上の問題をレビューする&lt;/li&gt;
&lt;li&gt;パッチをNVIDIAに提案する&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NVIDIAは、コミュニティやパートナーからのレビューによってドライバーの品質とセキュリティを改善できると説明しています。(&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  3. GSPへの機能移動で、公開しやすくなったため
&lt;/h2&gt;

&lt;p&gt;技術的な転換点が &lt;strong&gt;GSP（GPU System Processor）&lt;/strong&gt; です。&lt;/p&gt;

&lt;p&gt;以前は、GPU制御の多くをホストCPU上のカーネルドライバーが実行していました。しかし新しいGPUでは、GPU内部のGSPファームウェアが、初期化やハードウェア管理などの重要な処理を担当する構成へ移行しました。&lt;/p&gt;

&lt;p&gt;概念的には次のような変化です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;従来:
Linuxカーネルモジュール
    ↓ ハードウェアを直接細かく制御
GPU

現在:
Linuxカーネルモジュール
    ↓ コマンド／RPC
GSPファームウェア
    ↓
GPUハードウェア
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これにより、ハードウェア固有の複雑な制御や機密性の高い部分を署名済みファームウェア側に置きながら、Linuxと接続するカーネル側を公開しやすくなりました。NVIDIAも、最初のオープンモジュールが実現した技術的背景としてGSPドライバーアーキテクチャの段階的導入を明示しています。(&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;つまり、単に方針を変えたのではなく、&lt;strong&gt;ソース公開可能なドライバー構造へ事前に設計変更していた&lt;/strong&gt;ということです。&lt;/p&gt;

&lt;h2&gt;
  
  
  4. AI・クラウド・データセンター運用を簡単にするため
&lt;/h2&gt;

&lt;p&gt;NVIDIAのGPUは現在、デスクトップ用途だけでなく、以下の環境で大量に使われます。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetesクラスタ&lt;/li&gt;
&lt;li&gt;AI学習サーバー&lt;/li&gt;
&lt;li&gt;クラウドVM&lt;/li&gt;
&lt;li&gt;Grace Hopperシステム&lt;/li&gt;
&lt;li&gt;Confidential Computing&lt;/li&gt;
&lt;li&gt;統合メモリアーキテクチャ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;このような環境では、GPUドライバーをOSイメージやコンテナ基盤に自動的かつ安全に組み込めることが重要です。&lt;/p&gt;

&lt;p&gt;オープンモジュールでは、HMM、Confidential Computing、Graceプラットフォームのコヒーレントメモリなど、新しい機能も実装されました。Grace HopperやBlackwellなど、一部の新しいプラットフォームではオープンカーネルモジュールが必須になっています。(&lt;a href="https://developer.nvidia.com/blog/nvidia-transitions-fully-towards-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;これは、Linux上のAI・クラウド展開における運用コストを下げるという、NVIDIAにとって明確な事業上の利点があります。&lt;/p&gt;

&lt;h2&gt;
  
  
  5. NouveauなどLinux側との協調
&lt;/h2&gt;

&lt;p&gt;ソース公開とGSPファームウェアの利用によって、Linuxカーネル内のオープンソースドライバーであるNouveauも、電力管理やクロック管理などの機能を改善しやすくなりました。&lt;/p&gt;

&lt;p&gt;NVIDIAは、公開コードをNouveau改善の参考にでき、Nouveauからも同じGSPファームウェアを利用できると説明しています。(&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;ただし、NVIDIAの公開モジュールそのものがLinuxカーネル本体に完全統合されたわけではありません。NVIDIAは、コードベースが複数OSで共有されており、Linuxカーネルの設計慣例に適合していないため、そのままではupstreamに入れられないと説明しています。(&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  なぜ「MIT/GPL」のデュアルライセンスなのか
&lt;/h2&gt;

&lt;p&gt;表示される &lt;code&gt;MIT/GPL&lt;/code&gt; は、&lt;strong&gt;GPLv2またはMITライセンスの条件で利用できるデュアルライセンス&lt;/strong&gt;です。(&lt;a href="https://docs.nvidia.com/datacenter/tesla/driver-installation-guide/kernel-modules.html" rel="noopener noreferrer"&gt;NVIDIA Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;大まかな役割は次のとおりです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPLv2&lt;/strong&gt;：Linuxカーネルのライセンス体系との親和性を確保する&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MIT&lt;/strong&gt;：より制約の少ない再利用、移植、再配布を可能にする&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NVIDIAのコードはLinuxだけでなく、複数のOS、GPU、Jetson向けに共有されているため、GPLだけに限定せずMITも選択可能にすることには合理性があります。ただし、この部分はNVIDIAが公式に単一の動機として説明したものではなく、公開されているコード構成とライセンス方式からの推論です。(&lt;a href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  要するに
&lt;/h2&gt;

&lt;p&gt;NVIDIAが急に「すべてをオープンソースにする企業方針」へ変わったというより、&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GSPファームウェアにハードウェア制御を移し、Linuxとの接点となるカーネル部分を公開することで、配布・署名・保守・クラウド展開を容易にした&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;というのが実態に近いです。&lt;/p&gt;

&lt;p&gt;なお、前の質問に関しては、現在のNVIDIA公式方針では、&lt;strong&gt;Turing以降の対応GPUではオープンカーネルモジュールがデフォルトかつ推奨&lt;/strong&gt;です。R560系列以降、この方針に移行しています。Maxwell、Pascal、Voltaではプロプライエタリ版が必要です。(&lt;a href="https://developer.nvidia.com/blog/nvidia-transitions-fully-towards-open-source-gpu-kernel-modules/" rel="noopener noreferrer"&gt;NVIDIA Developer&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>linux</category>
      <category>opensource</category>
      <category>software</category>
    </item>
    <item>
      <title>Limiting SATA Link Speed to 3 Gbps on Linux Using libata.force</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Thu, 30 Jul 2026 13:55:58 +0000</pubDate>
      <link>https://dev.to/vast-cow/limiting-sata-link-speed-to-3-gbps-on-linux-using-libataforce-50a0</link>
      <guid>https://dev.to/vast-cow/limiting-sata-link-speed-to-3-gbps-on-linux-using-libataforce-50a0</guid>
      <description>&lt;p&gt;You can use the Linux kernel boot parameter &lt;code&gt;libata.force&lt;/code&gt; to &lt;strong&gt;limit the link speed of a specific ATA port to SATA 3 Gbps (Gen2) instead of 6 Gbps (Gen3)&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Configuration
&lt;/h2&gt;

&lt;p&gt;If the target port is recognized as &lt;code&gt;ata2&lt;/code&gt; in Linux, add the following kernel parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libata.force=2:3.0G
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format is &lt;code&gt;PORT:SPEED&lt;/code&gt;, where &lt;code&gt;PORT&lt;/code&gt; is the numeric portion of &lt;code&gt;ata2&lt;/code&gt;. To configure multiple ports, separate them with commas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libata.force=2:3.0G,5:3.0G
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to the official kernel documentation, &lt;code&gt;libata.force=[ID:]VAL&lt;/code&gt; can be used to limit the SATA link speed on a per-port basis to either &lt;code&gt;1.5Gbps&lt;/code&gt; or &lt;code&gt;3.0Gbps&lt;/code&gt;. (&lt;a href="https://docs.kernel.org/admin-guide/kernel-parameters.html" rel="noopener noreferrer"&gt;Linux Kernel Documentation&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifying the Target Port
&lt;/h2&gt;

&lt;p&gt;First, check the boot log to determine which ATA port the disk is connected to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dmesg | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'ata[0-9]+: SATA link'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ata2: SATA link up 6.0 Gbps
ata5: SATA link up 6.0 Gbps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also verify the mapping to block devices with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/disk/by-path/ | &lt;span class="nb"&gt;grep &lt;/span&gt;ata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is to specify the &lt;strong&gt;&lt;code&gt;ataN&lt;/code&gt; number reported by Linux&lt;/strong&gt;, not &lt;code&gt;/dev/sda&lt;/code&gt; or the motherboard's physical "SATA Port 2" label.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Setting Persistent with GRUB
&lt;/h2&gt;

&lt;p&gt;Add the parameter to the existing kernel command line in &lt;code&gt;/etc/default/grub&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"quiet splash libata.force=2:3.0G"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Debian- and Ubuntu-based systems, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;update-grub
&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On distributions that generate the GRUB configuration differently, use either &lt;code&gt;grub-mkconfig&lt;/code&gt; or &lt;code&gt;grub2-mkconfig&lt;/code&gt;, depending on your environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the Configuration
&lt;/h2&gt;

&lt;p&gt;After rebooting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cmdline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify the negotiated link speed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dmesg | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'ata2: SATA link'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ata2: SATA link up 3.0 Gbps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also check via sysfs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/class/ata_link/link2/sata_spd
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/class/ata_link/link2/sata_spd_limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/sys/class/ata_link/.../sata_spd&lt;/code&gt; reports the current link speed, while &lt;code&gt;sata_spd_limit&lt;/code&gt; reports the maximum speed imposed by &lt;code&gt;libata&lt;/code&gt;. These files are read-only, so the limit is normally configured through the kernel boot parameter rather than changed at runtime with &lt;code&gt;echo&lt;/code&gt;. (&lt;a href="https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-ata?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Linux Kernel Archive&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This does &lt;strong&gt;not&lt;/strong&gt; throttle bandwidth; it limits the &lt;strong&gt;maximum SATA PHY link negotiation speed&lt;/strong&gt; to 3 Gbps.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ataN&lt;/code&gt; numbering may change if controllers are added, BIOS settings are modified, or the kernel or drivers change.&lt;/li&gt;
&lt;li&gt;If your BIOS/UEFI provides a per-port "SATA Speed" or "Gen2" option, that may be a clearer way to configure the physical port.&lt;/li&gt;
&lt;li&gt;This method may not apply to disks connected through USB-to-SATA adapters, NVMe, SAS, or hardware RAID controllers.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>PiKVM Setup and Basic Usage</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Thu, 30 Jul 2026 09:34:26 +0000</pubDate>
      <link>https://dev.to/vast-cow/pikvm-setup-and-basic-usage-3oof</link>
      <guid>https://dev.to/vast-cow/pikvm-setup-and-basic-usage-3oof</guid>
      <description>&lt;p&gt;PiKVM is a remote KVM solution that lets you view video output, control the keyboard and mouse, and boot from virtual media even when the target computer's operating system is not running.&lt;/p&gt;

&lt;p&gt;This guide walks through the entire process in the order you would actually perform it, from writing the OS image to an SD card, through initial configuration, virtual media setup, remote access with Tailscale, and finally operating the target machine.&lt;/p&gt;

&lt;p&gt;Keep in mind that the interface and some features vary depending on the PiKVM model and KVMD version. The virtual media sections in this article primarily assume PiKVM V2 or later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the Read-Only Filesystem
&lt;/h2&gt;

&lt;p&gt;PiKVM OS normally operates with its filesystem mounted as read-only to reduce SD card wear and protect against filesystem corruption caused by unexpected power loss. After making configuration changes, you should always return the filesystem to read-only mode.&lt;/p&gt;

&lt;p&gt;An important point is that PiKVM provides &lt;strong&gt;two different commands for enabling writes&lt;/strong&gt;, each serving a different purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison of Write Mode Commands
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Primary Purpose&lt;/th&gt;
&lt;th&gt;Enable Writes&lt;/th&gt;
&lt;th&gt;Return to Read-Only&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;rw&lt;/code&gt; / &lt;code&gt;ro&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;PiKVM OS root filesystem&lt;/td&gt;
&lt;td&gt;Changing passwords, installing packages, editing files under &lt;code&gt;/etc&lt;/code&gt;, changing the hostname&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ro&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;kvmd-helper-otgmsd-remount rw&lt;/code&gt; / &lt;code&gt;ro&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Virtual media area at &lt;code&gt;/var/lib/kvmd/msd&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Uploading, deleting, or modifying ISO and IMG files&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kvmd-helper-otgmsd-remount rw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kvmd-helper-otgmsd-remount ro&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key point is that &lt;strong&gt;running &lt;code&gt;rw&lt;/code&gt; does not necessarily make the virtual media area writable&lt;/strong&gt;. Likewise, running &lt;code&gt;kvmd-helper-otgmsd-remount rw&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; allow you to modify system configuration files such as those under &lt;code&gt;/etc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use the appropriate command depending on what you are modifying.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Write the Image to the SD Card
&lt;/h1&gt;

&lt;p&gt;First, write the appropriate PiKVM OS image for your PiKVM model to an SD card.&lt;/p&gt;

&lt;p&gt;After writing completes—but before booting PiKVM—open the first FAT32 partition on the SD card. This partition contains the &lt;code&gt;pikvm.txt&lt;/code&gt; file used for first-boot configuration.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Configure Wi-Fi Before the First Boot
&lt;/h1&gt;

&lt;p&gt;If wired Ethernet is unavailable, configuring Wi-Fi before the first boot makes the initial setup much easier.&lt;/p&gt;

&lt;p&gt;Add the following lines to &lt;code&gt;pikvm.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WIFI_ESSID='Your Wi-Fi SSID'
WIFI_PASSWD='Your Wi-Fi Password'
WIFI_REGDOM='JP'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;WIFI_REGDOM='JP'&lt;/code&gt; specifies Japan as the wireless regulatory domain.&lt;/p&gt;

&lt;p&gt;If the following line already exists in &lt;code&gt;pikvm.txt&lt;/code&gt; immediately after writing the image, leave it unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FIRST_BOOT=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;FIRST_BOOT=1&lt;/code&gt; instructs PiKVM to generate SSH host keys, certificates, and other first-boot resources.&lt;/p&gt;

&lt;p&gt;If your Wi-Fi password contains a backslash, it must be escaped by doubling it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WIFI_PASSWD='abc\\def'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the configuration has been applied, &lt;code&gt;pikvm.txt&lt;/code&gt; is automatically deleted.&lt;/p&gt;

&lt;p&gt;Also note that configurations based on the Raspberry Pi Zero 2 W cannot use 5 GHz Wi-Fi.&lt;/p&gt;

&lt;p&gt;Safely eject the SD card, insert it into the PiKVM, and power it on.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Find the PiKVM IP Address
&lt;/h1&gt;

&lt;p&gt;PiKVM normally obtains an IP address using DHCP.&lt;/p&gt;

&lt;p&gt;After booting, the assigned IP address is displayed on the monitor connected to PiKVM, making it the easiest way to find it.&lt;/p&gt;

&lt;p&gt;If no monitor is attached, check your router's management interface or DHCP lease table to identify the assigned address.&lt;/p&gt;

&lt;p&gt;Throughout this guide, the PiKVM IP address will be represented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  4. Connect to PiKVM via SSH
&lt;/h1&gt;

&lt;p&gt;By default, the Linux administrator account is configured as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Username&lt;/td&gt;
&lt;td&gt;&lt;code&gt;root&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password&lt;/td&gt;
&lt;td&gt;&lt;code&gt;root&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Connect using an SSH client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@192.168.1.100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first connection, SSH asks you to verify the host key. Confirm that you are connecting to the correct device before accepting it.&lt;/p&gt;

&lt;p&gt;PiKVM also has a separate &lt;code&gt;admin&lt;/code&gt; account for the Web UI. By default, both the username and password are &lt;code&gt;admin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Linux &lt;code&gt;root&lt;/code&gt; account and the Web UI &lt;code&gt;admin&lt;/code&gt; account are completely independent, so both passwords must be changed.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Change Both Default Passwords First
&lt;/h1&gt;

&lt;p&gt;Running PiKVM with its default passwords is insecure. Change them before configuring networking or installing additional features.&lt;/p&gt;

&lt;p&gt;First, remount the root filesystem as writable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the Linux &lt;code&gt;root&lt;/code&gt; password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;passwd root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, change the Web UI &lt;code&gt;admin&lt;/code&gt; password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kvmd-htpasswd &lt;span class="nb"&gt;set &lt;/span&gt;admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After both passwords have been updated, return the filesystem to read-only mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete sequence is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw
passwd root
kvmd-htpasswd &lt;span class="nb"&gt;set &lt;/span&gt;admin
ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you forget to run &lt;code&gt;rw&lt;/code&gt; before changing passwords, the changes cannot be saved.&lt;/p&gt;

&lt;p&gt;The official documentation also recommends changing both passwords between &lt;code&gt;rw&lt;/code&gt; and &lt;code&gt;ro&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Change the Hostname
&lt;/h1&gt;

&lt;p&gt;If you manage multiple PiKVM devices, assigning meaningful hostnames based on their location or purpose makes administration easier.&lt;/p&gt;

&lt;p&gt;For example, a PiKVM managing the first server in a server room might use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pikvm-server01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the hostname using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw
systemctl restart systemd-hostnamed
hostnamectl set-hostname pikvm-server01
ro
reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After rebooting, both the SSH prompt and the network hostname reflect the new name.&lt;/p&gt;

&lt;p&gt;The official FAQ also recommends restarting &lt;code&gt;systemd-hostnamed&lt;/code&gt;, then running &lt;code&gt;hostnamectl set-hostname&lt;/code&gt;, followed by rebooting PiKVM.&lt;/p&gt;

&lt;p&gt;If you plan to install Tailscale, configuring the hostname first makes devices easier to identify in the Tailscale admin console.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Update PiKVM OS
&lt;/h1&gt;

&lt;p&gt;After the initial configuration is complete, update PiKVM OS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pikvm-update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally you do &lt;strong&gt;not&lt;/strong&gt; need to manually run &lt;code&gt;rw&lt;/code&gt; or &lt;code&gt;ro&lt;/code&gt;; &lt;code&gt;pikvm-update&lt;/code&gt; is the recommended update method.&lt;/p&gt;

&lt;p&gt;However, updates are never completely risk-free. The official documentation recommends performing updates while you still have physical access to the PiKVM, in case the SD card must be reflashed. Avoid updating a PiKVM that is only reachable over a VPN unless necessary.&lt;/p&gt;

&lt;p&gt;If you're using an older image that doesn't include &lt;code&gt;pikvm-update&lt;/code&gt;, install it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw
pacman &lt;span class="nt"&gt;-Syy&lt;/span&gt;
pacman &lt;span class="nt"&gt;-S&lt;/span&gt; pikvm-os-updater
pikvm-update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reboot afterward if required.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Install Tailscale on PiKVM
&lt;/h1&gt;

&lt;p&gt;If you intend to access PiKVM over the Internet, using a private networking solution such as Tailscale is much safer and easier to manage than exposing the Web UI or SSH directly.&lt;/p&gt;

&lt;p&gt;PiKVM provides a dedicated package called &lt;code&gt;tailscale-pikvm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After running &lt;code&gt;pikvm-update&lt;/code&gt;, install it in the following order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw
pacman &lt;span class="nt"&gt;-S&lt;/span&gt; tailscale-pikvm
systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; tailscaled
tailscale up &lt;span class="nt"&gt;--qr&lt;/span&gt;
ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;tailscale up --qr&lt;/code&gt; displays a QR code for the authentication URL directly in the terminal. This is especially convenient when using a remote console where copying long URLs is difficult. &lt;code&gt;--qr&lt;/code&gt; is an officially supported option in the current Tailscale CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using PiKVM as an Exit Node
&lt;/h3&gt;

&lt;p&gt;If you want to use PiKVM as an Exit Node, enable the Exit Node feature in Tailscale.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw

tailscale &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--advertise-exit-node&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--advertise-routes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.0/24

ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;--advertise-routes&lt;/code&gt;, specify the LAN subnet that is reachable from the PiKVM. For example, if your managed network is &lt;code&gt;192.168.0.0/24&lt;/code&gt;, use the command above.&lt;/p&gt;

&lt;p&gt;After applying the configuration, open the device in the Tailscale admin console and approve both &lt;strong&gt;Exit Node&lt;/strong&gt; and &lt;strong&gt;Subnet routes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can then select the PiKVM as the Exit Node from your Tailscale client. In addition to routing Internet traffic through the PiKVM, you'll also be able to access devices on the LAN connected to the PiKVM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring UDP GRO on Raspberry Pi
&lt;/h3&gt;

&lt;p&gt;When using a Raspberry Pi as an Exit Node, it is recommended to enable the Linux kernel's UDP GRO settings.&lt;/p&gt;

&lt;p&gt;On PiKVM, you can create the following systemd service to apply the required settings automatically at boot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/systemd/system/apply-udp-gro.service &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[Unit]
Description=Configure UDP GRO forwarding
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/ethtool -K eth0 rx-udp-gro-forwarding on rx-gro-list off
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;systemctl daemon-reload
systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; apply-udp-gro.service

ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This service runs the following command each time the PiKVM boots, ensuring that the UDP GRO settings persist across reboots.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ethtool &lt;span class="nt"&gt;-K&lt;/span&gt; eth0 rx-udp-gro-forwarding on rx-gro-list off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After configuring the service, you can verify the current settings with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ethtool &lt;span class="nt"&gt;-k&lt;/span&gt; eth0 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'rx-udp-gro-forwarding|rx-gro-list'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rx-gro-list: off
rx-udp-gro-forwarding: on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your PiKVM uses a network interface other than &lt;code&gt;eth0&lt;/code&gt;, replace it with the appropriate interface name for your environment.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. Upload Virtual Media to PiKVM
&lt;/h1&gt;

&lt;p&gt;PiKVM can present ISO and IMG files to the target machine as either a virtual CD/DVD drive or a virtual USB flash drive.&lt;/p&gt;

&lt;p&gt;Image files are stored in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/kvmd/msd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uploading through the Web UI's &lt;strong&gt;Drive&lt;/strong&gt; menu is usually the simplest approach.&lt;/p&gt;

&lt;p&gt;If you prefer using SCP or rsync manually, first remount the virtual media partition as writable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kvmd-helper-otgmsd-remount rw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Transfer the ISO from your local computer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp installer.iso root@192.168.1.100:/var/lib/kvmd/msd/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the transfer completes, remount the partition as read-only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kvmd-helper-otgmsd-remount ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The official documentation recommends the following sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;kvmd-helper-otgmsd-remount rw&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy files into &lt;code&gt;/var/lib/kvmd/msd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;kvmd-helper-otgmsd-remount ro&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping the virtual media partition read-only during normal operation helps prevent corruption caused by unexpected power loss.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is &lt;code&gt;chown kvmd:kvmd&lt;/code&gt; Necessary for ISO Files?
&lt;/h2&gt;

&lt;p&gt;Files uploaded via SCP are normally owned by &lt;code&gt;root&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /var/lib/kvmd/msd/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rw-r--r-- 1 root root ... installer.iso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As long as the file is readable by everyone, changing ownership is &lt;strong&gt;not necessarily required&lt;/strong&gt; for read-only ISO usage.&lt;/p&gt;

&lt;p&gt;In other words, you do &lt;strong&gt;not&lt;/strong&gt; need to mechanically run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;kvmd:kvmd installer.iso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for every uploaded ISO.&lt;/p&gt;

&lt;p&gt;What actually matters is that the &lt;code&gt;kvmd&lt;/code&gt; user has permission to read the file. If necessary, set the permissions like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kvmd-helper-otgmsd-remount rw
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 /var/lib/kvmd/msd/installer.iso
kvmd-helper-otgmsd-remount ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the other hand, if the image will be presented as a writable virtual flash drive, write permissions are also required. The official documentation uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;666 /var/lib/kvmd/msd/flash.img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for writable flash images.&lt;/p&gt;

&lt;p&gt;If you prefer consistent ownership across all files, you may use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kvmd-helper-otgmsd-remount rw
&lt;span class="nb"&gt;chown &lt;/span&gt;kvmd:kvmd /var/lib/kvmd/msd/installer.iso
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 /var/lib/kvmd/msd/installer.iso
kvmd-helper-otgmsd-remount ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, for read-only ISOs, the essential requirement is that the &lt;code&gt;kvmd&lt;/code&gt; user can read the file—not that the file is owned by &lt;code&gt;kvmd&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. Boot the Target Machine from Virtual Media
&lt;/h1&gt;

&lt;p&gt;After uploading an ISO, open the PiKVM Web UI and select the desired image from the &lt;strong&gt;Drive&lt;/strong&gt; menu.&lt;/p&gt;

&lt;p&gt;The basic workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;Drive&lt;/strong&gt; menu.&lt;/li&gt;
&lt;li&gt;Select the ISO or IMG file.&lt;/li&gt;
&lt;li&gt;Choose the media type.&lt;/li&gt;
&lt;li&gt;Connect the image.&lt;/li&gt;
&lt;li&gt;Reboot the target machine.&lt;/li&gt;
&lt;li&gt;Select the virtual media from the BIOS or UEFI boot menu.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;PiKVM can present virtual media in the following formats:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Appears as&lt;/th&gt;
&lt;th&gt;Typical Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CD/DVD&lt;/td&gt;
&lt;td&gt;Optical drive&lt;/td&gt;
&lt;td&gt;Standard OS installation ISOs and rescue media&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flash&lt;/td&gt;
&lt;td&gt;USB flash drive&lt;/td&gt;
&lt;td&gt;UEFI compatibility workarounds and writable disk images&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In most cases, ISO images should be connected as &lt;strong&gt;CD/DVD&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, some UEFI implementations cannot boot correctly from virtual optical drives. If that happens, reconnecting the same image as &lt;strong&gt;Flash&lt;/strong&gt; may allow the firmware to recognize it.&lt;/p&gt;

&lt;p&gt;This is a compatibility workaround for specific systems, not a universal requirement.&lt;/p&gt;

&lt;p&gt;PiKVM virtual media is accessible from both BIOS and UEFI, and the connection mode can be switched between CD/DVD and Flash through the Web UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Precautions When Using Virtual Media
&lt;/h2&gt;

&lt;p&gt;Do not power off PiKVM while an image is being uploaded or while a writable virtual image is connected to the target machine.&lt;/p&gt;

&lt;p&gt;Interrupting power during an upload or write operation can corrupt the image file or the filesystem.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. Send Ctrl+Alt+Del
&lt;/h1&gt;

&lt;p&gt;Since web browsers often intercept keys such as &lt;strong&gt;Ctrl&lt;/strong&gt; and &lt;strong&gt;Alt&lt;/strong&gt;, special key combinations should be sent from the PiKVM Web UI.&lt;/p&gt;

&lt;p&gt;To send &lt;strong&gt;Ctrl+Alt+Del&lt;/strong&gt;, open the &lt;strong&gt;Shortcuts&lt;/strong&gt; menu at the top of the Web UI and select &lt;strong&gt;Ctrl+Alt+Del&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the desired shortcut is unavailable, you can also use PiKVM's Magic Key shortcut mechanism. According to the current official documentation, you press and release the Magic Key, then press and release &lt;strong&gt;Ctrl&lt;/strong&gt;, &lt;strong&gt;Alt&lt;/strong&gt;, and &lt;strong&gt;Del&lt;/strong&gt; in sequence.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. Install Tailscale on the Target Machine
&lt;/h1&gt;

&lt;p&gt;If you also want to install Tailscale on the computer you're controlling through PiKVM, the following command is particularly convenient on Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale up &lt;span class="nt"&gt;--qr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scan the QR code displayed on the screen with your smartphone to complete Tailscale authentication.&lt;/p&gt;

&lt;p&gt;This eliminates the need to manually type long authentication URLs or rely on clipboard sharing, making it especially useful immediately after installing an operating system or in environments without a graphical desktop.&lt;/p&gt;




&lt;h1&gt;
  
  
  Recommended Setup Order
&lt;/h1&gt;

&lt;p&gt;To minimize rework, perform the initial setup in the following order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the PiKVM OS image to the SD card.&lt;/li&gt;
&lt;li&gt;Configure Wi-Fi in &lt;code&gt;pikvm.txt&lt;/code&gt; before the first boot.&lt;/li&gt;
&lt;li&gt;Boot PiKVM and determine its IP address.&lt;/li&gt;
&lt;li&gt;Connect via SSH as &lt;code&gt;root&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Change the Linux &lt;code&gt;root&lt;/code&gt; password.&lt;/li&gt;
&lt;li&gt;Change the Web UI &lt;code&gt;admin&lt;/code&gt; password.&lt;/li&gt;
&lt;li&gt;Change the hostname.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;pikvm-update&lt;/code&gt; while physical access is available.&lt;/li&gt;
&lt;li&gt;Install Tailscale if needed.&lt;/li&gt;
&lt;li&gt;Upload ISO and IMG files to the virtual media storage.&lt;/li&gt;
&lt;li&gt;Connect to the target machine through the Web UI.&lt;/li&gt;
&lt;li&gt;Attach virtual media and perform OS installation or recovery.&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;The most common source of confusion when using PiKVM is switching between the two write modes.&lt;/p&gt;

&lt;p&gt;When modifying system configuration, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rw
&lt;span class="c"&gt;# Make configuration changes&lt;/span&gt;
ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When uploading ISO or IMG files, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kvmd-helper-otgmsd-remount rw
&lt;span class="c"&gt;# Upload or remove ISO/IMG files&lt;/span&gt;
kvmd-helper-otgmsd-remount ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understanding the distinction between these two mechanisms and always returning to read-only mode afterward is fundamental to operating PiKVM safely.&lt;/p&gt;

&lt;p&gt;Also, immediately after the first boot, be sure to change &lt;strong&gt;both&lt;/strong&gt; the Linux and Web UI passwords. If you need remote access from outside your local network, it is generally better to use a private networking solution such as Tailscale rather than exposing the PiKVM Web UI directly to the Internet.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Bridging Ethernet with NetworkManager on Rocky Linux 9 While Preserving DHCP and the MAC Address</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:31:43 +0000</pubDate>
      <link>https://dev.to/vast-cow/bridging-ethernet-with-networkmanager-on-rocky-linux-9-while-preserving-dhcp-and-the-mac-address-79g</link>
      <guid>https://dev.to/vast-cow/bridging-ethernet-with-networkmanager-on-rocky-linux-9-while-preserving-dhcp-and-the-mac-address-79g</guid>
      <description>&lt;p&gt;On Ubuntu, it is common to use Netplan to attach an Ethernet interface to a Linux bridge. On Rocky Linux 9, however, &lt;strong&gt;NetworkManager&lt;/strong&gt; typically manages networking.&lt;/p&gt;

&lt;p&gt;Instead of Netplan, Rocky Linux uses &lt;code&gt;nmcli&lt;/code&gt; to create a bridge and attach the physical network interface to it.&lt;/p&gt;

&lt;p&gt;This article explains how to achieve the equivalent of an Ubuntu Netplan bridge configuration on Rocky Linux 9.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Topology
&lt;/h2&gt;

&lt;p&gt;For example, if your wired network interface is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eth0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the final configuration will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          DHCP Server
               │
        Ethernet Switch
               │
             eth0
               │
         Linux Bridge
             br0
               │
      IP Address (DHCP)
               │
         hostapd (AP)
               │
        Wi-Fi Clients
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The physical NIC becomes a member port of the bridge.&lt;/li&gt;
&lt;li&gt;The bridge (&lt;code&gt;br0&lt;/code&gt;) owns the IP address.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hostapd&lt;/code&gt; is attached to the bridge.&lt;/li&gt;
&lt;li&gt;Wi-Fi clients join the same Layer 2 network as the wired LAN.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create the Bridge
&lt;/h2&gt;

&lt;p&gt;First, create the bridge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nmcli connection add &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;type &lt;/span&gt;bridge &lt;span class="se"&gt;\&lt;/span&gt;
    ifname br0 &lt;span class="se"&gt;\&lt;/span&gt;
    con-name br0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Preserve the Bridge MAC Address
&lt;/h2&gt;

&lt;p&gt;Depending on the environment, a newly created bridge may receive a different MAC address.&lt;/p&gt;

&lt;p&gt;To preserve the existing network identity, configure the bridge to use the same MAC address as the original Ethernet interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nmcli connection modify br0 &lt;span class="se"&gt;\&lt;/span&gt;
    bridge.mac-address xx:xx:xx:xx:xx:xx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure DHCP on the Bridge
&lt;/h2&gt;

&lt;p&gt;The bridge—not the physical NIC—should obtain the IP address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nmcli connection modify br0 &lt;span class="se"&gt;\&lt;/span&gt;
    ipv4.method auto &lt;span class="se"&gt;\&lt;/span&gt;
    ipv6.method disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add the Ethernet Interface to the Bridge
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nmcli connection add &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;type &lt;/span&gt;bridge-slave &lt;span class="se"&gt;\&lt;/span&gt;
    ifname eth0 &lt;span class="se"&gt;\&lt;/span&gt;
    master br0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an existing Ethernet connection profile already exists, delete or disable it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmcli connection show

&lt;span class="nb"&gt;sudo &lt;/span&gt;nmcli connection delete &lt;span class="s2"&gt;"&amp;lt;existing-profile-name&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Activate the Bridge
&lt;/h2&gt;

&lt;p&gt;Bring the bridge online.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nmcli connection up br0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr show br0

bridge &lt;span class="nb"&gt;link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected state is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eth0&lt;/code&gt; is attached as &lt;code&gt;master br0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;br0&lt;/code&gt; owns the IP address.&lt;/li&gt;
&lt;li&gt;DHCP runs on &lt;code&gt;br0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The bridge uses the same MAC address as the original Ethernet interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Preserve the MAC Address?
&lt;/h2&gt;

&lt;p&gt;When a bridge is created, it may be assigned a new MAC address.&lt;/p&gt;

&lt;p&gt;If the MAC address changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DHCP server may recognize the system as a different device.&lt;/li&gt;
&lt;li&gt;A new DHCP lease may be assigned.&lt;/li&gt;
&lt;li&gt;MAC address–based access control may no longer work as expected.&lt;/li&gt;
&lt;li&gt;Neighboring devices may need to refresh their ARP caches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To minimize the impact on the existing network, configure the bridge to use the same MAC address as the original NIC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bridge.mac-address xx:xx:xx:xx:xx:xx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the Bridge with hostapd
&lt;/h2&gt;

&lt;p&gt;After creating the bridge, specify it in &lt;code&gt;hostapd.conf&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wlp2s0&lt;/span&gt;
&lt;span class="py"&gt;bridge&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;br0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wired LAN devices&lt;/li&gt;
&lt;li&gt;Wi-Fi clients&lt;/li&gt;
&lt;li&gt;The DHCP server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;to operate on the same Layer 2 network, enabling Wi-Fi clients to obtain IP addresses directly from the existing DHCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Configuration Persists Across Reboots
&lt;/h2&gt;

&lt;p&gt;A bridge created with &lt;code&gt;nmcli&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; a temporary configuration.&lt;/p&gt;

&lt;p&gt;NetworkManager stores connection profiles as &lt;code&gt;.nmconnection&lt;/code&gt; files under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/NetworkManager/system-connections/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These profiles are automatically restored after a reboot.&lt;/p&gt;

&lt;p&gt;For example, running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nmcli connection add &lt;span class="nb"&gt;type &lt;/span&gt;bridge ifname br0 con-name br0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;creates a configuration file similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/NetworkManager/system-connections/br0.nmconnection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bridge slave connections are stored in the same way.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;nmcli&lt;/code&gt; is therefore the standard method for creating persistent bridge configurations.&lt;/p&gt;

&lt;p&gt;By contrast, a bridge created only with the &lt;code&gt;ip&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;add br0 &lt;span class="nb"&gt;type &lt;/span&gt;bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is temporary and disappears after a reboot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Precautions When Reconfiguring Over SSH
&lt;/h2&gt;

&lt;p&gt;Ubuntu's Netplan provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;netplan try
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which temporarily applies a configuration and automatically rolls it back if connectivity is lost.&lt;/p&gt;

&lt;p&gt;NetworkManager does &lt;strong&gt;not&lt;/strong&gt; provide an equivalent automatic rollback mechanism.&lt;/p&gt;

&lt;p&gt;When changing network settings over SSH, it is recommended to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure you have access to an out-of-band management console such as IPMI or iLO.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;tmux&lt;/code&gt; or &lt;code&gt;screen&lt;/code&gt; to reduce the risk of losing your session.&lt;/li&gt;
&lt;li&gt;Verify that the bridge is functioning correctly before deleting the original connection profile.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Netplan and NetworkManager Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Netplan&lt;/th&gt;
&lt;th&gt;NetworkManager (&lt;code&gt;nmcli&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bridges:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;type bridge&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interfaces:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bridge-slave&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dhcp4: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ipv4.method auto&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dhcp6: false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ipv6.method disabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;macaddress:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bridge.mac-address&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;netplan apply&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nmcli connection up br0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;netplan try&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No equivalent feature&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;On Rocky Linux 9, you can use NetworkManager's &lt;code&gt;nmcli&lt;/code&gt; to build the same bridge configuration that is commonly implemented with Netplan on Ubuntu.&lt;/p&gt;

&lt;p&gt;The key points are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assign the IP address to the bridge rather than the physical NIC.&lt;/li&gt;
&lt;li&gt;Configure the bridge to use the same MAC address as the original Ethernet interface.&lt;/li&gt;
&lt;li&gt;Point &lt;code&gt;hostapd&lt;/code&gt; to the bridge.&lt;/li&gt;
&lt;li&gt;Bridge configurations created with &lt;code&gt;nmcli&lt;/code&gt; are persistent across reboots.&lt;/li&gt;
&lt;li&gt;Unlike Netplan, NetworkManager does not provide an automatic rollback feature similar to &lt;code&gt;netplan try&lt;/code&gt;, so exercise caution when making network changes over SSH.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this setup, wired LAN devices and Wi-Fi clients share the same Layer 2 network, allowing Wi-Fi clients to obtain IP addresses directly from the existing DHCP server.&lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>linux</category>
      <category>networking</category>
    </item>
    <item>
      <title>Bridging Ethernet with Netplan While Preserving DHCP and the MAC Address</title>
      <dc:creator>vast cow</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:23:28 +0000</pubDate>
      <link>https://dev.to/vast-cow/bridging-ethernet-with-netplan-while-preserving-dhcp-and-the-mac-address-20ko</link>
      <guid>https://dev.to/vast-cow/bridging-ethernet-with-netplan-while-preserving-dhcp-and-the-mac-address-20ko</guid>
      <description>&lt;p&gt;When using a Linux machine as a Wi-Fi access point with &lt;strong&gt;hostapd&lt;/strong&gt;, you may want Wi-Fi clients to join the same Layer 2 (L2) network as your existing wired LAN.&lt;/p&gt;

&lt;p&gt;A common approach is to add the Ethernet interface to a Linux bridge and assign the IP address to the bridge instead of the physical interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Netplan Configuration
&lt;/h2&gt;

&lt;p&gt;For example, if your Ethernet interface is &lt;code&gt;eth0&lt;/code&gt;, configure it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;renderer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networkd&lt;/span&gt;

  &lt;span class="na"&gt;ethernets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;eth0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;dhcp4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;dhcp6&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="na"&gt;bridges&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;br0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;eth0&lt;/span&gt;
      &lt;span class="na"&gt;dhcp4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;dhcp6&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;macaddress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xx:xx:xx:xx:xx:xx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Makes &lt;code&gt;eth0&lt;/code&gt; a member port of the bridge.&lt;/li&gt;
&lt;li&gt;Allows &lt;code&gt;br0&lt;/code&gt; to obtain its IP address via DHCP.&lt;/li&gt;
&lt;li&gt;Sets the MAC address of &lt;code&gt;br0&lt;/code&gt; to match the original Ethernet interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Fix the MAC Address?
&lt;/h3&gt;

&lt;p&gt;When a bridge is created, some environments assign a new MAC address to &lt;code&gt;br0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the MAC address changes, it may result in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DHCP server recognizing it as a new client.&lt;/li&gt;
&lt;li&gt;A different DHCP lease being assigned.&lt;/li&gt;
&lt;li&gt;MAC address–based access control rules no longer matching.&lt;/li&gt;
&lt;li&gt;ARP caches being refreshed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To minimize the impact on the existing network, explicitly configure the bridge to use the original Ethernet interface's MAC address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;macaddress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xx:xx:xx:xx:xx:xx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Safely Testing the Configuration
&lt;/h2&gt;

&lt;p&gt;When modifying network settings over a remote SSH session, an incorrect configuration can cause you to lose connectivity.&lt;/p&gt;

&lt;p&gt;Instead of immediately running &lt;code&gt;netplan apply&lt;/code&gt;, it is safer to use &lt;code&gt;netplan try&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;netplan try &lt;span class="nt"&gt;--timeout&lt;/span&gt; 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command temporarily applies the configuration. If you confirm that everything is working within 30 seconds, the changes become permanent.&lt;/p&gt;

&lt;p&gt;If the connection is lost or you do not confirm the changes, Netplan automatically restores the previous configuration after the timeout expires.&lt;/p&gt;

&lt;p&gt;This makes it much safer to test network configuration changes over SSH.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Bridge with hostapd
&lt;/h2&gt;

&lt;p&gt;After creating the bridge, configure &lt;strong&gt;hostapd&lt;/strong&gt; to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wlp2s0&lt;/span&gt;
&lt;span class="py"&gt;bridge&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;br0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, Wi-Fi clients connect to the wired LAN through &lt;code&gt;br0&lt;/code&gt;, placing them on the same Layer 2 network.&lt;/p&gt;

&lt;p&gt;As a result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wired devices&lt;/li&gt;
&lt;li&gt;Wi-Fi devices&lt;/li&gt;
&lt;li&gt;The DHCP server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;all operate on the same network segment, allowing Wi-Fi clients to obtain IP addresses directly from the existing DHCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;When using &lt;strong&gt;hostapd&lt;/strong&gt; on Linux, a bridged network configuration is a simple and maintainable solution.&lt;/p&gt;

&lt;p&gt;To minimize disruption to your existing network, keep these three points in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assign the IP address to the bridge rather than the physical Ethernet interface.&lt;/li&gt;
&lt;li&gt;Configure the bridge to use the same MAC address as the original Ethernet interface.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;netplan try&lt;/code&gt; during remote administration to safely test configuration changes before committing them.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
  </channel>
</rss>
