Skip to content

Hypervisor setup

The GigaHost hypervisor agent turns one physical Linux server into a provider host. This page covers the steps shared by home and cloud deployments.

  • A dedicated x86-64 server with hardware virtualization enabled in firmware.
  • Ubuntu 22.04, 24.04, or 26.04, or Debian 12 or 13. Proxmox VE is not supported, because it runs its own QEMU stack beside which libvirt cannot be installed.
  • A working systemd installation and root access.
  • Persistent SSD or NVMe storage for guest disks and hypervisor state.
  • Stable outbound Internet access and accurate system time.
  • A GigaHost account, any account, and a new host enrollment token from it.

Confirm that KVM is available before continuing:

Terminal window
test -c /dev/kvm && echo 'KVM is available'

Choose the mode before enrolling the host:

  • Home mode needs only outbound connectivity and is suitable for hosts behind NAT.
  • Cloud mode gives each guest one of the server’s additional public IPv4 addresses, routed through a router VM that the agent runs on the host.

A host has one mode. Changing modes requires enrolling a new host identity.

The agent is one binary, gigahost-hypervisor, built on Ubuntu 22.04 against its glibc 2.35, so it runs on every supported distribution. It is published on a signed release channel, and one command installs it together with its dependency installer and systemd unit:

Terminal window
curl -fsSL https://api.gigahost.cloud/install/hypervisor.sh | sudo sh

The script downloads the channel’s manifest and refuses it unless its Ed25519 signature matches the release public key embedded in the script (checked with OpenSSL 3, or with python3 where the OpenSSL is older), then downloads the release archive and refuses it unless its SHA-256 matches the signed manifest. Only then does it run the dependency installer and put gigahost-hypervisor in /usr/local/sbin, the dependency installer beside it as gigahost-install-hypervisor-deps, and the unit in /usr/local/lib/systemd/system. The website’s Provider page (/provider) and its Add a host page (/provider/hosts/new) show the same command, the manifest URL, and the public key, so you can compare the key the script carries with the one your account shows.

Run the same command to update. It installs a newer release, restarts a running agent (the agent resumes its work from its journal), and reports when the installed release is already current. Schedule updates like reboots: pause new rentals first if you want no work queued during the restart. The agent does not update itself. gigahost-hypervisor --version prints the installed release, which the agent also reports to Gigahost; a binary not installed by the script reports dev.

Gigahost serves every release of the agent for at least 90 days after it was built (a release’s name starts with its build time, such as 20261010T120000Z-…). Your host’s page and the Agent column of your host list show the release each host runs and until when it is served; from 14 days before the end they say “Support ends …: update it”. Update before then with the install command above.

Once Gigahost stops serving a release, it still accepts the agent’s heartbeats and metrics and what the work the host was already doing needs to finish (its progress, its results, a backup’s transfers), refuses its other requests, and names the release to install: the agent logs that once as an error, keeps its VMs running and its heartbeats and metrics going, and finishes the work under way; sudo gigahost-hypervisor check shows it, and you get a critical incident naming the host and the command, which resolves once the host runs a release that is served. Until then the host stays up and degraded: no outage opens, but it takes no new rentals and starts no new work, so your renters’ requests for its VMs (starting, stopping, backing up, deleting) wait until the agent is updated. An updated agent is served again as soon as its first heartbeat arrives. An agent of a release that is no longer served cannot enroll a new host either; install the current release first.

Check the dependencies later with:

Terminal window
sudo gigahost-install-hypervisor-deps --check

The installer accepts only the distributions listed above on amd64. It enables KVM/libvirt and installs the disk, image, and networking tools used by the agent, including smartmontools for disk health, and creates the state directories under /var/lib/gigahost. --check verifies an existing installation without changing the host. The installer does not repartition disks, create storage pools, or change the management uplink.

The installer makes one change to the system beside its packages. Debian 13 and Ubuntu 26.04 mount /tmp as a tmpfs that may grow to half of the RAM, and what is written to a tmpfs is held in memory, the memory your host sells to its guests: whatever filled /tmp would take it from them, up to the host running out of memory. The installer therefore caps a tmpfs /tmp of more than 1 GiB at 1 GiB and masks tmp.mount, so that /tmp is on the root filesystem from the next boot, and says so. --check reports a larger tmpfs /tmp without changing it, and enrollment refuses a host that has one. To do it by hand:

Terminal window
sudo mount -o remount,size=1G /tmp
sudo systemctl mask tmp.mount

A /tmp on disk, or a tmpfs of 1 GiB or less, is left as it is.

Gigahost keeps CET shadow stacks, a CPU feature, from every guest. A guest gets them where the host’s Linux, 6.18 or later, virtualizes them for a CPU that has them. The agent turns them off in every guest’s CPU where the host’s libvirt and QEMU know them by name; elsewhere only the kernel parameter clearcpuid=519 keeps them off, from the host’s next boot. The dependency installer says which in its cpu line, and where the parameter is needed it asks you on the terminal, also under curl | sh, whether to add it to GRUB’s configuration; --check only reports. The enrollment asks too. To do it yourself:

Terminal window
sudo gigahost-hypervisor shadow-stacks
sudo gigahost-hypervisor shadow-stacks --add-boot-parameter

The first command says what guests get and exits with 0 when they get none or the agent turns them off, 2 when the parameter is needed and GRUB’s configuration lacks it, and 3 when the configuration has it but the host has not started since. --add-boot-parameter writes /etc/default/grub.d/90-gigahost-no-shadow-stacks.cfg and runs update-grub; the parameter takes effect when the host starts again, which you schedule like any reboot. Linux then marks its kernel tainted, a warning that changes nothing else. Until the host runs with the parameter, it has the critical condition guest_shadow_stacks and takes no new rentals.

