Posted to Infrastructure on

KVM, libvirt, and the First Fedora Server VM

Turn the Fedora host into a small platform: validate KVM and libvirt, create a modest Fedora Server guest, keep virtual networking isolated, and make the VM recoverable.

#fedora#linux#local-server#kvm#libvirt#qemu#virtualization

Isolation Turns One Machine into a Small Platform

A local server becomes much more useful once workloads can be separated from the physical host. Fedora's KVM/QEMU/libvirt stack provides that isolation without introducing a separate proprietary hypervisor appliance.

This guide assumes you completed Hardened Local Administration with OpenSSH and firewalld and can administer the physical Fedora host through the hardened SSH path or a local console.

The first VM stays intentionally simple:

Fedora physical host
      │
      ├── KVM/QEMU/libvirt
      │      │
      │      └── default NAT network
      │             │
      │             └── Fedora Server VM
      │
      └── physical LAN

The guest gets outbound network access through libvirt NAT and remains reachable from the host. Later, Tailscale gives the admin workstation a direct private path to the VM without exposing the libvirt subnet to the LAN.

Example values: Hostnames, usernames, addresses, interface names, device paths, and other identifiers are illustrative unless explicitly described as software defaults. Discover the values on your own systems before running commands.

Verify the Virtualization Foundation

Check the CPU, kernel modules, and KVM device together:

lscpu | grep -i virtualization
lsmod | grep kvm
ls -l /dev/kvm

KVM is a Linux-kernel virtualization interface controlled through /dev/kvm; the kernel documentation is the authority for that API and its capabilities.1

If /dev/kvm is absent, return to UEFI/BIOS and verify hardware virtualization before troubleshooting higher layers.

Install the Stack, Not a Legacy Service Name

Install Fedora's virtualization group:

sudo dnf install @virtualization

If you plan to use UEFI firmware for the guest, install OVMF support too:

sudo dnf install edk2-ovmf

Depending on the guest firmware path, useful components include packages such as:

qemu-kvm
libvirt-daemon-kvm
libvirt-daemon-config-network
virt-install
virt-manager
edk2-ovmf

A missing libvirtd.service is not evidence of a failed install. Modern libvirt can run through modular daemons.2

Check the tools you will actually use:

virsh --version
qemu-system-x86_64 --version

The System Connection Is the Test That Matters

Older tutorials often tell readers to look only for libvirtd.service. Modern libvirt supports modular daemons such as virtqemud, with systemd socket activation on systems that use systemd.2:1

Check the socket when useful, but test the real control plane:

systemctl status virtqemud.socket --no-pager
sudo virsh -c qemu:///system list --all

If qemu:///system works, the system QEMU/libvirt control plane is functioning even if the service layout differs from an older tutorial.

Use qemu:///system for system-managed VMs. A per-user qemu:///session environment has different permissions and lifecycle semantics.2:2

Keep the Default NAT Network Boring

List the networks first:

sudo virsh -c qemu:///system net-list --all

A standard libvirt installation commonly provides a network named default. Libvirt's own documentation shows that network as NAT behind virbr0, with an example IPv4 range of 192.168.122.0/24.3 Those are documented defaults, not values your installation is guaranteed to use.

If default exists but is inactive, start it:

sudo virsh -c qemu:///system net-start default

Whether or not you had to start it, set it to autostart with libvirt and inspect its real definition instead of assuming defaults:

sudo virsh -c qemu:///system net-autostart default
sudo virsh -c qemu:///system net-dumpxml default

A common default looks like:

host bridge:     virbr0
host address:    192.168.122.1/24
guest DHCP:      192.168.122.2-254
forward mode:    NAT

NAT gives guests outbound connectivity through the host. Machines on the physical LAN cannot normally open inbound connections to guests unless you add routing, port forwarding, or a different network mode.3:1

Check the bridge:

ip -br address show virbr0

Before a guest is attached, virbr0 can show as DOWN here, and ip link show virbr0 can report NO-CARRIER, even when virsh reports the network as active. Fedora community reports are useful context for that confusing state, but virsh remains the authoritative view of the network object.4

The Physical LAN and Virtual Network Are Different Trust Boundaries

Inspect active firewalld zones:

sudo firewall-cmd --get-active-zones

A clean host may show:

local-server
  interfaces: eno2

libvirt
  interfaces: virbr0

Do not move virbr0 into the physical local-server zone just to make the output look simpler.

The physical LAN and libvirt NAT network are separate routing and trust boundaries. Libvirt manages firewall behavior for its virtual networks, and Fedora has continued to evolve that backend, including a change that moves libvirt's virtual-network firewall handling to nftables. Inspect libvirt's network objects rather than hard-coding assumptions about the rules behind them.5

