<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Linux |</title><link>https://www.isarvin.com/tags/linux/</link><atom:link href="https://www.isarvin.com/tags/linux/index.xml" rel="self" type="application/rss+xml"/><description>Linux</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-gb</language><lastBuildDate>Mon, 08 Jun 2026 00:00:00 +0000</lastBuildDate><image><url>https://www.isarvin.com/media/sharing.png</url><title>Linux</title><link>https://www.isarvin.com/tags/linux/</link></image><item><title>Emulating an Old-World LoongArch64 VM with QEMU</title><link>https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/</link><pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate><guid>https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/</guid><description>&lt;p&gt;LoongArch64 has an awkward bit of history: the ecosystem is often described as split between
. For the kind of server images I care about here, especially KylinOS Server and UnionTech OS Server, the practical target is still old-world compatibility.&lt;/p&gt;
&lt;p&gt;That is why the firmware file matters. A newer LoongArch64 UEFI build is not automatically better for these guests. For this workflow, &lt;code&gt;QEMU_EFI_7.2.fd&lt;/code&gt; is the boringly useful choice: old enough to match the systems being installed, but still aligned with QEMU&amp;rsquo;s LoongArch64 support era.&lt;/p&gt;
&lt;p&gt;I have shared my copy here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The file I am using is 3,801,088 bytes. Its SHA256 checksum is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;59ab6e18af0ca75ff1556ebb4b6ad640ffbf09499710e655172254f93a2d63ba
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can verify it after downloading:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;shasum -a &lt;span class="m"&gt;256&lt;/span&gt; QEMU_EFI_7.2.fd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="callout flex px-4 py-3 mb-6 rounded-md border-l-4 bg-blue-100 dark:bg-blue-900 border-blue-500"
data-callout="note"
data-callout-metadata=""&gt;
&lt;span class="callout-icon pr-3 pt-1 text-blue-600 dark:text-blue-300"&gt;
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m16.862 4.487l1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8l.8-2.685a4.5 4.5 0 0 1 1.13-1.897zm0 0L19.5 7.125"/&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="callout-content dark:text-neutral-300"&gt;
&lt;div class="callout-title font-semibold mb-1"&gt;Note&lt;/div&gt;
&lt;div class="callout-body"&gt;&lt;p&gt;This post is about full-system emulation, not native virtualisation. On an x86_64 or Apple Silicon host, LoongArch64 can be quite slow. If a guest has not reached the installer or booted system after a long time, the problem may be firmware, guest compatibility, or host QEMU support rather than just patience.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;details class="print:hidden xl:hidden" open&gt;
&lt;summary&gt;Table of Contents&lt;/summary&gt;
&lt;div class="text-sm"&gt;
&lt;nav id="TableOfContents"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#install-qemu"&gt;Install QEMU&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#prepare-the-files"&gt;Prepare the files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#boot-from-an-iso"&gt;Boot from an ISO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#boot-an-installed-qcow2"&gt;Boot an installed qcow2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#use-qemu-monitor"&gt;Use QEMU Monitor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#confirm-the-architecture"&gt;Confirm the architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#optional-use-utm-on-macos"&gt;Optional: use UTM on macOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;div class="hb-steps"&gt;
&lt;h3 id="install-qemu"&gt;Install QEMU&lt;/h3&gt;
&lt;p&gt;On macOS, Homebrew is the easiest way to install QEMU:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install qemu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-system-loongarch64 --version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-system-loongarch64 -machine &lt;span class="nb"&gt;help&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-system-loongarch64 -cpu &lt;span class="nb"&gt;help&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On Linux, install the package that provides &lt;code&gt;qemu-system-loongarch64&lt;/code&gt;. For example, on a Fedora-like host:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dnf install -y qemu-system-loongarch64 qemu-img
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;QEMU 7.2 or newer is strongly preferred. QEMU 7.1 introduced initial LoongArch support, and QEMU 7.2 included important updates. In practice, I would not spend time debugging old QEMU builds for this.&lt;/p&gt;
&lt;p&gt;UTM is worth knowing about on macOS because it is a pleasant QEMU-based VM manager. It can emulate a LoongArch64 VM, so the point is not that UTM is unavailable. For this old-world cloud-image workflow, though, I still prefer the direct &lt;code&gt;qemu-system-loongarch64&lt;/code&gt; command because the firmware, device, monitor, and forwarding options are easier to reproduce exactly.&lt;/p&gt;
&lt;h3 id="prepare-the-files"&gt;Prepare the files&lt;/h3&gt;
&lt;p&gt;Create a working directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p ~/VMs/loongarch64
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/VMs/loongarch64
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Put these files in that directory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QEMU_EFI_7.2.fd&lt;/code&gt;, downloaded from one of the cloud-drive links above.&lt;/li&gt;
&lt;li&gt;A LoongArch64 server ISO, such as KylinOS Server or UnionTech OS Server.&lt;/li&gt;
&lt;li&gt;A qcow2 disk created for the VM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create the disk with &lt;code&gt;qemu-img&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-img create -f qcow2 KylinOS-Server-loongarch64.qcow2 40G
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Adjust the filename and size for your own guest. These systems are Red Hat-like, so a 40 GB disk is a reasonable starting point for a small build or image-preparation VM.&lt;/p&gt;
&lt;h3 id="boot-from-an-iso"&gt;Boot from an ISO&lt;/h3&gt;
&lt;p&gt;Use this shape when installing a fresh LoongArch64 server guest from an ISO:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-system-loongarch64 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -M virt &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -bios QEMU_EFI_7.2.fd &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -cdrom KylinOS-Server-loongarch64.iso &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -chardev socket,id&lt;span class="o"&gt;=&lt;/span&gt;qemu-ga.0,path&lt;span class="o"&gt;=&lt;/span&gt;qemu-ga.sock,server&lt;span class="o"&gt;=&lt;/span&gt;on,wait&lt;span class="o"&gt;=&lt;/span&gt;off &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -cpu la464-loongarch-cpu &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device nec-usb-xhci,id&lt;span class="o"&gt;=&lt;/span&gt;xhci,addr&lt;span class="o"&gt;=&lt;/span&gt;0x1b &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device usb-kbd,id&lt;span class="o"&gt;=&lt;/span&gt;keyboard,bus&lt;span class="o"&gt;=&lt;/span&gt;xhci.0,port&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device usb-tablet,id&lt;span class="o"&gt;=&lt;/span&gt;tablet,bus&lt;span class="o"&gt;=&lt;/span&gt;xhci.0,port&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device virtio-gpu-pci &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device virtio-serial &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device virtserialport,chardev&lt;span class="o"&gt;=&lt;/span&gt;qemu-ga.0,name&lt;span class="o"&gt;=&lt;/span&gt;org.qemu.guest_agent.0 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -hda KylinOS-Server-loongarch64.qcow2 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -m 2G &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -monitor telnet:localhost:5555,server&lt;span class="o"&gt;=&lt;/span&gt;on,wait&lt;span class="o"&gt;=&lt;/span&gt;off &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -net nic,model&lt;span class="o"&gt;=&lt;/span&gt;virtio &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -net user,hostfwd&lt;span class="o"&gt;=&lt;/span&gt;tcp::10000-:22 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -smp &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -vnc 0.0.0.0:1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For UnionTech OS Server, keep the same command structure and replace the ISO and qcow2 filenames. The main point is not the exact distribution name; it is the combination of &lt;code&gt;qemu-system-loongarch64&lt;/code&gt;, &lt;code&gt;-M virt&lt;/code&gt;, &lt;code&gt;QEMU_EFI_7.2.fd&lt;/code&gt;, &lt;code&gt;la464-loongarch-cpu&lt;/code&gt;, VirtIO devices, VNC, QEMU Guest Agent wiring, and SSH port forwarding.&lt;/p&gt;
&lt;p&gt;Connect with a VNC viewer to the host&amp;rsquo;s display &lt;code&gt;:1&lt;/code&gt;, which normally means TCP port &lt;code&gt;5901&lt;/code&gt;. If you add &lt;code&gt;--serial stdio&lt;/code&gt;, QEMU will also print serial output in the terminal, which can be useful when the graphical display is quiet.&lt;/p&gt;
&lt;p&gt;During installation, treat the guest as a Red Hat-like system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use VirtIO-backed disk and network devices.&lt;/li&gt;
&lt;li&gt;Install or keep &lt;code&gt;qemu-guest-agent&lt;/code&gt; if the image-preparation workflow needs guest-agent operations later.&lt;/li&gt;
&lt;li&gt;Ensure SSH is installed and enabled if you want to log in through the forwarded host port.&lt;/li&gt;
&lt;li&gt;Keep the firmware file and qcow2 disk together so the boot command stays reproducible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="boot-an-installed-qcow2"&gt;Boot an installed qcow2&lt;/h3&gt;
&lt;p&gt;After installation, stop the emulation session and boot from the qcow2 disk without &lt;code&gt;-cdrom&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-system-loongarch64 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -M virt &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -bios QEMU_EFI_7.2.fd &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -chardev socket,id&lt;span class="o"&gt;=&lt;/span&gt;qemu-ga.0,path&lt;span class="o"&gt;=&lt;/span&gt;qemu-ga.sock,server&lt;span class="o"&gt;=&lt;/span&gt;on,wait&lt;span class="o"&gt;=&lt;/span&gt;off &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -cpu la464-loongarch-cpu &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device nec-usb-xhci,id&lt;span class="o"&gt;=&lt;/span&gt;xhci,addr&lt;span class="o"&gt;=&lt;/span&gt;0x1b &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device usb-kbd,id&lt;span class="o"&gt;=&lt;/span&gt;keyboard,bus&lt;span class="o"&gt;=&lt;/span&gt;xhci.0,port&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device usb-tablet,id&lt;span class="o"&gt;=&lt;/span&gt;tablet,bus&lt;span class="o"&gt;=&lt;/span&gt;xhci.0,port&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device virtio-gpu-pci &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device virtio-serial &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -device virtserialport,chardev&lt;span class="o"&gt;=&lt;/span&gt;qemu-ga.0,name&lt;span class="o"&gt;=&lt;/span&gt;org.qemu.guest_agent.0 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -hda KylinOS-Server-loongarch64.qcow2 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -m 2G &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -monitor telnet:localhost:5555,server&lt;span class="o"&gt;=&lt;/span&gt;on,wait&lt;span class="o"&gt;=&lt;/span&gt;off &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -net nic,model&lt;span class="o"&gt;=&lt;/span&gt;virtio &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -net user,hostfwd&lt;span class="o"&gt;=&lt;/span&gt;tcp::10000-:22 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -smp &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -vnc 0.0.0.0:1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then SSH through the forwarded port:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh root@localhost -p &lt;span class="m"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the guest has no network, first use VNC or serial output to inspect it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ip addr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl status sshd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl status qemu-guest-agent
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On KylinOS Server or UnionTech OS Server, the package-management commands should feel familiar if you have used RHEL-like systems:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dnf install -y openssh-server qemu-guest-agent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; --now sshd qemu-guest-agent
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="use-qemu-monitor"&gt;Use QEMU Monitor&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;-monitor telnet:localhost:5555,server=on,wait=off&lt;/code&gt; option opens a QEMU Monitor endpoint. Connect to it from the host:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;telnet localhost &lt;span class="m"&gt;5555&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Common monitor commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info block
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;system_powerdown
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;system_reset
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;quit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;system_powerdown&lt;/code&gt; is the polite option if the guest supports ACPI shutdown. &lt;code&gt;quit&lt;/code&gt; stops QEMU directly and can lose guest data, so I only use it when the VM is disposable or already stuck.&lt;/p&gt;
&lt;p&gt;For more monitor commands, see the
.&lt;/p&gt;
&lt;h3 id="confirm-the-architecture"&gt;Confirm the architecture&lt;/h3&gt;
&lt;p&gt;Inside the guest:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uname -m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uname -a
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first command should print:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;loongarch64
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At that point the VM is ready for the usual Red Hat-like server chores: package updates, qemu-guest-agent checks, image clean-up, kernel clean-up, and whatever build or compatibility work needed a LoongArch64 environment in the first place.&lt;/p&gt;
&lt;h3 id="optional-use-utm-on-macos"&gt;Optional: use UTM on macOS&lt;/h3&gt;
&lt;p&gt;If you prefer a GUI, UTM can be configured for this kind of VM as well. I would treat it as a convenience path rather than the main recipe, because the command line is easier to copy, audit, and rerun during image work.&lt;/p&gt;
&lt;p&gt;The important system settings are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Architecture: &lt;code&gt;LoongArch64&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;System: &lt;code&gt;QEMU LoongArch Virtual Machine (default) (virt)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;CPU: &lt;code&gt;Default&lt;/code&gt;, or the closest available LoongArch CPU setting exposed by your UTM build.&lt;/li&gt;
&lt;li&gt;Memory and CPU cores: choose values that match your host. LoongArch64 emulation is slow, and checking &lt;code&gt;Force Multicore&lt;/code&gt; may help sometimes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="UTM system settings for a LoongArch64 QEMU virt machine."
srcset="https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-loongarch64-system_hu_17b1900a04fad702.webp 320w, https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-loongarch64-system_hu_a5924c7faa16bc13.webp 480w, https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-loongarch64-system_hu_cf8130110d3023bd.webp 700w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-loongarch64-system_hu_17b1900a04fad702.webp"
width="700"
height="369"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Add &lt;code&gt;QEMU_EFI_7.2.fd&lt;/code&gt; as a drive with image type &lt;code&gt;BIOS&lt;/code&gt;, and keep it read-only.&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="UTM drive settings showing QEMU_EFI_7.2.fd as a BIOS image."
srcset="https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-bios-drive_hu_4dbabf08fe722a7e.webp 320w, https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-bios-drive_hu_adcceb036749e08e.webp 480w, https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-bios-drive_hu_ea81d84dff8896d8.webp 700w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-bios-drive_hu_4dbabf08fe722a7e.webp"
width="700"
height="368"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;For display, &lt;code&gt;virtio-ramfb&lt;/code&gt; is a useful starting point.&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="UTM display settings using virtio-ramfb."
srcset="https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-virtio-ramfb-display_hu_a5d22c2b3d75e1da.webp 320w, https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-virtio-ramfb-display_hu_2a54e78b93a1066e.webp 480w, https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-virtio-ramfb-display_hu_f4421257622eef82.webp 700w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://www.isarvin.com/blog/emulate-loongarch64-vm-with-qemu/utm-virtio-ramfb-display_hu_a5d22c2b3d75e1da.webp"
width="700"
height="368"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;You still need the same guest-side checks after installation: SSH, &lt;code&gt;qemu-guest-agent&lt;/code&gt; if required, VirtIO networking, and &lt;code&gt;uname -m&lt;/code&gt; showing &lt;code&gt;loongarch64&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>Cleaning Up Unused Linux Kernels</title><link>https://www.isarvin.com/blog/clean-unused-linux-kernels/</link><pubDate>Sun, 07 Jun 2026 00:00:00 +0000</pubDate><guid>https://www.isarvin.com/blog/clean-unused-linux-kernels/</guid><description>&lt;p&gt;After installing another Linux kernel version, the old one usually stays installed. This is not a bug. It is a useful rollback path when the new kernel fails to boot, lacks a driver, or behaves strangely.&lt;/p&gt;
&lt;p&gt;Once the system has been rebooted and confirmed healthy, though, keeping too many old kernels becomes a quiet kind of clutter. &lt;code&gt;/boot&lt;/code&gt; gets noisy, GRUB entries pile up, and small boot partitions can eventually complain.&lt;/p&gt;
&lt;p&gt;This is the checklist I would want before touching a real machine: confirm what is running, remove only the obsolete package set, refresh the boot menu, and leave a fallback kernel unless the host is truly disposable.&lt;/p&gt;
&lt;div class="callout flex px-4 py-3 mb-6 rounded-md border-l-4 bg-red-100 dark:bg-red-900 border-red-500"
data-callout="caution"
data-callout-metadata=""&gt;
&lt;span class="callout-icon pr-3 pt-1 text-red-600 dark:text-red-300"&gt;
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0zM12 15.75h.007v.008H12z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="callout-content dark:text-neutral-300"&gt;
&lt;div class="callout-title font-semibold mb-1"&gt;Caution&lt;/div&gt;
&lt;div class="callout-body"&gt;&lt;p&gt;Run these commands as &lt;code&gt;root&lt;/code&gt;, and do not remove the kernel currently running the system. If the target kernel is shown by &lt;code&gt;uname -r&lt;/code&gt;, reboot into another kernel first.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;details class="print:hidden xl:hidden" open&gt;
&lt;summary&gt;Table of Contents&lt;/summary&gt;
&lt;div class="text-sm"&gt;
&lt;nav id="TableOfContents"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#confirm-the-running-kernel"&gt;Confirm the running kernel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#list-installed-kernel-packages"&gt;List installed kernel packages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#remove-an-old-kernel-on-red-hat-like-systems"&gt;Remove an old kernel on Red Hat-like systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#remove-an-old-kernel-on-debian-like-systems"&gt;Remove an old kernel on Debian-like systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-a-final-sanity-check"&gt;Do a final sanity check&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#limit-future-build-up"&gt;Limit future build-up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#keep-the-habit-conservative"&gt;Keep the habit conservative&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;div class="hb-steps"&gt;
&lt;h3 id="confirm-the-running-kernel"&gt;Confirm the running kernel&lt;/h3&gt;
&lt;p&gt;Start with the one kernel version that must not be removed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uname -r
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If this prints the version you planned to delete, stop here. Reboot, select a newer or otherwise known-good kernel from GRUB, then check again.&lt;/p&gt;
&lt;h3 id="list-installed-kernel-packages"&gt;List installed kernel packages&lt;/h3&gt;
&lt;p&gt;On Red Hat-like systems such as RHEL, CentOS Stream, Rocky Linux, AlmaLinux, or Fedora, query RPM packages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rpm -aq &lt;span class="p"&gt;|&lt;/span&gt; grep kernel
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On Debian-like systems such as Debian or Ubuntu, query installed &lt;code&gt;linux-*&lt;/code&gt; packages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dpkg -l &lt;span class="p"&gt;|&lt;/span&gt; grep -Ei &lt;span class="s1"&gt;&amp;#39;linux-image|linux-headers|linux-modules&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -Ei &lt;span class="s1"&gt;&amp;#39;^ii&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{ print $2 }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What you are looking for is the version suffix shared by the packages that belong to the old kernel. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;6.8.5-301.fc40.x86_64&lt;/code&gt; on a Fedora-style system.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;5.4.0-165-generic&lt;/code&gt; on an Ubuntu-style system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I usually copy the suffix into a variable and print the matched packages before removing anything. It adds one boring step, and boring is excellent here.&lt;/p&gt;
&lt;h3 id="remove-an-old-kernel-on-red-hat-like-systems"&gt;Remove an old kernel on Red Hat-like systems&lt;/h3&gt;
&lt;p&gt;Use the package manager for the package removal itself, then clean the leftover modules directory if it remains.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;6.8.5-301.fc40.x86_64&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;uname -r&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Refusing to remove the running kernel: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rpm -aq &lt;span class="p"&gt;|&lt;/span&gt; grep kernel &lt;span class="p"&gt;|&lt;/span&gt; grep -F &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rpm -aq &lt;span class="p"&gt;|&lt;/span&gt; grep kernel &lt;span class="p"&gt;|&lt;/span&gt; grep -F &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; xargs -r dnf remove -y
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -rf &lt;span class="s2"&gt;&amp;#34;/lib/modules/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dnf autoremove
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If your system only has &lt;code&gt;yum&lt;/code&gt;, use it for the final clean-up step:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yum autoremove
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then refresh GRUB configuration files under &lt;code&gt;/boot&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ls -l /boot/ &lt;span class="p"&gt;|&lt;/span&gt; grep rescue
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find /boot/ -name &lt;span class="s1"&gt;&amp;#39;grub.cfg&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; -r file&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Updating &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; grub2-mkconfig -o &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Some systems also keep Boot Loader Specification entries under &lt;code&gt;/boot/loader/entries/&lt;/code&gt;. If the removed kernel still has a stale &lt;code&gt;.conf&lt;/code&gt; file there, remove that specific file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ls -l /boot/loader/entries/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Be picky here. Delete only entries that clearly match the kernel version already removed.&lt;/p&gt;
&lt;h3 id="remove-an-old-kernel-on-debian-like-systems"&gt;Remove an old kernel on Debian-like systems&lt;/h3&gt;
&lt;p&gt;Again, set the version suffix first. &lt;code&gt;apt purge&lt;/code&gt; removes the matching kernel packages and their package configuration; the separate &lt;code&gt;apt autoremove&lt;/code&gt; then lets APT clean up dependencies that are no longer needed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;5.4.0-165-generic&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;uname -r&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Refusing to remove the running kernel: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dpkg -l &lt;span class="p"&gt;|&lt;/span&gt; grep -Ei &lt;span class="s1"&gt;&amp;#39;linux-image|linux-headers|linux-modules&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -Ei &lt;span class="s1"&gt;&amp;#39;^ii&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{ print $2 }&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -E &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;$&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dpkg -l &lt;span class="p"&gt;|&lt;/span&gt; grep -Ei &lt;span class="s1"&gt;&amp;#39;linux-image|linux-headers|linux-modules&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -Ei &lt;span class="s1"&gt;&amp;#39;^ii&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{ print $2 }&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -E &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;$&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; xargs -r apt purge -y
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -rf &lt;span class="s2"&gt;&amp;#34;/lib/modules/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deprecated_kernel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt autoremove
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;update-grub
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On older Ubuntu releases, this pattern is especially handy after a manual kernel update: headers, image packages, modules, and modules-extra packages may all share the same version suffix.&lt;/p&gt;
&lt;h3 id="do-a-final-sanity-check"&gt;Do a final sanity check&lt;/h3&gt;
&lt;p&gt;After clean-up, inspect what remains:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uname -r
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Red Hat-like&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rpm -aq &lt;span class="p"&gt;|&lt;/span&gt; grep kernel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Debian-like&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dpkg -l &lt;span class="p"&gt;|&lt;/span&gt; grep -Ei &lt;span class="s1"&gt;&amp;#39;linux-image|linux-headers|linux-modules&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -Ei &lt;span class="s1"&gt;&amp;#39;^ii&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{ print $2 }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result I want is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The running kernel is still installed.&lt;/li&gt;
&lt;li&gt;At least one known-good fallback kernel remains, unless this is a tightly controlled image-building environment.&lt;/li&gt;
&lt;li&gt;GRUB no longer shows entries for kernel packages that were already removed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/lib/modules/&lt;/code&gt; does not contain a directory for the removed kernel.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="limit-future-build-up"&gt;Limit future build-up&lt;/h3&gt;
&lt;p&gt;On Fedora/RHEL-like systems, DNF normally keeps only a small number of install-only packages, including kernels. If the host keeps too many kernels, check the configured limit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -E &lt;span class="s1"&gt;&amp;#39;^installonly_limit=&amp;#39;&lt;/span&gt; /etc/dnf/dnf.conf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For most ordinary hosts, &lt;code&gt;installonly_limit=3&lt;/code&gt; is a reasonable balance: current kernel, one fallback, and one extra slot during updates. I would avoid setting it below &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On Debian/Ubuntu, old automatically installed kernels are often handled by:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt autoremove
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If a kernel package was manually marked as installed, APT may keep it. In that case, inspect the package names first and only then adjust the manual/auto mark or purge the exact old packages.&lt;/p&gt;
&lt;h3 id="keep-the-habit-conservative"&gt;Keep the habit conservative&lt;/h3&gt;
&lt;p&gt;Kernel clean-up is not about being tidy for its own sake. It is about keeping the boot path understandable while preserving a rollback option.&lt;/p&gt;
&lt;p&gt;For normal servers, I prefer keeping the current kernel and one previous known-good kernel. For disposable build hosts or image-making VMs, I may be more aggressive, but only after the new kernel has booted and the important workload has actually run.&lt;/p&gt;
&lt;/div&gt;</description></item></channel></rss>