Ubuntu’s images, and some providers’ Debian images, install unattended-upgrades and run it daily. An upgrade may restart libvirt, replace QEMU’s libraries under running guests or leave a new kernel waiting for a reboot, on its own at any hour. The dependency installer warns in its upgrades line where it runs, when installing and with --check, and changes nothing about it: the host is yours to run. To turn it off, keep the package lists fresh but the upgrade off in /etc/apt/apt.conf.d/20auto-upgrades:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "0";

stop the timer, and answer the package’s debconf question so that a reconfiguration does not switch it back on:

Terminal window
sudo systemctl disable --now apt-daily-upgrade.timer
echo 'unattended-upgrades unattended-upgrades/enable_auto_updates boolean false' | sudo debconf-set-selections

Then upgrade by hand, apt-get update && apt-get upgrade, while you watch the host. An upgrade that leaves a new kernel waiting for a reboot is best followed by one: pause new rentals first, as for any reboot, so that no work is queued during the restart.

Beside the guests’ metrics, the agent reports the host’s own every minute: CPU busy and steal time overall and per NUMA node, load, memory and hugepages, each storage pool’s usage, disk I/O and SMART health per backing device, traffic on the uplink, temperatures where sensors exist, and the memory errors the kernel has counted. The readings appear under Status on the host’s Overview tab on the website (/provider/hosts/HOST_ID), traffic in the bit/s unit it fills (kbit/s, Mbit/s, Gbit/s) and disk rates in binary units such as MiB/s, with the history on its Usage tab, and through ghost provider host get, metrics, and instance-usage.

The Overview opens with the host’s Capacity, what it offers for rent under the capacity revision in force: its vCPUs (cpu_core_limit_per_numa_node times its NUMA nodes), the RAM and the disk that come with them (memory_bytes_per_cpu_core and root_storage_bytes_per_cpu_core times those vCPUs), what its rentals hold of each, running or stopped, its VMs, the RAM and disk per vCPU, and its prices on one line. Then come its Health (the agent, the conditions below and the host’s open incidents), Status (the readings, then a table each for the NUMA nodes, storage pools, uplinks, temperatures and disks), and Capacity details: what each NUMA node offers and its rentals hold, every size of the host with its RAM and disk and whether it is offered, and what is reserved of each storage pool. A cloud host’s IP addresses follow its Hardware and Network tests: whether an address has answered through it, and the addresses of the IP pools that serve it (the first 50 of each pool, the rest on the pool’s page) with their gateway and state.

The agent judges these readings itself and reports what you should look at as a condition of the host, which you get as an incident for as long as it lasts. A warning leaves the host as it is. A critical condition degrades the host: it takes no new rentals until the condition is over, and its instances keep running.

Condition Warning Critical
CPU temperature 5 °C below the CPU’s own limit; 98 °C on an AMD CPU, which names none the CPU’s own limit; 105 °C on an AMD CPU
Disk temperature (NVMe) the drive’s warning temperature the drive’s critical temperature
Memory module temperature 75 °C the module’s critical temperature, 85 °C
Disk health its SMART self-assessment failed
Disk wear 80% of its rated endurance used 95%
Disk media errors the count rose within the last 24 hours
Memory errors corrected errors were counted within the last 24 hours an error could not be corrected since the host started
Hardware RAID array it is being rebuilt, or its controller names a state the agent does not know it is degraded, failed or offline
Hardware RAID controller the state of its arrays cannot be read
Root pool less than 5% free
Backup pool missing: its directory is gone, or on the root filesystem or another pool’s filesystem, or its volume group or thin pool cannot be read
Agent filesystem less than 10% free less than 5% free
Memory less than 2% available
Accepted capacity a NUMA node has fewer CPUs or less memory, or the root pool less room, than the capacity Gigahost accepted for the host; checked at every start of the agent too
Guest shadow stacks guests would get CET shadow stacks, and the host does not run with clearcpuid=519
Shared IP port in use (cloud hosts) a service listens publicly on a port from 1000 to 60999 of the host’s address, which are GigaHost’s for VMs that share it (shared IP)

A temperature counts once three samples in a row, three minutes, were at or over its limit. Only the sensors of the CPU (k10temp, coretemp), of NVMe drives, and of memory modules (spd5118, jc42) are judged. The sensor chips of mainboards are shown and not judged: an input that nothing is connected to reads over 100 °C for good. A Ryzen 7000 or 9000 holds 95 °C under full load by design, which is no condition.

Memory errors are counted only on a host whose memory corrects errors (ECC) and whose kernel has the EDAC driver of its memory controller loaded. On every other host the host’s Overview says that they are not available, which is not the same as none. gigahost-install-hypervisor-deps --check tells which of these sensors the host reads and which module to load for one that it does not, without failing for it.

GigaHost supports two guest root-storage layouts:

  • Thin LVM identifies a pool as volume_group/thin_pool. Size the pool so all advertised guest disks and operational headroom fit on physical storage, and its metadata for a thin volume for each VM the host can hold, four for each logical CPU it sells, and a few of the host’s own: enrollment works the need out with thin_metadata_size and says how much is missing. The configured thin pool must be dedicated to GigaHost and have LVM zeroing enabled. Check with lvs -o segtype,zero VG/POOL; if needed, enable it with lvchange --zero y VG/POOL before enrollment.
  • Preallocated file storage places fallocate-reserved guest images in a dedicated filesystem. Create its directory before starting the agent and add it to the configuration. Every configured file pool must use a distinct filesystem so capacity cannot be counted twice.

