Cockpit turns a KVM host into something close to an ESXi or Proxmox console: a browser tab where guests start, stop, get more RAM, and open a console. The piece that does the virtualization work is a separate package called cockpit-machines, and it does not come with Cockpit itself.
This guide walks the whole path: installing the machines plugin, signing in, escalating to administrative access, creating a virtual machine from the Create VM dialog, editing CPU, memory and boot order on an existing guest, and getting a graphical console on a VM that was built from the command line without one. The Ubuntu install and every UI behaviour described below were run on a live host with libvirt 12.0.0, QEMU 10.2.1, cockpit 360 and cockpit-machines 351 on Ubuntu 26.04 LTS, in August 2026. Install commands for the other distributions come from the Cockpit project’s own installation matrix.
Install the machines plugin on the KVM host
This assumes libvirt and QEMU are already running. If they are not, start with the KVM and QEMU setup for Ubuntu or the Rocky, AlmaLinux and RHEL equivalent, then come back here.
On Ubuntu 26.04 both packages are in universe, so a plain install picks them up:
sudo apt update
sudo apt install -y cockpit cockpit-machines
Ubuntu 24.04 is a different story. Its universe pocket still carries cockpit 314 and cockpit-machines 310, both released in March 2024, so a plain install there lands you two years behind. Backports is enabled by default on Ubuntu and carries a far more recent pair, which is also what the Cockpit project recommends for Debian and Ubuntu:
. /etc/os-release
sudo apt install -y -t ${VERSION_CODENAME}-backports cockpit cockpit-machines
Debian is in better shape than Ubuntu 24.04, with cockpit 337 and cockpit-machines 332 in Debian 13 stable, but backports still sits about 15 months ahead of that. The repository has to be defined before the same command will resolve:
. /etc/os-release
echo "deb http://deb.debian.org/debian ${VERSION_CODENAME}-backports main" | sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t ${VERSION_CODENAME}-backports cockpit cockpit-machines
Debian 13 will answer the next apt update with a notice suggesting apt modernize-sources, because that one-line format is the older style. The repository still works; convert it later if the notice bothers you.
On the RHEL family nothing extra is needed. cockpit lives in BaseOS and cockpit-machines in AppStream, both enabled by default on Rocky Linux, AlmaLinux and RHEL:
sudo dnf install -y cockpit cockpit-machines
Fedora Server already ships Cockpit, so on that image only the machines package is genuinely new. openSUSE and Arch each carry both:
# openSUSE Leap and Tumbleweed
sudo zypper --non-interactive install cockpit cockpit-machines
# Arch, Manjaro, EndeavourOS
sudo pacman -S cockpit cockpit-machines
Cockpit is socket activated. The web service is not resident, it spawns when someone connects, so what you enable is the socket rather than a daemon:
sudo systemctl enable --now cockpit.socket
Then open port 9090. firewalld ships a named service definition for it, while ufw wants the port number:
# RHEL family, Fedora, openSUSE
sudo firewall-cmd --permanent --add-service=cockpit
sudo firewall-cmd --reload
# Ubuntu and Debian
sudo ufw allow 9090/tcp
Both of those open a root-capable web console to whatever the host is reachable from, which on a machine with a public address means the internet. Scope it to the network you administer from. Adding a narrow rule does not remove the wide one, so the old rule has to go explicitly. Swap 10.0.0.0/8 for the range your workstation actually sits in, because pasting it verbatim from outside that range locks you out of the console:
# RHEL family, Fedora, openSUSE
sudo firewall-cmd --permanent --remove-service=cockpit
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="cockpit" accept'
sudo firewall-cmd --reload
# Ubuntu and Debian
sudo ufw delete allow 9090/tcp
sudo ufw allow from 10.0.0.0/8 to any port 9090 proto tcp
Confirm the socket is listening before you touch a browser:
systemctl is-active cockpit.socket
sudo ss -tlnp | grep 9090
An active socket bound on all interfaces looks like this. The -p flag needs root to fill the process column, and what holds the socket is systemd until the first connection hands it to cockpit-tls:
active
LISTEN 0 4096 *:9090 *:* users:(("cockpit-tls",pid=37615,fd=3),("systemd",pid=1,fd=211))
The two package version numbers do not track each other, so do not read anything into the gap. Cockpit and cockpit-machines are separate projects with independent release sequences: Ubuntu 26.04 pairs cockpit 360 with cockpit-machines 351, and those two releases are two weeks apart by date, not nine versions behind. If a page you expect is missing, check the package is installed rather than assuming it is stale.