Storage Pools Are Configuration, Not the Files Themselves

List existing pools:

sudo virsh -c qemu:///system pool-list --all

A storage pool is a libvirt-managed source of storage from which VM volumes can be created or discovered.6

If a deliberate default directory pool already exists, keep it. If you need one, create the directory with the expected SELinux context and register it:

sudo mkdir -p /var/lib/libvirt/images
sudo restorecon -Rv /var/lib/libvirt/images
sudo virsh -c qemu:///system pool-define-as default dir --target /var/lib/libvirt/images
sudo virsh -c qemu:///system pool-start default
sudo virsh -c qemu:///system pool-autostart default
sudo virsh -c qemu:///system pool-list --all

Libvirt distinguishes the pool definition from the underlying files. That distinction becomes important during cleanup.6:1

Verify Installation Media Before the VM Ever Sees It

Download the current Fedora Server installer from Fedora's official download page and verify its published checksum or signature before using it.7

If virt-manager temporarily registers a directory such as ~/Downloads as a storage pool so it can access the ISO, that is not inherently a problem. Inspect it before unregistering it.

Never use destructive storage commands against a pool until you understand what backend and path it represents.

Start with One Modest Guest

For a first VM, virt-manager is useful because it exposes the same libvirt objects you can later inspect with virsh.

virt-manager

A modest first guest might be:

Name:       server-vm-01
OS:         Fedora Server
vCPU:       2
RAM:        4 GiB
Disk:       40 GiB
Firmware:   UEFI when appropriate
Network:    default / NAT

Those are capacity examples, not universal recommendations. Size the guest for the host and workload, and create a non-root administrator inside it.

Names Become Infrastructure Once You Have More Than One Guest

Inside the VM, inspect the current hostname first:

hostnamectl

If it is not the name you want, set the guest hostname explicitly:

sudo hostnamectl set-hostname server-vm-01

The name is arbitrary. What matters is choosing a scheme that remains understandable when one guest becomes several.

Prove the Guest Has the Network You Think It Has

Inside the VM:

ip -br address
ip route

A default NAT guest may resemble:

enp1s0     UP     192.168.122.x/24
default via 192.168.122.1

Inspect the real values. From the host, check libvirt DHCP leases and then test the address you found:

sudo virsh -c qemu:///system net-dhcp-leases default
ping -c 3 <GUEST_LIBVIRT_IP>

Libvirt's domain and network documentation explains that guest NICs attached to a virtual network appear through host-side tap devices such as vnetN, while the virtual network handles DHCP and NAT around them.8

Treat the VM Like a Separate Server

Inside the guest, enable SSH:

sudo systemctl enable --now sshd

Create a separate SSH identity on the admin workstation:

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/server_vm_01_ed25519 -C "admin-workstation-to-server-vm-01"

Install only the public key in the guest's ~/.ssh/authorized_keys. The workstation has no direct route to the guest yet, so paste the key through the virt-manager console or use ssh-copy-id over the optional ProxyJump path described below. Once key authentication works, add a guest sshd drop-in:

sudo nano /etc/ssh/sshd_config.d/10-server-vm-01.conf
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
AllowUsers admin

Replace admin in AllowUsers with the guest's real administrator account. Keep the virt-manager console available, then validate and restart only if validation succeeds:

sudo sshd -t && sudo systemctl restart sshd

The OpenSSH directives are the same ones used on the physical host and defined by upstream sshd_config(5).9

ProxyJump Is an Optional Fallback, Not the Final Topology

The admin workstation normally has no direct route to the host's private libvirt subnet. If you need workstation SSH to the guest before Tailscale is in place, an optional temporary path is:

admin workstation
      │
      ▼
physical Fedora host
      │
      ▼
Fedora Server VM

If the physical host should act as an SSH jump host, allow TCP forwarding in its sshd policy:

AllowTcpForwarding yes

Then the workstation can use:

Host server-vm-01-lan
    HostName <GUEST_LIBVIRT_IP>
    User admin
    IdentityFile ~/.ssh/server_vm_01_ed25519
    IdentitiesOnly yes
    ProxyJump local-server-lan

The VM private key still stays on the admin workstation. The physical host forwards the TCP stream; it does not need to own that private key.

That explains a state that looks strange at first: ssh server-vm-01-lan from the workstation can work while ssh admin@<GUEST_LIBVIRT_IP> from the host fails with Permission denied (publickey). The host does not have the workstation's guest key, and that is the point.

Diagnose ProxyJump by Layer, Not by Guessing

A failure can occur even when ordinary host-to-guest networking works. Work through the layers:

  1. Can the host reach the guest address?
  2. Is guest TCP 22 listening?
  3. Does effective sshd policy permit forwarding?
  4. Is SELinux denying the forwarding process?