Each pool has a kind:

  • Exactly one root pool holds the guests’ root disks. On a cloud host it also holds the 4 GiB root volume of the router VM. On every host it holds 32 GiB of scratch space for the backups of running guests: two scratches of 16 GiB, ghs-slot-0 and ghs-slot-1, where the blocks a guest overwrites during its backup are kept.
  • A backup pool holds the encrypted artifacts of local backups, each about the size of the guest’s root disk at most. Backup pools can use thin LVM or file storage.

There is no staging pool any more: a configuration that names one is refused with the advice to remove it, or to make it a backup pool.

The root pool needs the 32 GiB of scratch space beside the root storage your capacity profile offers and the router’s volume. Enrollment checks that, and refuses a host whose root pool is too small, so that a running guest can be backed up whatever you have rented out. The agent makes the two scratches when it starts: in a file-backed root pool two files with all of their 16 GiB allocated, which it attaches as loop devices, and in a thin-LVM root pool two thin volumes with all of their blocks provisioned. They stay for the next backup, are no part of what the pool offers, and do not count toward the five percent of the root pool that must stay free. The size of a scratch and their number are the same on every host and no setting of yours: a renter need not know the host to know what a backup does.

A host makes two backups or restores at a time, of running guests or stopped ones, and further ones wait in the queue until one of the two is done. A guest that overwrites more than 16 GiB of its disk while it is backed up fills its scratch. QEMU then pauses the guest, the backup reads the rest of the disk, and the agent resumes the guest when the backup ends; the guest’s writes are not lost, and it sees no error. A stopped guest is read without a scratch.

A hosted backup needs no pool of its own, and a local backup needs a backup pool. The backend refuses a local backup on a host without a backup pool with local_backups_unavailable, one whose artifact no backup pool could hold even when empty with backup_too_large_for_host, and one that fits only once other backups leave with backup_storage_busy; a stored local backup keeps its room until it is deleted. A restore of a local backup, or a new instance from one, whose backup pool the host is missing is refused with backup_pool_unavailable (see a missing backup pool). Give the largest backup pool a little more than the largest root disk you offer, and more to keep several backups.

Each offering in the catalog shows renters, as backup_limits, which backups of its root every host behind it has room for when nothing else holds its backup pools.

For a file-backed pool, create its directory:

Terminal window
sudo install -d -m 0711 /var/lib/gigahost/root-images

Add each pool to hypervisor.toml. File-backed pools name their directory:

[[file_storage_pools]]
external_id = "root-images"
kind = "root"
directory = "/var/lib/gigahost/root-images"
[[file_storage_pools]]
external_id = "backups"
kind = "backup"
directory = "/srv/gigahost-backups"

A thin-LVM pool is named by its volume group and thin pool, here as the root pool in place of the file-backed one above:

[[lvm_storage_pools]]
external_id = "vg0/guest-root"
kind = "root"

Use a separate filesystem for guest storage where possible. Do not advertise more usable capacity than the host can provide.

A file-backed backup pool needs a filesystem of its own: its directory must not be on the host’s root filesystem or on another pool’s filesystem, or the agent takes it for a pool whose drive is not mounted (see below).

A backup pool can be added to a host that is enrolled and rents out instances. Its mode and location stay as they were enrolled, and its capacity as Gigahost accepted it (see Raise a host’s capacity); only the storage pools change.

  1. Prepare the storage as for enrollment: a thin pool with zeroing enabled, or a dedicated filesystem, mounted, with the pool’s directory on it.

  2. Add the pool to hypervisor.toml as a [[lvm_storage_pools]] or [[file_storage_pools]] entry with kind = "backup", as above.

  3. Check what the host would offer:

    Terminal window
    sudo gigahost-hypervisor check

    Without contacting the backend, it reads the configuration as the agent does when it starts, then lists every pool the host would offer with its kind, its backend, its capacity, and what of it the host keeps for itself. A backup pool it cannot find is shown as a warning with what it found. It ends with status 1, and says why, where the agent would refuse to start, the machine lacks the capacity Gigahost accepted (the agent would start, and the host be degraded), or it could not read the pools.

  4. Restart the agent with sudo systemctl restart gigahost-hypervisor. The inventory it sends when it starts lists the new pool: from then on the backend places local backups in it, and the catalog’s backup_limits count it.

A pool that holds local backups stays in the configuration until they are deleted: the agent refuses to start while a local backup’s artifact is in a pool the configuration no longer names, with backup pool holding N local backups is not configured any more (first: PATH); put the pool back in the configuration, or delete the backups first. An empty backup pool may be removed.

A backup pool holds nothing a running instance needs, so the agent starts without one it cannot find, as long as the root pool is there: a file pool whose directory does not exist, or is on the host’s root filesystem or on another pool’s filesystem (a drive that is not mounted leaves its mount point there, an empty directory that must not receive backups), and a thin-LVM pool whose volume group or thin pool cannot be read. The agent leaves the pool out of its inventory and reports the condition backup pool missing with the pool’s ID and what it found, which you get as an incident. It is a warning: the host stays up and its instances run on.

While the pool is missing, the backend places no local backup in it, refuses a restore of a backup in it, or a new instance from one, with backup_pool_unavailable, and charges the backups’ owners nothing for keeping them, which earns you nothing for them either. Their owners see those backups as unavailable, and why.

Mount the drive or activate the volume group. The agent finds the pool again within a minute and sends a new inventory; restart it if it does not. Before restores and storage charges resume, the backend has the agent check every backup’s artifact in the pool: a backup whose artifact is there with its recorded size is available again, and one whose artifact is gone is marked lost. Its owner is told, it is charged for nothing after the pool went missing, and it stays listed until its owner deletes it.