Installing a Cockpit page while the console is running is not something upstream encourages, so log out and back in rather than trusting a refresh to pick it up.
Sign in and switch on administrative access
Point a browser at port 9090 on the host over HTTPS. Cockpit generates a self-signed certificate into /etc/cockpit/ws-certs.d/0-self-signed.cert the first time the socket fires, so the browser throws a warning on the way in.
https://10.0.1.50:9090
Replacing that certificate has rules worth knowing before you try. Cockpit uses the last file in that directory with a .cert or .crt extension in alphabetical order, the private key must be a same-named .key file, and it must not be encrypted. A file called mycert.cert wins only because it sorts after 0-self-signed.cert. Check what Cockpit actually picked:
# Debian and Ubuntu
sudo /usr/lib/cockpit/cockpit-certificate-ensure --check
# RHEL family
sudo /usr/libexec/cockpit-certificate-ensure --check
Log in with a normal system account. There is no separate Cockpit user database, the credentials are the host’s own, and the login page names both the distribution and the server so you know which machine you reached.

A first login lands in limited access mode, flagged by a yellow banner on the Overview page and a Limited access button in the header. Read-only pages work in that state. Write actions do not.
Click the header button to escalate, and re-enter your password in the dialog that opens. On hosts where sudo is passwordless, which is the default on most cloud images, the dialog skips straight to confirming the switch. The button shows for any session that is not already root, including accounts with no sudo rights at all, so a user outside the wheel group on the RHEL family or sudo on Debian and Ubuntu only finds out when the escalation fails. Cockpit remembers the choice in browser storage rather than against the account, so the same user in a different browser or a private window starts back in limited access.
Why the Virtual machines page comes up empty
A user who is not in the libvirt group sees No VM is running or defined on this host even when the host has guests running right now. The page reports an empty inventory rather than a permission error, which sends people off to debug libvirt when libvirt is fine.
This was reproduced on a host running five guests. A freshly created account with no supplementary groups signed into Cockpit, opened Virtual machines, and got the empty-state message along with zero storage pools and zero networks. One group change fixed it:
sudo usermod -aG libvirt kvmops
Log out of Cockpit and sign back in so the session picks up the new group. The same account then lists every guest, with working Run and Shut down buttons, while still in limited access mode. Escalating to administrative access reaches the same place by a different route, but group membership is what lets an operator drive VMs without handing them the whole host.

The table is live. CPU and memory bars poll while you watch, the state pill tracks each domain, and the action button on each row switches between Run and Shut down depending on that state. Two buttons sit above it: Create VM, and Import VM for wiring an existing disk image into a new domain.
Create a virtual machine
Create VM opens a single dialog rather than a multi-page wizard. Name, connection, installation source, disk and memory all live on one screen, and the operating system is detected from whatever source you point at.