Check effective sshd settings and recent AVCs:

sudo sshd -T | grep -E 'allowtcpforwarding|disableforwarding|permitopen'
sudo ausearch -m AVC -ts recent
sudo journalctl -t setroubleshoot --since "10 minutes ago"

Fedora's SELinux troubleshooting guidance recommends diagnosing AVC denials instead of disabling SELinux to make a workload function.10

If a narrow local policy is required for a confirmed policy gap, document it, keep it removable, and revisit it after policy updates.

Grow Storage Only After You Know the Guest Layout

Fedora Server installations can leave free extents in an LVM volume group. Inspect first:

lsblk
sudo pvs
sudo vgs
sudo lvs

Only extend the root logical volume after the commands above confirm its actual path. The path below is an example; using the wrong logical volume can change the wrong filesystem:

sudo lvextend -l +100%FREE -r /dev/fedora/root

Confirm that the root filesystem actually grew:

df -h /

Autostart Makes the VM Part of the Host Baseline

On the host:

sudo virsh -c qemu:///system autostart server-vm-01
sudo virsh -c qemu:///system dominfo server-vm-01 | grep -i autostart

Remember the physical host's own boot boundary: if its root disk requires manual LUKS unlock, no VM can autostart until Fedora finishes booting.

Clean Up Temporary Pools Without Deleting Data by Accident

List and inspect a temporary pool before touching it:

sudo virsh -c qemu:///system pool-list --all
sudo virsh -c qemu:///system pool-info Downloads
sudo virsh -c qemu:///system vol-list Downloads
sudo virsh -c qemu:///system pool-dumpxml Downloads

If the pool only registers the expected user directory and you want libvirt to stop managing it, the commands below stop and unregister the pool definition. Confirm the backend path first, and do not substitute a destructive storage operation that deletes underlying data:

sudo virsh -c qemu:///system pool-destroy Downloads
sudo virsh -c qemu:///system pool-undefine Downloads

A pool definition and the files it exposes are related, but they are not the same thing.6:2

Validate the Whole Virtualization Layer

On the host:

sudo virsh -c qemu:///system list --all
sudo virsh -c qemu:///system net-list --all
sudo virsh -c qemu:///system pool-list --all
ip -br address
sudo firewall-cmd --get-active-zones
getenforce
sudo virsh -c qemu:///system dominfo server-vm-01 | grep -i autostart
systemctl --failed

The target state is:

VM:              running + persistent + autostart
network:         intended NAT network active + autostart
bridge:          libvirt-managed
storage pool:    deliberate pool active + autostart
firewalld:       physical NIC and virtual network separated
SELinux:         enforcing
failed units:    0 unexpected failures

Inside the guest:

hostnamectl
ip -br address
systemctl is-active sshd
systemctl --failed

The VM is now a stable internal workload. The next guide gives the workstation, host, and guest direct private identities so routine administration no longer depends on a jump through the physical host.

Footnotes

Scroll to article content with footnote reference 3

Libvirt's Network XML format publishes the canonical default NAT example with virbr0, 192.168.122.1, and a DHCP range in 192.168.122.0/24. These are libvirt defaults/examples, not values a reader should assume without checking.

Scroll to article content with footnote reference 3 Scroll to article content with footnote reference 3
Scroll to article content with footnote reference 4

Fedora community threads such as "How to assign MAC address to a bridge interface?" show why virbr0 reporting NO-CARRIER or DOWN can confuse users. Use such reports as operational context; use libvirt's network object and documentation as the authority.

Scroll to article content with footnote reference 4
Scroll to article content with footnote reference 5

Fedora's Libvirt Virtual Network NFTables change discussion documents the distribution-level evolution of libvirt's virtual-network firewall backend. It is a useful reminder not to hard-code assumptions about the implementation behind libvirt-managed networking.

Scroll to article content with footnote reference 5
Scroll to article content with footnote reference 8

Libvirt's Domain XML and Network XML references describe network-backed guest interfaces, generated vnetN devices, DHCP, bridges, and the default NAT network.

Scroll to article content with footnote reference 8
Scroll to article content with footnote reference 10

Fedora's SELinux troubleshooting guidance explains the audit-first workflow for diagnosing policy denials instead of disabling SELinux globally.

Scroll to article content with footnote reference 10

Down the rabbit hole

Fedora Local Server Guide

Posted to Infrastructure on

Private Remote Administration with Tailscale

Posted to Infrastructure on
Browse all posts

Map the system you need

Describe the product, workflow, integration, or operation you are building—and where the current system loses power. Include the people, tools, boundaries, constraints, and outcome that matter.

Every message is reviewed. Responses are limited.

Press Escape to close preview popover.