A backup deleted while its pool was missing leaves its artifact behind in the pool, since the agent could not reach it then. When the pool is back, and also whenever the agent starts and once a day, the agent asks the backend which of the backups it finds on the host the backend no longer has, and removes those leftovers; it never removes a backup the backend still has.

A missing root pool still stops the agent: the instances’ disks are in it.

Where the controller allows it, hand its drives to Linux (HBA or JBOD mode) and build the redundancy with Linux software RAID. The platform then sees every drive: its model in the catalog, its SMART health, wear, media errors and temperature, and the array’s level.

A hardware RAID controller shows Linux one logical disk for each of its arrays and hides the drives. The agent knows such a disk by its driver (megaraid_sas, mpt3sas, hpsa, smartpqi, aacraid, arcmsr, 3w-sas) and works with it as follows:

  • The catalog shows the array under the controller’s name as a hardware RAID array, with its level where that can be read: from the driver (hpsa, mpt3sas), or from the controller’s tool when every array it lists has the same level. Otherwise the level is shown as unknown.
  • The drives behind the controller are read through smartctl (-d megaraid,N, -d cciss,N and the like, as smartctl --scan-open names them) and judged like any disk. They are listed under the host’s details as drives behind a hardware RAID controller. The array itself is shown with its health unknown, never as healthy.
  • The state of the arrays comes from the kernel (mpt3sas) or from the controller’s own tool, which you install: storcli or perccli for MegaRAID and PERC, ssacli for Smart Array, arcconf for Adaptec. A degraded or failed array degrades the host; one that is being rebuilt is a warning.
  • Without the tool the agent cannot tell a drive that left its array, and you get the warning that the controller cannot be watched for as long as that lasts.

Enrollment and gigahost-install-hypervisor-deps --check tell what the agent reads of a controller. The state of the controller’s battery and of its write cache is not read.

Create private directories for configuration and durable state:

Terminal window
sudo install -d -m 0700 /etc/gigahost
sudo install -d -m 0711 /var/lib/gigahost /var/lib/gigahost/instances
sudo install -d -m 0700 /var/lib/gigahost/images

Enrollment needs a short-lived, single-use token from your provider account. You create it with the ghost client when you are ready to enroll (see below).

Start /etc/gigahost/hypervisor.toml with the following shared settings, then add the mode-specific settings from the home or cloud guide:

backend_url = "https://api.gigahost.cloud"
host_label = "my-host"
location = "sgp"
cpu_core_limit_per_numa_node = 0
memory_bytes_per_cpu_core = "4G"
root_storage_bytes_per_cpu_core = "64G"
allowed_numa_nodes = [0]
libvirt_uri = "qemu:///system"
cloud_uplink = "auto"
dns_servers = ["8.8.8.8", "1.1.1.1"]

The agent keeps its state, journal, and telemetry spool in /var/lib/gigahost (state.json, journal.sqlite3, telemetry.sqlite3), and VM state and the image cache in /var/lib/gigahost/instances and /var/lib/gigahost/images, the directories the dependency installer creates. Set state_file, journal_file, telemetry_spool_file, instance_directory, or image_cache_directory only to move one elsewhere; a relative path is resolved against the configuration file’s directory.

The agent caches the operating-system images it downloads, by default on the filesystem that also holds its state and, on most hosts, the operating system. image_cache_max_bytes (default "20G", at least "1G") bounds the cache: before a download and once an hour the agent evicts the images used the longest ago until the cache fits, and an image unused for 30 days goes in any case, except on a host that downloads the catalog ahead of demand (below). An image that a job is using is never evicted, and a VM’s disk is a copy, so an evicted image only costs a download when it is next used. A download needs the image and 5 GiB beside it free on the cache’s filesystem, and cached images that no job uses make that room too, the least recently used first; only when even they would not do does the creation fail, with a message that says so, before anything is written.

A home host downloads the catalog’s images ahead of demand, so that a new or reinstalled VM does not wait for its image over your uplink; a cloud host does not unless you set it. Two settings of hypervisor.toml control it:

image_predownload = true
image_predownload_rate_bps = 10000000

image_predownload is true by default on a home host and false on a cloud host. The agent asks Gigahost every 15 minutes which images to keep: the enabled catalog images your largest VM size can take, those most VMs were created from in the last 30 days first. It downloads them one at a time, each only while it fits within image_cache_max_bytes beside the images already cached and leaves the larger of 5 GiB and a fifth of the filesystem free, and it evicts nothing for them; an image that does not fit is left out, and a smaller one may still fit. The whole catalog takes about 10.4 GB, so the default limit holds it. The downloads take at most half the download bandwidth your host’s network tests measured, or image_predownload_rate_bps (10 Mbit/s) while there is no measurement, and they pause while a VM’s own image or a restore’s backup downloads or a network test runs, then continue where they stopped. Each image is verified by its checksum before it enters the cache, and one that is withdrawn from the catalog or replaced by a newer build is removed within about 15 minutes, once no job uses it. The cached images stay however long they go unused, and a VM’s own download evicts the images outside the plan first. ghost provider host get shows the cache (image_cache): what the images take, the filesystem’s size and free space, the pace, and each image with its state, cached, downloading, pending, left_out or failed, and why.

The agent also watches the filesystem of its state. With less than 10% of it free you get an incident, and with less than 5% the host is degraded and takes no new rentals until more is free. The host’s Overview and ghost provider host metrics show its size and free space.

