<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Kvm on inetshell</title><link>https://inet.sh/en/tags/kvm/</link><description>Recent content in Kvm on inetshell</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/en/tags/kvm/index.xml" rel="self" type="application/rss+xml"/><item><title>Mount a qcow2 disk on the host with qemu-nbd (read or repair files of a powered-off VM)</title><link>https://inet.sh/en/posts/linux/mount-qcow2-qemu-nbd/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/linux/mount-qcow2-qemu-nbd/</guid><description>A qcow2 cannot be loop-mounted: qemu-nbd exposes it as a block device (/dev/nbd0) and the partitions come out of that. How to do it read-only or read-write, what happens when the VM disk carries LVM (duplicate VG names, vgimportclone), how to mount an internal snapshot, guestmount as the nbd-free alternative, and why never with the VM running.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p>With the VM <strong>powered off</strong>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">modprobe nbd <span class="nv">max_part</span><span class="o">=</span><span class="m">16</span>
</span></span><span class="line"><span class="cl">qemu-nbd --connect<span class="o">=</span>/dev/nbd0 /var/lib/vz/images/100/vm-100-disk-0.qcow2   <span class="c1"># add -r for read-only</span>
</span></span><span class="line"><span class="cl">partprobe /dev/nbd0
</span></span><span class="line"><span class="cl">lsblk /dev/nbd0                                  <span class="c1"># nbd0p1, nbd0p2…</span>
</span></span><span class="line"><span class="cl">mount /dev/nbd0p2 /mnt/vm
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ... read, copy, or repair ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">umount /mnt/vm
</span></span><span class="line"><span class="cl">qemu-nbd --disconnect /dev/nbd0
</span></span></code></pre></td></tr></table>
</div>
</div><p>Without the <code>--disconnect</code> the VM will not start again: QEMU finds the image locked.</p>
<h2 id="why-nbd">Why nbd</h2>
<p>A <code>.qcow2</code> is not a dump of the disk: it has a header, a cluster table, internal snapshots, and sometimes a backing file behind it. <code>losetup</code> does not understand it. <code>qemu-nbd</code> does, and publishes it as a block device through the kernel&rsquo;s NBD client; from there on it is one more disk for <code>lsblk</code>, <code>mount</code>, <code>fsck</code>, or <code>dd</code>.</p>
<p><code>max_part</code> matters: without it the kernel does not create the <code>nbd0p1</code>, <code>nbd0p2</code>… nodes. <code>partprobe</code> forces them if they do not show up after <code>--connect</code>.</p>
<h2 id="if-the-vm-disk-carries-lvm">If the VM disk carries LVM</h2>
<p>The norm on Ubuntu Server, RHEL, and Fedora: inside the partition there is a VG. Activate it, mount the LV, and when done deactivate it before disconnecting:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">vgscan
</span></span><span class="line"><span class="cl">vgchange -ay                                   <span class="c1"># activates whatever it finds on nbd0</span>
</span></span><span class="line"><span class="cl">lvs                                            <span class="c1"># ubuntu-vg/ubuntu-lv, rhel/root…</span>
</span></span><span class="line"><span class="cl">mount /dev/ubuntu-vg/ubuntu-lv /mnt/vm
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">umount /mnt/vm
</span></span><span class="line"><span class="cl">vgchange -an ubuntu-vg
</span></span><span class="line"><span class="cl">qemu-nbd --disconnect /dev/nbd0
</span></span></code></pre></td></tr></table>
</div>
</div><p>Classic trap: the host has a VG with the <strong>same name</strong> as the VM&rsquo;s (<code>ubuntu-vg</code> vs <code>ubuntu-vg</code>, <code>pve</code> vs <code>pve</code>). LVM refuses to activate two alike. The tool for that is <code>vgimportclone</code>, which renames the VG on the imported disk without touching the host&rsquo;s:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">vgimportclone -n vm100-vg /dev/nbd0p3
</span></span><span class="line"><span class="cl">vgchange -ay vm100-vg
</span></span></code></pre></td></tr></table>
</div>
</div><p>If you would rather change nothing inside the VM, <code>guestmount</code> (below) resolves the LVM on its own and does not touch the host&rsquo;s LVM.</p>
<h2 id="windows-ntfs-and-internal-snapshots">Windows, NTFS, and internal snapshots</h2>
<ul>
<li><strong>NTFS</strong>: <code>mount -t ntfs3 /dev/nbd0p3 /mnt/vm</code> (kernel 5.15+) or <code>ntfs-3g</code>. If Windows was left hibernated or with <em>Fast Startup</em>, NTFS only mounts read-only; shut it down properly from inside before writing.</li>
<li><strong>qcow2 internal snapshot</strong> (<code>qemu-img snapshot -l vm.qcow2</code>): <code>qemu-nbd -r -l before-the-change --connect=/dev/nbd0 vm.qcow2</code> mounts the state of that snapshot, read-only, without touching the current disk.</li>
<li><strong>Other formats</strong>: <code>qemu-nbd</code> also speaks vmdk, vhdx, and vdi (<code>-f vhdx</code> if it does not detect it); <code>qemu-img convert -O raw</code> is the alternative if you are going to work on the image for a long time.</li>
</ul>
<h2 id="guestmount-the-same-without-nbd-or-root">guestmount: the same without nbd or root</h2>
<p>libguestfs mounts the image over FUSE, detects partitions and LVM on its own, and does not interfere with the host:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">apt install libguestfs-tools          <span class="c1"># dnf install guestfs-tools on Fedora/RHEL</span>
</span></span><span class="line"><span class="cl">virt-filesystems --long -h --all -a vm.qcow2      <span class="c1"># what is inside</span>
</span></span><span class="line"><span class="cl">guestmount -a vm.qcow2 -i --ro /mnt/vm            <span class="c1"># -i: mount the operating system it finds</span>
</span></span><span class="line"><span class="cl">guestunmount /mnt/vm
</span></span></code></pre></td></tr></table>
</div>
</div><p>Slower than nbd for copying gigabytes, but impossible to leave half closed.</p>
<h2 id="traps">Traps</h2>
<ul>
<li><strong>Never with the VM running.</strong> Read-write you will corrupt the filesystem for sure; with <code>-r</code> you see an inconsistent state (what the guest holds in cache is not on disk). If you need to look at a live VM, take a snapshot and mount the snapshot.</li>
<li><strong>The image stays locked</strong> while <code>qemu-nbd</code> is connected: <code>qm start 100</code> fails with <code>Failed to get &quot;write&quot; lock</code>. <code>qemu-nbd -d /dev/nbd0</code> releases it; <code>ls /sys/block/nbd*/pid</code> tells which ones are still in use.</li>
<li><strong><code>--disconnect</code> with &ldquo;device busy&rdquo;</strong>: something is still mounted or a VG is active (<code>vgchange -an</code>). <code>lsof /dev/nbd0*</code> finds it.</li>
<li><strong>Proxmox with LVM-thin or ZFS</strong> needs no nbd: the VM disk is already a block device (<code>/dev/pve/vm-100-disk-0</code>, <code>/dev/zvol/rpool/data/vm-100-disk-0</code>); <code>kpartx -av</code> on it exposes the partitions. <code>qemu-nbd</code> is for qcow2 on directory and NFS storages.</li>
<li><strong>Backing files</strong>: an image with a <code>backing file</code> mounts the same; whatever you write goes to the top layer. Do not move the base while it is connected.</li>
</ul>
<p>See also: <a href="/en/posts/windows/disk2vhd-p2v-vhdx-live-machine/">convert a physical machine to VHDX</a> and <a href="/en/posts/proxmox/cloud-image-templates/">disk import on Proxmox</a>. Reference: <a href="https://www.qemu.org/docs/master/tools/qemu-nbd.html">qemu-nbd</a> in the QEMU documentation.</p>
]]></content:encoded></item><item><title>PiKVM: force 1080p (or any mode you want) by editing the EDID</title><link>https://inet.sh/en/posts/pikvm/pikvm-force-1080p-edid/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/pikvm/pikvm-force-1080p-edid/</guid><description>The PiKVM V3 (and DIY V0–V2) captures 1080p only at 50 Hz and therefore advertises 720p by default; some consoles, like Proxmox&amp;#39;s, look bad. kvmd-edidconf --import-preset=v3.1080p-by-default --apply changes the EDID the host sees. How to check it, change the monitor name so Windows re-detects it, and go back to the original preset.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">rw
</span></span><span class="line"><span class="cl">kvmd-edidconf --import-preset<span class="o">=</span>v3.1080p-by-default --apply    <span class="c1"># V3, and DIY V0–V2 with their preset (v0, v1, v2)</span>
</span></span><span class="line"><span class="cl">ro
</span></span></code></pre></td></tr></table>
</div>
</div><p>Then <strong>reboot the host</strong> or unplug and replug the HDMI: the EDID is only read when the host detects the monitor. On <strong>V4 Mini</strong> and <strong>V4 Plus</strong> none of this is needed: they capture 1080p at 60 Hz out of the box.</p>
<h2 id="why">Why</h2>
<p>The PiKVM presents itself to the host as a monitor, and what that monitor &ldquo;can do&rdquo; lives in the <strong>EDID</strong>: resolutions, refresh rates, and which one is preferred. The host picks from that.</p>
<p>The V3 (and DIY builds with the TC358743 CSI capture chip) has a hardware limit: <strong>1080p only at 50 Hz</strong>. Since 50 Hz is an unusual rate, the default EDID marks <strong>720p60</strong> as the preferred mode and many hosts stay there. That is right for most, but some systems look bad at 720p: the Proxmox console, some BIOS screens, desktops that scale oddly. The <code>v3.1080p-by-default</code> preset keeps the same modes but makes <strong>1080p50</strong> the preferred one.</p>
<h2 id="checking">Checking</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kvmd-edidconf                 <span class="c1"># no arguments: manufacturer, product, serial, name and audio of the current EDID</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In the web UI, the video indicator shows the resolution and fps being captured; after the host reboot it should read <code>1920x1080</code> and <code>50</code>. If the host has a display tool (<code>xrandr</code>, Windows Settings), the &ldquo;monitor&rdquo; shows up with the EDID&rsquo;s name.</p>
<h2 id="other-edid-settings">Other EDID settings</h2>
<p>Windows stores per-monitor settings keyed by manufacturer, product, and serial. If the host stays in an old mode after changing the EDID, change its identity so it treats it as a new monitor:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">rw
</span></span><span class="line"><span class="cl">kvmd-edidconf --set-monitor-name<span class="o">=</span>PIKVM --set-mfc-id<span class="o">=</span>PKV --set-product-id<span class="o">=</span><span class="m">34953</span> --set-serial<span class="o">=</span><span class="m">1234567890</span> --apply
</span></span><span class="line"><span class="cl">ro
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>--set-audio=1</code> advertises audio over HDMI (to capture sound on models that support it).</li>
<li>On the <strong>V4 Plus</strong>, with a real monitor connected to the OUT2 output, <code>--import-display-ids</code> copies its identity into the PiKVM&rsquo;s EDID.</li>
<li>Back to the original: <code>kvmd-edidconf --import-preset=v3 --apply</code> (or <code>v4mini</code>, <code>v4plus</code> depending on the model).</li>
</ul>
<h2 id="traps">Traps</h2>
<ul>
<li><strong><code>rw</code> before, <code>ro</code> after.</strong> The PiKVM filesystem is read-only; without <code>rw</code> the change is not saved and without <code>ro</code> it nags you on every login.</li>
<li><strong>The host must re-read the EDID.</strong> <code>--apply</code> takes effect on the PiKVM instantly, but the host does not notice until a reboot, a cable replug, or on some BIOSes only at POST.</li>
<li><strong>50 Hz on the V3 is not a bug</strong>, it is the capture chip&rsquo;s limit. For 60 Hz, V4.</li>
<li><strong>Black screen after a hand-made EDID</strong>: import the model&rsquo;s preset again and apply.</li>
<li><strong>Proxmox</strong> and other framebuffer consoles fix the mode at boot; after changing the EDID you have to reboot the node, not just the session.</li>
</ul>
<p>Reference: <a href="https://docs.pikvm.org/edid/">EDID</a> in the PiKVM documentation.</p>
]]></content:encoded></item><item><title>Proxmox: Windows is slow with CPU type host (md_clear and flush_l1d)</title><link>https://inet.sh/en/posts/proxmox/windows-slow-cpu-type-host/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/proxmox/windows-slow-cpu-type-host/</guid><description>Why a Windows VM on Proxmox VE crawls and stutters with CPU type &amp;#39;host&amp;#39; and flies with x86-64-v2-AES: the md_clear and flush_l1d flags switch on the MDS and L1TF mitigations inside the guest and blow up memory latency. Which CPU type to pick and what happens if you insist on host.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">qm <span class="nb">set</span> <span class="m">102</span> --cpu x86-64-v2-AES    <span class="c1"># or x86-64-v3 if every host is Haswell / EPYC or newer</span>
</span></span><span class="line"><span class="cl">qm stop <span class="m">102</span> <span class="o">&amp;&amp;</span> qm start <span class="m">102</span>       <span class="c1"># a reboot from inside Windows does NOT apply the change</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="symptoms">Symptoms</h2>
<p>Windows 10, 11, or Server with <code>cpu: host</code>: slow boot, stuttering desktop, multi-second freezes, anything that touches memory is bad. The same VM with <code>x86-64-v2-AES</code> responds normally. A Linux VM with <code>host</code> on the same node notices nothing.</p>
<h2 id="why-it-happens">Why it happens</h2>
<p><code>host</code> passes the VM <strong>every</strong> flag of the physical CPU, including two that exist for Intel vulnerability mitigations:</p>
<table>
  <thead>
      <tr>
          <th>Flag</th>
          <th>Mitigation</th>
          <th>What Windows does when it sees it</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>md_clear</code></td>
          <td>MDS (Microarchitectural Data Sampling)</td>
          <td>Runs <code>VERW</code> on every kernel ↔ user transition to flush buffers</td>
      </tr>
      <tr>
          <td><code>flush_l1d</code></td>
          <td>L1TF (L1 Terminal Fault)</td>
          <td>Flushes the L1D cache on every VM entry / context switch</td>
      </tr>
  </tbody>