Installation type drives the rest of the form. Five options show on a System connection, and they behave quite differently. A User session drops Network boot, and the two automated sources appear only when Cockpit finds the matching virt-install capability on the host:
| Installation type | What it expects | Best for |
|---|---|---|
| Download an OS | Nothing, Cockpit fetches the install tree | A quick guest with no local media |
| Cloud base image | A qcow2 cloud image path on the host | Fast, repeatable guests seeded by cloud-init |
| Local install media | An ISO or install tree path on the host | Windows, or any OS you already downloaded |
| URL | An HTTP path to an ISO or install tree | Netinstall images from a mirror |
| Network boot (PXE) | A reachable PXE server | Fleet provisioning against existing infrastructure |
The Connection radio deserves a moment of thought because it is easy to get wrong and annoying to undo. System creates the guest on qemu:///system, which is the connection virsh uses by default and the one every other tool on the host expects. User session puts it on qemu:///session, owned by your login, invisible to sudo virsh list, and with no access to the default NAT network. Pick System unless you have a specific reason not to.
Why the Automation tab is greyed out
The Automation tab arms only when the chosen source can actually drive an unattended install. A cloud base image arms it straight away. The download and install-tree paths stay dead until an operating system is selected: on the test host the tab reported aria-disabled=true with Installation type set to Download an OS and no OS picked, then flipped to false the moment Fedora Linux 42 was chosen from the list. Switching that same selection to Local install media pointing at an ISO disabled it again.
What the tab then contains is not the same from one source to the next, which is the part that catches people out. Measured on the same host across all three paths:
| Source | Fields on the Automation tab | Mechanism |
|---|---|---|
| Cloud base image | Root password, User login, User password, SSH keys | cloud-init user data |
| Download an OS | Profile (Server or Workstation), Root password, User login, User password, Boot arguments | virt-install --unattended |
| URL or local path to an install tree | Boot arguments only | Kernel arguments passed to the installer |
The SSH keys field exists on the cloud-image path and nowhere else. If injecting a key at build time matters to you, that is the path to take, and it works the same way as the cloud-init and virt-install walkthrough covers in depth. One trap sits inside it: the key is only written when User login is also filled in, because it lands in that user’s cloud-init entry. Add a key on its own and the guest boots with no key and no warning.

Two buttons finish the job, and they interact with that tab. Create and run defines the domain and boots it. Create and edit defines it and drops you on the VM page with the guest still shut off, which is what you want whenever the machine needs an extra disk, a different NIC, or a virtio-win CD-ROM attached before it boots for the first time.
The catch is that unattended credentials only get applied on a first boot that Cockpit performs itself. Filling in a password on the Download an OS path immediately disables Create and edit, which was reproducible on the test host: the button read aria-disabled=false before typing a root password and true straight after. On the cloud image path the button stays clickable and only a hover tooltip warns you, so the credentials get dropped unless you notice it. Use Create and run whenever the Automation tab has anything in it, and do the hardware edits afterwards.
Day to day operations on an existing guest
Clicking a name on the list opens that guest’s page: an Overview card on the left, Usage counters below it, the console on the right, and cards for Disks, Network interfaces, Host devices, Snapshots and Shared directories stacked underneath.
The primary button toggles between Run and Shut down. Everything harsher hides behind the kebab menu beside it, and the menu changes with state. A running guest offers Pause, Shut down, Force shut down, Send non-maskable interrupt, Reboot, Force reboot, Migrate, Edit description, Delete, and Add TPM when the host supports one and the guest does not already have it. A shut-off guest trades most of those for Clone and Rename, which is why they are easy to miss: neither is visible while the VM is running.
Reboot is worth understanding before you rely on it. Cockpit calls libvirt’s reboot with default flags, and the QEMU driver tries the guest agent first, then falls back to ACPI. On a guest with neither qemu-guest-agent installed nor ACPI handling, nothing happens at all and Force reboot is the only thing that lands.
What you can change while the guest is running
Less than the edit links suggest. Memory and CPU both open their dialogs on a live guest, but only part of each applies.
In the memory dialog, Current allocation is editable and takes effect immediately through the balloon driver. Maximum allocation is hard-disabled with the helper text “Only editable when the guest is shut off”, because raising the ceiling is memory hotplug and libvirt will not do it on the fly:

The CPU dialog is blunter about it. Touch vCPU count, sockets, cores or threads on a running guest and the dialog raises a banner reading “Changes will take effect after shutting down the VM”. Cockpit writes the persistent domain config and leaves the live guest alone, so the numbers you set are real but deferred. That same dialog exposes CPU Mode, with host-model, host-passthrough and the full QEMU model list, which is more CPU control than most people expect from a web UI.
Boot order follows the same rule and does not pretend otherwise. Its edit link is greyed out while the domain runs, because boot order is read once at power-on. Shut the VM down and the link becomes active:

Every bootable device the domain owns is listed with its backing file or MAC address, a checkbox to include it in the boot chain, and arrows to move it. Ticking the network entry and pushing it to the top is the two-click version of a PXE rebuild.
The Autostart toggle in the Overview card is the UI form of virsh autostart, and flipping it on is what brings a guest back after a host reboot. Disks and network interfaces get their own Add buttons further down, and the Add disk dialog hides a Cache selector behind Show additional options offering default, none, writethrough, writeback, directsync and unsafe, along with a bus choice of sata, scsi, usb or virtio. Attaching a second qcow2 from an existing pool, or bridging a guest onto the LAN with a netplan bridge, does not need a trip to the command line.
Get a graphical console on a VM that has none
Guests built by virt-install with --graphics none, which is the norm for scripted and cloud-image builds, carry no VGA device at all. Switch the console to Graphical on one of those and Cockpit says Graphical console support not enabled and offers an Add VNC button. Serial still works on a guest that virt-install built, because that adds a serial device even with graphics off. A domain imported around an existing disk image may have neither, and in that case the serial side offers its own Add serial console button. The Graphical and Serial labels are a toggle pair in the console header rather than page tabs, and a guest with more than one serial device gets a dropdown there instead.
Add VNC writes the device straight into the domain XML with no confirmation dialog, but a running guest cannot grow a display adapter mid-flight. The VM immediately picks up a Changes pending badge next to its name, and the console pane swaps its message for an instruction to restart:

The Shutdown and restart button in that pane does a graceful stop and start, and offers a forced variant for guests that ignore ACPI. On a 3 GiB Ubuntu guest the round trip took under a minute, and the console attached on its own once the domain was back:

No VNC client, no port forward, no separate viewer package. The console header gains a Send key menu for combinations the browser would otherwise swallow, an Expand control, and Detach to pop the console into its own tab, which is the sane way to sit through an OS installer.
Worth adding the display before you need it. A guest that will only ever be reached over SSH does not need one, but a guest you might have to rescue does, and adding it during an outage costs you a reboot at exactly the wrong moment.
Where Cockpit stops and virsh takes over
Cockpit covers the operations you repeat daily, and it reaches further than its reputation suggests: clone, snapshot, host device passthrough, disk cache mode and CPU model are all in there. It still is not a full libvirt front end, and knowing the boundary saves time hunting for controls that were never built.
There is no domain XML editor, so anything expressed only in XML stays a command-line job. CPU pinning and NUMA placement are absent, as is per-disk IO mode, and individual CPU feature flags cannot be toggled even though the model can be chosen. Growing a disk is another gap: the Disks card reports sizes but resizing means qemu-img plus virt-resize on the host. Clone is there, but it shells out to virt-clone, so on a host without virt-install the menu entry renders greyed out with a tooltip saying so rather than disappearing. That only comes up on the RHEL family, since virt-install is a hard dependency of the package on Debian and Ubuntu.
Building guests in bulk is also the wrong job for a browser. A dialog that takes six fields per VM does not compete with virt-builder producing a customized image in about seventy seconds, and it never will. The same goes for anything you would rather express as a file, such as a repeatable directory storage pool definition.
What Cockpit is genuinely good at is the middle ground: giving a colleague who does not live in virsh a safe way to reboot a guest, add memory, or watch an installer, without an SSH key on the hypervisor. Pair it with the virsh command reference for everything the page cannot reach, and put those operators in the libvirt group rather than in sudoers.