libvirt’s virtlogd, which libvirt uses by default, writes what each guest prints on its serial console to console.log in the guest’s directory under /var/lib/gigahost/instances, with mode 0600. The agent keeps at most 4 MiB of it per guest, and every ten seconds sends the end of each log that changed, up to 64 KiB for each of eight guests, for the renter to read. When it starts a guest again, it first keeps the end of what the guest wrote since its previous start, up to 64 KiB, in console.boot beside the log, also mode 0600, and sends that once too, so a report takes at most about 1 MiB. A guest that ran before the agent recorded consoles is recorded from its next start. sudo virsh console still attaches to a running guest’s console.

backend_url defaults to https://api.gigahost.cloud when omitted. A plain HTTP URL is accepted only when its host is a literal IPv4 or IPv6 address for testing; http://localhost and every other hostname are rejected. Enrollment and control requests never follow redirects. The agent does not resolve the telemetry hostname: the backend alone resolves telemetry.gigahost.cloud and publishes its current IPs in the signed discovery manifest used for HTTP bulk traffic.

backup_upload_rate_bps is the aggregate hosted-backup upload limit in bit/s: 500 Mbit/s on a cloud host and 50 Mbit/s on a home host when it is left out. Adjust it so backups cannot crowd out guest traffic; on a home host, to what its upload can spare. At 50 Mbit/s a backup of 30 GB takes about 80 minutes to upload. Local and hosted backups both stream through two reusable 64 MiB transfer buffers; no complete raw staging disk is created. All-zero regions of a disk are stored without data. Backups written by current agents use a container format that older agents cannot restore, although current agents restore older backups, so update the agent when a new release is published. A hosted backup that may grow beyond about 625 GiB uploads in larger parts, the smallest whole number of MiB that fits it into S3’s 10,000 parts, and its two buffers grow to that size, so larger disks are not refused. Running guests use libvirt pull mode and therefore need temporary copy-before-write scratch capacity in the root pool. Restore recreates the root and writes into it directly, without a restore staging volume.

Gigahost does not encrypt live VM root volumes. Enrollment and inventory refresh require thin-LVM pool zeroing, which prevents newly provisioned blocks from exposing previous guest data without writing the entire virtual disk. Qcow2 roots are always fresh, use preallocation=falloc, and have no backing file. Image conversion may punch sparse ranges, so the agent fallocates the complete populated file again and verifies its physical allocation before boot. This reserves capacity without a full-volume write. Backup artifacts remain compressed and encrypted independently.

location is required for both home and cloud hosts. Choose a stable slug from ghost catalog locations. The backend separately maps enabled Vultr regions to those provider-neutral locations; a location does not need a Vultr mapping. Each location also carries country and continent codes for catalog filtering. A CPU limit of 0 auto-detects the smallest logical-CPU count among the listed NUMA nodes; a nonzero value sets a lower manual limit independently on every listed node. The automatic value is worked out at enrollment; once the host is enrolled, a 0 stands for the limit Gigahost accepted, and the agent never lowers or raises the limit by itself, except in update-capacity, where a CPU limit of 0 takes every CPU of the smallest allowed node (see Raise a host’s capacity). A node holds as many VMs as their CPU shares allow, up to four for each logical CPU; nothing else limits their number. An instance_limit_per_numa_node that a configuration still has is ignored with a warning: remove it.

A VM’s own overhead is counted in its RAM. 64 MiB of each size’s RAM is the VM’s, for QEMU and its devices, and the guest gets the rest: a VM of 4 GiB boots with 4 GiB less 64 MiB. Beside its guests’ RAM, every listed node keeps 0.5 % of it for the VMs’ overhead that grows with their memory, such as page tables; what the node has beyond that is your host’s, for its own services, the router VM of a cloud host and its page cache. With 4 GiB for a logical CPU and a limit of 16, a node sells 64 GiB to guests and needs 64.3 GiB of MemTotal for them. Enrollment needs that and a margin of 512 MiB besides on every listed node. RAM and root storage use binary K, M, or G suffixes and describe saleable capacity per logical CPU. A size may have decimals, such as 1.6G. One that is no whole number of KiB is rounded down to one, so 1.6G is 1,677,721 KiB, and enrollment shows what it resolved to.

Your host offers one ladder of sizes, each reserving twice the logical CPUs of the one before, and with them twice the RAM and the disk: a quarter of a core and half a core with 1 vCPU, one core and two cores with 2 vCPUs, and from there as many cores as vCPUs, 4, 8, 16 and so on. Between two sizes of N and 2N vCPUs with a core each, a mid-step has 2N vCPUs on the N cores, RAM and disk of the smaller one, where the larger one exists. The last size has cpu_core_limit_per_numa_node vCPUs, a power of two or not; no VM has more, and every VM remains within one NUMA node. With 4 GiB of RAM and 64 GiB of disk for a core and a limit of 16, the sizes have 1, 2, 4, 8, 16, 32 and 64 GiB of RAM, and 16 GiB to 1 TiB of disk; the mid-steps of 4, 8 and 16 vCPUs have 8, 16 and 32 GiB. A VM weighs on the host’s scheduler as much as the cores it reserves: a mid-step of 4 vCPUs on 2 cores as much as the size of 2 vCPUs.

