Known limitations
This page states what SimpleVirt 1.0 does not do. It exists so you can plan a deployment around the boundaries rather than discover them during an incident.
Each item names the limitation, why it is there, and what to do instead. Where SimpleVirt refuses an operation rather than performing it badly, that refusal is deliberate: a product that reports protection it cannot deliver is worse than one that says no.
1. SimpleVirt does not back up virtual machine data
SimpleVirt provides no backup of VM data in this release. There is no scheduled backup, no backup catalogue, no restore of a virtual machine, and no integration with a backup product. You must supply your own backup solution for the data inside your guests.
Snapshots are not a substitute for a backup. A snapshot is a short-term undo for a change you are about to make — a patch, an upgrade, a configuration edit — and nothing more. Specifically:
- They are crash-consistent unless the guest is quiesced first. On SimpleVirt HCI (Ceph/RBD) a snapshot of a running VM is application-consistent when the SimpleVirt Guest Tools agent freezes the guest's filesystems first — freeze, then snapshot, then a guaranteed thaw — and falls back to crash-consistent when no agent answers. On an NFS (qcow2) datastore SimpleVirt does not quiesce the guest at all: the snapshot is always crash-consistent, capturing the guest's filesystem and any database inside it exactly as if you had pulled the power cable, so they recover the way they would from a power cut. §6 states which tier gives you which, and the VM's Guest Tools view reports the agent's state honestly.
- They live on the same datastore as the VM. A snapshot is stored inside the VM's own disk image. It does not protect you against the loss of that datastore, that storage array, or that site.
- They are supported on both NFS and SimpleVirt HCI (Ceph/RBD) storage. An NFS snapshot is an internal qcow2 snapshot; a SimpleVirt HCI snapshot is a native RBD snapshot taken across all of the VM's images. Neither is refused — but neither is held outside the VM's own storage, so the consistency limit above and the durability limit below apply to both tiers (see §6).
- They do not survive a migration or an HA recovery. Moving a VM to another host redefines it on the destination; a storage move lands its disk in a new volume; an HA restart reconstructs the VM on a surviving host. In all three cases every rollback point is gone. The Migrate wizard warns you on its final screen when the VM has snapshots.
What to do. Run a backup product that works inside the guest (an in-guest agent) or against your storage tier, keep its copies off the datastore holding the VM, and test a restore. See Backup responsibility for how the pieces SimpleVirt does provide fit into a recovery plan.
2. High availability requires NFSv4.1 or SimpleVirt HCI storage
HA is supported on two storage tiers only: NFSv4.1 (or later) and SimpleVirt HCI (Ceph/RBD). SimpleVirt refuses to arm HA on a cluster whose VM storage is NFSv3, and refuses just as firmly when a datastore's lock tier cannot be determined at all.
The reason is that a failover has to finish, not merely start. SimpleVirt takes a cross-host lock on every writable disk so that two hosts can never run the same VM at once. On NFSv4.1 that lock is leased: when a host dies, the NFS server expires its lease on its own and the disk becomes available to the survivor. Below 4.1 the lock has no lease — a host that dies hard leaves its lock held on the server forever. The surviving host then does exactly the right thing and is denied the disk, and the restart can never complete. Arming HA in that state would report a cluster as protected while no VM could ever be restarted, so SimpleVirt refuses to arm instead.
What to do. Enable NFSv4.1 or later on the export and re-add the datastore from the cluster, or move those VMs onto SimpleVirt HCI storage. SimpleVirt does not pin a protocol version when it mounts an NFS datastore — the mount negotiates the highest version both ends support — so an export that offers 4.1 is mounted at 4.1 or better. The HA readiness checklist on the cluster's High availability tab names the datastore and the remedy.
A cluster that had HA armed on NFSv3 before upgrading is not disarmed — withdrawing protection without warning would be its own outage. It will show the failed readiness check, it cannot be re-armed until its storage changes, and it can always be disabled.
3. A lease-protected VM cannot be live-migrated
On an NFS datastore that does not provide the kernel's own lock leases, SimpleVirt protects single-writer safety with its own heartbeat lease written to the datastore. A VM holding such a lease is refused a live migration, because moving it live would mean two hosts holding the same disk open during the transfer.
This applies to the VM's storage, not to your HA settings: a host arms the lease whenever it starts a VM whose disks are on such a datastore, whether or not anyone has enabled HA on the cluster.
What to do. Use cold migration. Power the guest off, migrate, power it on. The Migrate wizard offers this: the destination is listed as needs power off rather than incompatible, and the wizard states the cost — the guest is down for the whole move. Host evacuation reports the same, and names the host that would take the VM once it is powered off.
See HA lease fence for the mechanism.
4. Migration data is encrypted by default, but the libvirt control listener is not
Live migration, evacuation and the workload distributor move guest memory and domain XML
over libvirt's transport, and that guest data is encrypted and mutually authenticated by
default (qemu+tls / VIR_MIGRATE_TLS). The controller stands up a migration CA at
startup and auto-issues a short-lived per-host leaf certificate at host-add and on renewal;
the handler refuses a migration rather than downgrading it if a peer lacks a leaf.
Plaintext migration (qemu+tcp) exists only as an explicit opt-out, when
migration.encryption is set to false.
The caveat that makes an isolated migration VLAN a prerequisite is the control channel,
not the data. The plaintext libvirt listener on TCP 16509 (auth_tcp="none",
unauthenticated) is opened to migration peers regardless of the encryption setting.
Whatever can open a TCP connection to port 16509 on a host has administrative control of
that host and every VM on it.
A dedicated, isolated migration VLAN is therefore a deployment prerequisite, not a recommendation. SimpleVirt keeps those ports closed by default and opens them only on a configured migration adapter and only to the fleet peers the controller pushes, and it takes the listener down if the firewall fails to load — but reaching the port is still the access, so the network has to carry nothing else.
Installation → Migration network specifies exactly what "isolated" means and how to configure the adapter. Read it before you build a cluster.
5. HA failover requires SimpleVirt-Command to be reachable
SimpleVirt Cluster Services — the component that lets a cluster recover a failed host's VMs
without the controller — ships as an experimental preview and is not supported for
production. Like every preview it is off until an operator names it in the controller's
experimental: block (see Experimental features), and its endpoint
answers 403 until they do. SimpleVirt-Command's own window onto it is read-only: the
controller samples each host's Cluster Services status and compares verdicts with it, and
acts on nothing it sees there.
Supported failover is decided and driven by SimpleVirt-Command. If the controller is down, unreachable, or partitioned away from the cluster at the moment a host dies, its VMs are not restarted until the controller comes back.
What to do. Treat the controller as production infrastructure: give it a static address, protect its disk, keep its state backed up (see Backup responsibility), and do not run it on a host whose failure you are relying on HA to survive. Bringing the controller back restores failover; nothing about the cluster needs to be rebuilt.
6. Application-consistent snapshots require SimpleVirt HCI and Guest Tools
SimpleVirt HCI — pooled shared storage built from the hosts' own disks (Ceph/RBD) — is a supported storage tier, alongside NFS, and is a supported tier for HA (see §2). Its day-2 lifecycle is shipped: a failed data disk can be drained and replaced from the product, and a host can be safely decommissioned from the pool, each with a preflight that names the impact (health, data protection, remaining capacity, and storage-monitor quorum) before it acts.
VM snapshots are shipped on both tiers. An NFS snapshot is an internal qcow2 snapshot; a SimpleVirt HCI snapshot is a native RBD snapshot taken across all of the VM's images. Neither is refused — the earlier release that turned snapshots away on SimpleVirt HCI is gone.
The limit is on consistency. Application-consistent snapshots are available only on SimpleVirt HCI, and only with SimpleVirt Guest Tools installed in the guest. On SimpleVirt HCI the snapshot path freezes the guest's filesystems through the Guest Tools agent, takes the RBD snapshot, then always thaws — so a running VM's snapshot is application-consistent when the agent answers and falls back to crash-consistent when it does not. A snapshot on an NFS (qcow2) datastore is never quiesced: it is always crash-consistent, as described in §1. If you need application-consistent rollback points for a running database, put that VM on SimpleVirt HCI and install Guest Tools; otherwise quiesce the application yourself before you snapshot. Either way, a snapshot is still not a backup (see §1).
7. The only outbound notification channel is a webhook
SimpleVirt has ONE outbound channel: a signed HTTP webhook. There is no email/SMTP, no SNMP trap, and no syslog forwarding, and the webhook is off unless you configure it.
When it is configured, the controller POSTs a JSON body to your endpoint every time an
alarm fires, is acknowledged, or clears. Each request carries X-SimpleVirt-Event,
X-SimpleVirt-Timestamp and X-SimpleVirt-Signature: v1=<hex>, where the signature is
HMAC-SHA256 over v1:<timestamp>:<body> keyed by your shared secret — so your receiver can
verify the body came from your controller and reject a replay. Configure it in the
controller's config.yaml:
notifications:
webhook:
url: "https://hooks.example.com/services/your-endpoint"
secret: "a-long-random-shared-secret" # REQUIRED; an unsigned webhook is refused
timeout: 10s # per attempt; capped at 30sDelivery is best-effort, deliberately. The queue is bounded and DROPS rather than
grows, there are at most three attempts per event, and nothing about a slow or dead
receiver can block the controller. The alarm itself is durable regardless — the record is
in GET /api/v1/alarms and GET /api/v1/alarms/history whether the notification arrived
or not — and GET /api/v1/alarms reports the delivery counters, including how many were
dropped. Treat the webhook as a prompt to go and look, not as a delivery guarantee.
If you need email, SMS or a pager, point the webhook at something that already does that
(Slack, Teams, PagerDuty, Opsgenie, or your own receiver). If you would rather poll, the
read paths are GET /healthz on the controller and on each host for liveness,
GET /api/v1/alarms for the fleet's open alarms, GET /api/v1/hosts/{id}/health for a
host's own assessment, and GET /api/v1/events for the fleet event log.
8. Utilization history is 24 hours, and carries no network throughput
SimpleVirt-Command retains 24 hours of utilization history per host and per VM. It is taken from the five-second inventory poll it already runs — no extra load on a host — and downsampled before it is stored: one point a minute per host, one point every five minutes per VM. It survives a controller restart, and it is backed up with the rest of the data directory.
It is charted in SimpleVirt-Command. A host's Health → Utilization view draws the retained CPU and memory series under Utilization history; a VM's Performance tab draws its retained CPU, memory and storage under Usage history. Both offer a 1-hour, 6-hour or 24-hour range. (The two small trend tiles above the host's chart are a different thing and are not the retained series: they are drawn in your browser from the live refresh — at most 60 samples, about five minutes, discarded on reload — so that something on the page moves while you watch it.) The same retained series is read from:
GET /api/v1/hosts/{id}/metrics?range=1h|6h|24h
GET /api/v1/vms/{uuid}/metrics?range=1h|6h|24hBoth need the matching view privilege (host.view / vm.view) and return
{range_seconds, resolution_seconds, retained_hours, samples[]}.
Two limits are worth knowing.
24 hours is the whole window. There is no week, no month and no year: "what did CPU look like last Tuesday" is still not a question SimpleVirt can answer. It is a bounded ring, not a time-series database, sized so it can always be on without a disk budget or a schema. Per-VM history is capped at 256 VMs; past that, the VMs that reported least recently lose their series first.
There is no network throughput, for a host or for a VM. No counter for it exists anywhere in SimpleVirt today, so it is in neither the live view nor the retained history: a host's charts are CPU and memory, a VM's are CPU, memory and storage. (SimpleVirt HCI keeps its own storage IOPS and bandwidth history; that is the storage cluster, not a network counter.)
What to do. If you need capacity trending beyond a day, or retrospective analysis, collect metrics into your own time-series system. Note that SimpleVirt does not push METRICS anywhere (§7 covers only alarm notifications), so this means polling the API.
9. The audit archive is bounded at about 900,000 records
Every mutation is written to an append-only audit log — audit.log in the appliance's
data directory (/var/lib/simplevirt-command on the controller, /var/lib/simplevirt on a host,
also reachable as /var/log/simplevirt/audit.log).
It rotates at 8 MiB and keeps 12 rotated segments, so the archive is bounded at roughly
104 MiB — about 900,000 records — per appliance. Rotated segments are named
audit.log.<UTC timestamp> beside the active file and keep that name for life. Past the
twelfth, the oldest segment is deleted; the newest records are always the ones that
survive.
Three limits are worth knowing.
Retention is by size, not by time. How far back 900,000 records reach depends entirely on how busy your fleet is. If you have a retention obligation stated in months, copy the archive off the appliance and hold it yourself — SimpleVirt cannot promise a duration.
Only the controller's own log is paged and exportable. The controller serves its whole archive at:
GET /api/v1/audit?cursor=&limit=&user=&action=&since=&until=
GET /api/v1/audit/export.csv?user=&action=&since=&until=Both need host.manage. Paging walks forward, oldest first, across rotated segments; the
cursor survives a rotation, and says so (cursor_expired) if retention deleted the records
it pointed at. since/until take a date (2026-09-01) or an RFC 3339 timestamp. A host's
own log is NOT paged this way: each host still serves only its trailing tail, which is what
GET /api/v1/events merges. To produce a fleet-wide compliance answer you still need to
collect each host's log yourself.
The Events view is a tail, and it is not the archive. It reads the newest 250 records per source, merges them newest-first and stops at 200 rows, so it answers "what just happened" and not "what happened in September". The archive itself is in the console: Administration → Tasks & Events → Audit archive walks this controller's whole log with the server's cursor, filters by user, action and date range on the server, and exports the active filter — not the page on screen — as CSV. It is the two endpoints above with a front end, so it inherits their boundary exactly: this controller's log, not the fleet's.
What to do. If you need long-term retention, a searchable history, or an audit trail
held outside the appliance, copy audit.log and its audit.log.* segments off each
appliance on a schedule. Watch free space on appliances that have been running a long
time; the installer's 32 GiB disk recommendation (see
Installation) exists partly for this.
10. The alarm rules are a fixed set, and every alarm is about a host
Alarms themselves are shipped and visible: the controller evaluates the fleet once a
minute, an alarm fires when a condition first appears, survives a controller restart, can
be acknowledged by name, and clears only when the condition actually goes away. The
Alarms page carries the open set and the history, the Health rail button carries a
count of the unacknowledged ones, and GET /api/v1/alarms and
GET /api/v1/alarms/history serve the same thing. What is bounded is the rule set.
You cannot define an alarm, and you cannot change a threshold. There are nine conditions, compiled in, with no configuration and no UI to edit them:
| Condition | Grade | Fires when |
|---|---|---|
| Host CPU high | warning | host CPU usage above 90% |
| Host memory high | warning | host memory usage above 90% |
| Host load high | warning | 1-minute load average above twice the host's core count |
| Host in maintenance | warning | the host is in maintenance mode |
| Datastore disconnected | critical | a datastore the host reports is not active |
| Datastore nearly full | warning | a datastore has under 10% free |
| Service not running | critical | simplevirtd, libvirtd or systemd-networkd is inactive |
| Host certificate mismatch | critical | the host's pinned certificate no longer matches |
| Host not pinned | critical | the host has no pinned certificate to compare |
There are only two grades, warning and critical — the two the health endpoint has always emitted — and no informational level.
Every alarm is about a host, or about a datastore or a service on one. There is no
alarm about a virtual machine, a cluster, a network or a task: a VM that has been down for
a day raises nothing. Use GET /api/v1/vms and the fleet event log for those.
The history is bounded at 1,000 transitions, oldest discarded first. It is a support artifact, not an archive — a controller that flaps a datastore for a week will roll older transitions out of it. The audit log (§9) is the durable record.
What to do. If you need a threshold SimpleVirt does not have, poll GET /api/v1/alarms,
GET /api/v1/hosts/{id}/health and the metrics endpoints in §8 from your own monitoring
system and hold the rules there. §7 covers getting the alarms SimpleVirt does raise out to it.
11. The support bundle has no button in SimpleVirt-Command
The support bundle is API-only in this release. GET /api/v1/support-bundle
(admin.users) returns one redacted .tar.gz — versions for the controller and every
host, the controller's configuration, the fleet and cluster inventory, recent tasks, HA
state, the alarm set and history, and the tails of the controller's own audit and service
logs. It is assembled in memory and bounded, so producing one on a sick controller is
cheap. There is no control anywhere in the console that produces it, so collecting one
means signing in as an administrator and fetching that path with the session's cookie.
Operations → Support bundle has the exact procedure.
Two boundaries are worth knowing.
It is the controller's bundle. The logs in it are the controller's own; a host's
simplevirtd log is not in it, and nothing in the product collects one. A fault that lives on a
host still needs that host's /var/log/simplevirt/simplevirtd.log fetched by hand.
It is a point-in-time snapshot of tails. Take it while the fault is still live. A bundle taken after a restart may no longer carry the window that matters.
12. File Services is a preview, and its failover is controller-driven
SimpleVirt File Services (SMB/NFS shares carved from SimpleVirt HCI) is behind the
file_services preview flag and is off by default. It is production-quality in the
code — NFS defaults to deny-by-default client scoping, SMB serves over a locked-down,
SMB3-only, sealed, default-closed configuration, per-share quotas fail closed, and reboot
recovery is host-autonomous — but the end-to-end serving path must be proven on a real
multi-host cluster before the flag comes off. See File Services.
Within the preview, three boundaries are deliberate and will not change in v1:
The floating address is controller-driven, not quorum-owned. One reachable member holds it; if that host dies the controller re-homes it on its next reconcile (~30s). While SimpleVirt-Command is down, the current placement is frozen — shares keep serving where they are, but a host that dies during a controller outage is not re-homed until the controller returns. A controller↔server partition where the server stays alive can briefly double-claim the address; CephFS (MDS-coordinated) keeps the on-disk data consistent throughout, and the window closes within one reconcile once the stale host is reachable again. A cluster-services (Raft) owned address is the planned hardening.
SMB failover is a hard reconnect, and there is no account lockout. A VIP move resets in-flight SMB sessions (clients reconnect to resume); SMB3 persistent handles are not configured. SMB authentication is NTLMv2 against a per-host local account store with no rate-limiting — in-flight traffic is sealed (encryption required) and reachability is limited to the share's allowed-client CIDRs, but keep the storage network trusted.
Share data is not backed up or snapshotted by File Services. Share contents live in the HCI CephFS and follow the HCI pool's protection geometry; there is no per-share snapshot or backup. Removing a share retains its data by default (reclaim it explicitly); see backup responsibility.
Related
- File Services — enable, declare shares, mount, and the preview boundaries
- Backup responsibility — what SimpleVirt protects and what you must protect yourself
- Installation — hardware minimums and the migration network
- Operations — day-2 procedures
- Troubleshooting — diagnosing a problem you already have