</table>
<p>Inside a VM those operations are extremely expensive. The measurements from the <a href="https://forum.proxmox.com/threads/the-reasons-for-poor-performance-of-windows-when-the-cpu-type-is-host.163114/">original Proxmox forum thread</a>: on a Xeon E5-2667 v2 the RAM read latency goes from <strong>~100 ns to ~2000 ns</strong>; on an i9-13900K, from 30 to 90 ns. Windows, which touches kernel memory all the time, crawls.</p>
<p>The generic types (<code>x86-64-v2-AES</code>, <code>x86-64-v3</code>, <code>IvyBridge-IBRS</code>, …) do not expose those two flags, so Windows neither sees the vulnerability nor turns on the mitigation.</p>
<h2 id="which-type-to-pick">Which type to pick</h2>
<table>
  <thead>
      <tr>
          <th>Type</th>
          <th>Host requirement</th>
          <th>Notes</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>x86-64-v2-AES</code></td>
          <td>Intel Westmere / AMD Opteron 4th gen or newer</td>
          <td>GUI default since PVE 8. Migrates across different hosts</td>
      </tr>
      <tr>
          <td><code>x86-64-v3</code></td>
          <td>Intel Haswell / AMD EPYC or newer</td>
          <td>AVX2. Migrates across hosts that meet it</td>
      </tr>
      <tr>
          <td><code>x86-64-v4</code></td>
          <td>Skylake-SP / Zen 4 or newer</td>
          <td>AVX-512</td>
      </tr>
      <tr>
          <td><code>host</code></td>
          <td>All identical</td>
          <td>Maximum flags, but no migration across different CPUs and this Windows problem</td>
      </tr>
  </tbody>