The platform offers no new VM with less than 768 MiB of memory, counted on the size’s RAM, 64 MiB of VM overhead included, or with less than 8 GiB of root disk, which most images need. A size of your host with less of either is not offered, so the ladder starts higher: with memory_bytes_per_cpu_core = "1.5G" a VM of 1 vCPU on a quarter of a core would have 384 MiB, and the smallest VM the host offers has 1 vCPU on half a core and 768 MiB; with root_storage_bytes_per_cpu_core = "25G" the quarter of a core would have 6.25 GiB of disk, and the smallest VM has half a core and 12.5 GiB. The larger sizes are not affected, and VMs that run keep their size when a minimum changes. The host’s Overview tab lists every size with its RAM and disk and whether it is offered, noting a size with too little RAM, too little disk or both; ghost provider host get HOST_UUID lists the sizes that are not offered in sizes_below_minimum_memory and sizes_below_minimum_disk, and the host’s Pricing tab says the same under what each size earns. Enrollment boots the smallest and the largest size the host will offer, and refuses a host none of whose sizes has both minimums, naming the per-core figure to raise.

The enrollment command verifies the selected nodes, guest memory plus VM overhead and the margin in each node’s MemTotal, and configured root pools before registering the host. The CPU limit and the NUMA nodes can later be raised in place (see Raise a host’s capacity); the mode, the location and every other capacity setting stay as enrolled for that host identity. To change one of them, drain and retire the host and enroll a new identity. Identically configured hosts with matching CPU, location, price, and SLA appear as one catalog group; the backend chooses a physical host when a renter creates an instance.

Protect the completed file:

Terminal window
sudo chmod 600 /etc/gigahost/hypervisor.toml

After completing the home-mode or cloud-mode configuration, create an enrollment token on the machine where you use ghost:

Terminal window
ghost account get-host-enrollment-token --label "my-host"

The command prints the token and when it expires (expires_at). The website creates one too: Provider › Add a host (/provider/hosts/new) shows it once, inside the enroll command to run on the server. Then enroll the host and paste the token value when it asks; it is read like a password, without echo:

Terminal window
sudo gigahost-hypervisor enroll

The command asks for the token first, before its checks, and reads it from standard input instead when that is a pipe. To read it from a file, pass --token-file PATH. The token is never a configuration setting or a command-line argument, which other users of the host could read. Treat it as a secret: it is accepted once, by one host, and expires three hours after it is created by default (expires_at says when), so create it only when you are ready to enroll. If enrollment is interrupted, run the same command with the same token again; the host resumes with the same identity and is not enrolled twice.

While it runs, the command shows every step on the terminal as it happens: the host checks, on a home host a wireguard step that installs a key on a throwaway tunnel, the smoke-test image download with its size and speed, each smoke-test VM as it is created, booted, and removed again, the registration, and the network tests, each with how long it took. A step that fails says what it was doing, and a failure before registration leaves the token unused. If the wireguard step fails, look for a denial of /usr/bin/wg in journalctl -k | grep -i denied (AppArmor or SELinux); the running agent also logs an error at start when the check fails later, such as after an upgrade. A non-empty NO_COLOR turns the colors off, and output that is not a terminal, such as a log file, gets one plain line per step instead of a live line.

Enrollment creates the host identity in the configured state_file. Keep this file on durable storage. Never copy it to a second running host or reuse it as part of a machine image.

Before registering the host, the agent reads Linux CPU topology, NUMA memory, the usable capacity of every configured pool, and the root pool’s Linux-visible physical devices and RAID levels. Of a hardware RAID controller it reads the logical disk Linux exposes and the array’s level where that is told; see Hardware RAID. The agent then boots the smallest and largest Ubuntu 24.04 configuration on every selected NUMA node. Each pass must reach cloud-init readiness, and libvirt must report the guest’s virtio balloon statistics, which tell the agent that a new guest’s kernel has started; the final pass must also accept a real key-only root SSH login using a newly generated temporary key and link-local bridge addresses. Every mode creates an isolated temporary bridge. Temporary domains, volumes, bridges, addresses, and keys are removed before registration. The agent attempts to publish that inventory immediately after enrollment; a network failure is retained for retry when the persistent agent starts. Invalid CPU topology, undersized memory, a non-thin LVM target, disabled thin-pool zeroing, or overlapping file-pool filesystems makes enrollment fail instead of advertising capacity the machine cannot provide.

Libvirt runs QEMU as an unprivileged account. The agent makes each VM state directory searchable but not listable by other users. Every ancestor of instance_directory and each file-backed root-pool directory must likewise permit search access (chmod o+x PATH); disk and key files retain restrictive file permissions. Enrollment fails with the exact inaccessible path otherwise.

Successful first enrollment posts the configured non-refundable host-creation charge. Reusing the same enrollment identity cannot post the charge twice.

Newly enrolled hosts do not accept rentals automatically. They remain paused even after inventory and healthy heartbeats are visible. Finish pricing and network configuration, inspect the host, and then open admission explicitly:

Terminal window
ghost provider hosts
ghost provider host resume <HOST_UUID>

A cloud-mode host can be resumed only once one of its registered addresses has passed its one-time check through it; see address and host checks. Home hosts need no such check: each rental’s public address is its Gigahost-run gateway.

See Pricing and SLA to set what the host earns on its Pricing tab on the website or with ghost provider set-pricing (the API) once the host is enrolled: per instance, per vCPU or as a total for the whole host, and per TiB of traffic on a cloud host. Pricing is never part of the agent’s configuration or a deployment file, and setting or changing it needs neither a new enrollment nor a deployment. Complete the host’s capacity, storage-pool, location, and pricing setup in your provider account before accepting rentals. Backups are no part of your prices: the platform prices them the same on every host. Existing VMs change prices only after 90 days of notice, and the applicable restore and storage rates are copied to each backup. Platform ceilings reject earnings above the configured limits.

Run the agent once in the foreground:

Terminal window
sudo gigahost-hypervisor run

Every command reads /etc/gigahost/hypervisor.toml unless --config PATH or the environment variable GIGAHOST_CONFIG_FILE names another file.

Wait for the initial checks to complete, then stop it with Ctrl+C. Resolve any reported KVM, libvirt, storage, network, or permission errors before installing the service.

Before it serves anything, every start of the agent checks that the machine still has the capacity Gigahost accepted for the host: as many logical CPUs on every allowed NUMA node as the CPU limit, in each node’s MemTotal the guests’ RAM and 0.5 % of it, and room in the root pool for every guest disk beside the router’s volume and the backup scratch, the disks of existing guests counting as room. A root pool enrolled before VMs were counted by their CPU shares may lack the room for the metadata of that many disks, a file pool 4.5 MiB for each and a thin pool its volumes’ share of its metadata: the agent logs it and the host keeps selling its disk as far as it goes; extend the pool, or a thin pool’s metadata with lvextend --poolmetadatasize, before you raise the host’s capacity. Free or available RAM plays no part. The margin of 512 MiB that enrollment asked for is not needed any more, so a BIOS, firmware or kernel change that takes a little of a node’s RAM is fine. A machine with cores or SMT turned off, RAM removed or a smaller root pool still starts, and the agent logs the shortfall with its numbers. The host is then degraded (the Accepted capacity condition above): it takes no new rentals, its instances keep running, and its inventory is not updated until the machine has the capacity again. The agent never lowers an accepted limit to fit, so restore what is missing; it checks the whole machine again with every inventory, every 15 minutes or sooner, and the host is healthy again once the machine passes. sudo gigahost-hypervisor check makes the same check without starting the agent and fails on such a machine, naming the shortfall. A configuration that is not the host’s, with another mode, location or capacity than Gigahost accepted, is another matter: the agent refuses to start with it and says what to restore.

Enrollment starts real smoke VMs and rejects a host unless QEMU runs as a non-root user without effective capabilities, has seccomp filtering, and has an enforcing libvirt AppArmor or SELinux policy. On SELinux systems, both global enforcement and the QEMU domain type must be enforcing; setools is required to check the loaded policy. The agent also rejects remote libvirt TCP/TLS listeners and any libvirt URI other than qemu:///system.

The running agent recollects inventory on startup and every 15 minutes. It publishes a new revision only when CPU, NUMA, memory, or usable storage changes, or when the parts of 100.64.0.0/10 that other software on a home host uses change (see home mode); revision state is written durably so retries after a crash are idempotent.

The installer placed gigahost-hypervisor.service in /usr/local/lib/systemd/system. It runs /usr/local/sbin/gigahost-hypervisor run --config /etc/gigahost/hypervisor.toml as root with a private /tmp, no new privileges, and the kernel-facing protections systemd offers, and restarts the agent five seconds after any exit. Put local changes in a drop-in under /etc/systemd/system/gigahost-hypervisor.service.d/ rather than editing the unit, since an update replaces it. Enable and inspect it:

Terminal window
sudo systemctl enable --now gigahost-hypervisor
sudo systemctl status gigahost-hypervisor
sudo journalctl -u gigahost-hypervisor -f

The host should appear online in your provider account. Keep automatic security updates enabled, but schedule reboots and hypervisor upgrades so active guests are handled deliberately. ghost provider host pause <HOST_UUID> prevents new rentals without interrupting existing guests or work already queued for the agent.

Review ghost provider incidents as part of normal operations. The inbox deduplicates host outages, degraded health, storage pressure, telemetry gaps, invalid telemetry excluded from billing, failed work, and network bandwidth drops, and links each item to the affected host or job where applicable. Failed work is yours only when it failed on your host’s side, and each kind of job, step, and error is an item of its own. A failure at a step the platform runs itself, such as setting up a home VM’s gateway, or one the renter caused, such as a restore not authorized in time, is no incident of yours, and neither is a guest that starts no operating system again after its renter’s reboot, reset, upgrade or restore: that system is the renter’s. A guest that does not boot a catalog image when it is created or reinstalled is still yours to look into. Acknowledge an item while working on it and resolve it after the underlying condition is fixed; a later recurrence reopens the same incident. On the website, Provider › Incidents (/provider/incidents) lists them with their State (Open, Acknowledged or Resolved) and Severity (Critical, Error, Warning or Info) in columns of their own, filtered by either, and a host’s Overview lists its open ones the same way.

For permanent planned retirement while guests still exist, use:

Terminal window
ghost provider host retire <HOST_UUID> --notice-days 7 --yes

Seven full days is the minimum. Admission closes immediately, affected renters see the backend-calculated deadline, and durable deletion jobs begin when it is reached. Keep the agent online through cleanup. Instance usage and gateway base charges stop at the deadline, but storage capacity, public addresses, and gateways remain fenced until their deletion is confirmed. Local-backup records and billing close at the deadline, although their physical files may remain; hosted backups are unaffected. Failed cleanup is reported as action_required and is not retried without a bound.

To retire only one customer VM, keep the host online and schedule the same minimum notice with ghost provider retire-instance <INSTANCE_UUID> --notice-days 7 --yes. If the machine is physically unrecoverable, use ghost provider host emergency <HOST_UUID> --reason '...' --yes instead. Emergency retirement stops billing immediately, records every affected VM as abandoned, revokes the enrollment, and notifies renters; it cannot be undone. External gateway deletion is still completed asynchronously by the backend.