</table>
<p>Pick the highest one that <strong>every</strong> node in the cluster supports:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">grep -o -w -E <span class="s1">&#39;avx2|avx512f&#39;</span> /proc/cpuinfo <span class="p">|</span> sort -u   <span class="c1"># avx2 → v3 ok; avx512f → v4 ok</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>That is the Proxmox staff recommendation in that same thread: use the <code>x86-64-v*</code> matching the hardware instead of blindly turning security flags off.</p>
<h2 id="if-you-insist-on-host">If you insist on host</h2>
<p>There are cases where <code>host</code> is needed: nested virtualization (Hyper-V, WSL2 inside the VM, an ESXi lab), because the <code>x86-64-v*</code> types do not expose <code>vmx</code>/<code>svm</code>. You can drop <code>md_clear</code> with the <code>flags</code> option:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">qm <span class="nb">set</span> <span class="m">102</span> --cpu host,flags<span class="o">=</span>-md-clear
</span></span></code></pre></td></tr></table>
</div>
</div><p>But <code>flush_l1d</code> is not in the list of flags Proxmox lets you touch, so you keep half of the problem. And hiding mitigations from the guest is a security decision: reasonable on a single-owner node, not on one with tenants you do not control.</p>
<h2 id="checking-from-inside-windows">Checking from inside Windows</h2>
<p>Microsoft&rsquo;s official module shows which mitigations the guest has active:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">Install-Module</span> <span class="n">SpeculationControl</span> <span class="n">-Scope</span> <span class="n">CurrentUser</span>
</span></span><span class="line"><span class="cl"><span class="nb">Get-SpeculationControlSettings</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>With <code>host</code>, <code>MDSWindowsSupportEnabled</code> and <code>L1TFWindowsSupportEnabled</code> come out <code>True</code>; with <code>x86-64-v2-AES</code>, <code>False</code>. It is the quick way to confirm the change applied.</p>
<h2 id="traps">Traps</h2>
<ul>
<li><strong>The change needs stop/start.</strong> The CPU type is fixed when the QEMU process launches; a reboot from inside the guest does not touch it.</li>
<li><strong>It is an Intel thing.</strong> <code>md_clear</code> and <code>flush_l1d</code> do not exist on AMD; with <code>host</code> on EPYC/Ryzen you will not see this drop (though <code>host</code> still blocks migration across different CPUs).</li>
<li><strong>It applies to new VMs too.</strong> Templates created with <code>host</code> inherit the problem; change it in the template.</li>
<li><strong>Windows stays safe against what matters inside a VM with either type</strong>: the mitigations that count (Spectre v2, etc.) remain active; what goes away is the double mitigation of vulnerabilities the host already mitigates.</li>
</ul>
<p>References: <a href="https://forum.proxmox.com/threads/the-reasons-for-poor-performance-of-windows-when-the-cpu-type-is-host.163114/">Proxmox forum thread</a> and the <a href="https://pve.proxmox.com/pve-docs/chapter-qm.html#qm_cpu">CPU section</a> of the admin guide.</p>
]]></content:encoded></item><item><title>Virtualized pfSense with VirtIO: no traffic until hardware checksum offload is disabled</title><link>https://inet.sh/en/posts/pfsense/virtio-disable-hardware-checksum-offload/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/pfsense/virtio-disable-hardware-checksum-offload/</guid><description>pfSense or OPNsense on Proxmox, KVM or bhyve with VirtIO NICs (vtnet): the firewall boots but passes no traffic, or hosts behind it cannot be reached from the hypervisor and other VMs. It is vtnet checksum offload on FreeBSD. System &amp;gt; Advanced &amp;gt; Networking &amp;gt; Disable hardware checksum offload and a mandatory reboot.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p><strong>System → Advanced → Networking → Disable hardware checksum offload</strong> ✔ → <strong>Save</strong> → <strong>reboot the VM</strong>. The GUI does not say so, but the change does not apply to <code>vtnet</code> interfaces until the reboot.</p>
<p>To test without rebooting (lost at boot):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ifconfig vtnet0 -rxcsum -txcsum -tso -lro
</span></span><span class="line"><span class="cl">ifconfig vtnet1 -rxcsum -txcsum -tso -lro
</span></span></code></pre></td></tr></table>
</div>
</div><p>On OPNsense: <strong>Interfaces → Settings → Hardware CRC → Disable hardware checksum offload</strong>, and the same reboot.</p>
<h2 id="symptoms">Symptoms</h2>
<p>The VM boots, the interfaces are <code>UP</code>, LAN DHCP hands out addresses, and still:</p>
<ul>
<li>Hosts behind the firewall cannot reach the Internet, or connections stall halfway (ping passes, TCP hangs).</li>
<li>From the virtualization host itself or from other VMs on the same host nothing protected by pfSense is reachable, not even the GUI.</li>
<li>Switching the VM&rsquo;s NIC to <code>e1000</code> fixes it on the spot, which confirms the diagnosis.</li>
</ul>
<h2 id="why-it-happens">Why it happens</h2>
<p>FreeBSD&rsquo;s <code>vtnet</code> driver, with checksum offload enabled, hands the network stack packets whose checksum never got computed: between VMs on the same host, and between the host and the VM, traffic never crosses a physical NIC that would do it. FreeBSD sees invalid checksums and drops them (FreeBSD bug 165059). Netgate documents it under <a href="https://docs.netgate.com/pfsense/en/latest/virtualization/virtio-driver-support.html">VirtIO Driver Support</a>.</p>
<p>pfSense tries to tick the option only when it detects <code>vtnet</code> interfaces <strong>at install time</strong>. If the VirtIO NIC was added later, if you restored the <code>config.xml</code> from a physical firewall, or if you migrated the VM from another hypervisor with a different NIC type, the option stays off.</p>
<h2 id="procedure">Procedure</h2>
<ol>
<li>
<p><strong>System → Advanced → Networking</strong>, <em>Network Interfaces</em> section:</p>
<ul>
<li><strong>Disable hardware checksum offload</strong>: tick it.</li>
<li><strong>Disable hardware TCP segmentation offload</strong> and <strong>Disable hardware large receive offload</strong>: already ticked by default; leave them.</li>
</ul>
</li>
<li>
<p><strong>Save</strong>.</p>
</li>
<li>
<p><strong>Reboot</strong> from Diagnostics → Reboot. Without this step nothing changes on <code>vtnet</code>.</p>
</li>
<li>
<p>Check:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ifconfig vtnet0 <span class="p">|</span> grep options
</span></span></code></pre></td></tr></table>
</div>
</div><p>Neither <code>RXCSUM</code> nor <code>TXCSUM</code> (nor <code>TSO4</code>, <code>LRO</code>) should show up.</p>
</li>
</ol>
<h2 id="if-it-is-still-the-same">If it is still the same</h2>
<ul>
<li><strong>All of the VM&rsquo;s NICs must be the same kind.</strong> A <code>vtnet</code> with offload and an <code>e1000</code> behave differently; check every <code>ifconfig</code>.</li>
<li><strong>Offload on the host.</strong> Netgate notes that sometimes checksums and TSO must also be turned off on the hypervisor. On Proxmox/KVM, on the VM&rsquo;s <code>tap</code> interface: <code>ethtool -K tap100i0 tx off</code> (the number is the VMID and <code>i0</code> the NIC). Rarely needed; try it last.</li>
<li><strong>Plan B</strong>: <code>e1000</code> NIC model on the VM. Less throughput than VirtIO without offload, but none of this bug.</li>
</ul>
<h2 id="traps">Traps</h2>
<ul>
<li><strong>The reboot is not optional</strong> and the GUI does not ask for it. It is the number one cause of &ldquo;I ticked it and it still does not work&rdquo;.</li>
<li><strong>It is not a rules problem.</strong> With offload on, packets are dropped before reaching <code>pf</code>; the firewall log shows nothing.</li>
<li><strong><code>e1000</code> on OPNsense too</strong> as plan B; the equivalent setting lives under <em>Interfaces → Settings</em>.</li>
<li><strong>Multiqueue</strong> (<code>queues=N</code> on Proxmox) works fine with <code>vtnet</code> once offload is off; do not mistake it for the cause.</li>
</ul>
]]></content:encoded></item></channel></rss>