The agent advertises independent free slots for provisioning, backup, restore, upload, and maintenance work, along with the exact step kinds supported by its binary. The backend leases only compatible work and atomically claims every resource a step touches, such as its instance or backup, so two steps never work on the same one at once. Storage pools are not claimed; the free slots bound how much work runs on them. On every host the steps that move a whole disk, a backup’s upload or local write and a restore’s writing, share two slots, and the backend starts no more than two backups and restores of a host at a time; the other backup steps, such as starting a backup or publishing it, run beside them. Enrollment also agrees with the backend on a version of the control protocol. A backend that no longer speaks an agent’s version refuses its requests until the agent is updated, and names the release to install, as it does for a release it no longer serves (support window). The agent logs the refusal once as an error, keeps its VMs running, and asks again only with its heartbeat, at the heartbeat interval; update it with the install command, which restarts it. sudo gigahost-hypervisor check shows the version the agent speaks, whether the backend refused it, and whether the backend has the agent’s message types.

A host can sell more of its machine without a new enrollment, keeping its identity, its rentals and their history. Two settings of hypervisor.toml can grow:

  • cpu_core_limit_per_numa_node, up to the logical CPUs of the smallest of the allowed NUMA nodes; 0 takes them all.
  • allowed_numa_nodes, with another NUMA node that has at least as many logical CPUs as the accepted CPU limit. With a limit of 0, a node with fewer would take the limit below the accepted one: the command names the node, which must stay out.

The RAM and root storage per logical CPU stay as enrolled, so every size keeps its shape and the host’s saleable RAM and disk grow with its CPUs: the machine must have them. A lower limit, a node dropped or another value of any other capacity setting is refused, naming the change, and needs a new enrollment (below), as does another mode or location.

Stop the agent, change the settings, raise the capacity, and start the agent again:

Terminal window
sudo systemctl stop gigahost-hypervisor
sudoedit /etc/gigahost/hypervisor.toml
sudo gigahost-hypervisor update-capacity --reason "Sell all 64 threads"
sudo systemctl start gigahost-hypervisor

The guests keep running while the agent is stopped, but the host sends no heartbeats: a minute without them opens an outage, so run the steps one after the other. update-capacity refuses to run while the agent does, since the agent rewrites the state the command updates. It shows the capacity Gigahost accepted and the one the configuration gives, and refuses a configuration that raises nothing or changes anything else. It then checks the machine as enrollment does: every allowed node needs as many logical CPUs as the new limit, and in its MemTotal the guests’ RAM, 0.5 % of it and the margin of 512 MiB; the root pool needs room for every guest disk of the new capacity with the metadata of the most VMs it allows, beside the router’s volume and the backup scratch, the disks of existing guests counting as room. It boots no VM: enrollment’s smoke tests proved the machine already. The reason, 1 to 500 bytes on one line, is kept with the change.

The command then sends the new capacity to Gigahost, signed. The backend checks it the same way against the latest inventory the agent sent, records it as the next revision of the host’s capacity with its time and reason, and the command keeps it as the host’s own. A refusal shows the backend’s reason and changes nothing; with explicit limits, the agent then refuses to start with the larger configuration until the command succeeds or you restore the accepted values. A refusal for want of CPUs, RAM or disk names the inventory it judged by and its age: if the machine changed since, the backend does not know it yet, so start the agent, which sends a fresh inventory at once, stop it again, and run the command again. A host the backend no longer knows (retired, or enrolled again under a new identity) has nothing to raise.

If the command ends without the backend’s answer, from a lost connection, an answer it cannot verify or a failure of the backend, the backend may have accepted the new capacity. Run the same command again before you start the agent: a second run of a capacity the backend accepted answers the same revision, and the command keeps it. An agent started in between keeps the old capacity (with explicit limits it refuses to start), and the backend refuses its inventories until the command has run. The command exits with status 0 when the host keeps the new capacity, with 1 when nothing changed, and with 3 when it is to be run again.

When the agent starts, it sends its inventory at once, and the catalog offers the larger sizes from its next refresh, under new offering IDs. Prices are kept per vCPU, so every size the host offered keeps its price for existing and new rentals, and the whole-host total on the Pricing tab grows with the vCPUs; the command changes no price, which you set on the Pricing tab or with ghost provider set-pricing as before. Existing rentals keep their reservations, NUMA node, prices and SLA. The host’s Overview tab names the revision in force and when it was accepted, and lists every revision with its reason under Capacity details, as ghost provider host get (capacity_revisions) does.

Immediately retire or re-enroll an empty host

Section titled “Immediately retire or re-enroll an empty host”

A host’s mode and location, and any change of its capacity other than a higher CPU limit or more NUMA nodes (see Raise a host’s capacity), need a new enrollment. For one, the host must have no live rentals, unreleased instance resources, gateways, local backups that renters still keep, or active jobs. Stop the service and run:

Terminal window
sudo systemctl stop gigahost-hypervisor
sudo gigahost-hypervisor reset-enrollment --yes

The command first asks the backend to retire the identity. It independently refuses to continue if a managed libvirt domain still exists, then archives the state file, durable job journal, telemetry spool, and SQLite sidecars with a .retired-<HOST_UUID> suffix. Local backup artifacts are untouched; after retirement they are outside the platform-managed lifecycle and may be retained or removed by the provider. Their platform records and storage billing close at retirement, while hosted backups remain available.

You can also retire an empty host remotely with ghost provider host delete <HOST_UUID> --yes. The local reset remains required before enrollment can be repeated. Create a fresh single-use enrollment token, replace the consumed token file, adjust the configuration, and run enroll again. The backend creates a new host identity and host-creation charge; the retired historical record remains visible, and its label may be reused.

A cloud host keeps its public addresses across this. Hand the retired identity’s pool to the new one with ghost provider ip-pool hand-over <POOL_UUID> --host <NEW_HOST_UUID>: the new host gets a pool with the same label and every address, and each address is checked again through it before a rental may get it. The rentals the addresses served stay on record with the old pool.