VirtualBox refuses any guest property of 1,024 bytes or more, and that decides whether your Ignition config ever reaches the VM. It is much tighter than the command-line ceiling the Fedora CoreOS docs warn about. On the test box a pretty-printed Butane output of 1,142 bytes failed with VERR_TOO_MUCH_DATA, the Fedora CoreOS guest booted with no config at all, and SSH never answered. The final config transpiled without --pretty came to 844 bytes, and the VM accepted SSH logins 76 seconds after startvm.
Below, Fedora CoreOS (FCOS) runs on VirtualBox from the official OVA image. The steps cover VirtualBox modules on a Fedora host with Secure Boot on, the Butane config, the /Ignition/Config guest property, NAT port forwards, and a pointer config over bridged networking for anything bigger than 1 KB. Every number below was measured with VirtualBox from RPM Fusion (7.2.18, tested October 2026 on a Fedora 45 host) and the stable FCOS stream. Windows and macOS hosts, and the older live ISO route, were not tested.
What the test box measured
Our host was a 4 vCPU, 7 GB Fedora VM with nested virtualization, running two FCOS guests at 2 GB each. These figures shape the rest of the steps.
| Measurement | Result |
|---|---|
Largest value VBoxManage guestproperty set accepts | 1,023 bytes (1,024 fails with VERR_TOO_MUCH_DATA) |
Final Butane config from Step 3, --pretty vs compact | 1,154 bytes vs 844 bytes |
| OVA image (stable stream) | 1.1 GB, signature checked by coreos-installer |
| OVA defaults on import | 2 vCPU, 4096 MB, EFI firmware, 10 GB disk, NAT |
startvm to first SSH login | 76 s on both runs |
First boot, systemd-analyze | 54.1 s (26.7 s of it in the initrd) |
| Memory after boot, 2 GB guest | 405 MiB used, 1,529 MiB available |
| Root filesystem after resizing the disk to 20 GB | 19.4 GB XFS, grown on first boot |
/Ignition/Config after first boot | Deleted by Ignition |
Two rows matter most. 1,023 bytes is enough for an SSH key, a hostname and one small unit file, and nothing more. A second Quadlet, a TLS certificate or a few storage.files entries push a config over the limit, and VirtualBox gives no warning beyond a failed guestproperty set that is easy to miss in a script. Compact output buys you about 310 bytes, and Step 7 covers the pointer config for everything else. The memory row says a 2 GB guest is plenty for FCOS plus a small exporter, so reserve the 4 GB the OVA asks for only when your containers need it.
Step 1: Install VirtualBox on the Fedora host
On Fedora, VirtualBox comes from RPM Fusion free, which builds the kernel modules on the host with akmods. The Oracle repository is the other option, but if Oracle has not published a tree for your Fedora release yet, dnf gets a 404 on its repository metadata (the third-party repo 404 guide covers that case). Enable RPM Fusion free first. Two attempts through mirrors.rpmfusion.org failed on our run (one mirror did not resolve, the next timed out), so this command fetches the release package from the primary host:
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
Then install VirtualBox together with its akmod package, which builds the three kernel modules VirtualBox needs.
sudo dnf install VirtualBox akmod-VirtualBox
On our host the install pulled in kernel-devel only for a newer kernel than the one the host was running, and akmods builds only where a matching kernel-devel exists. So vboxdrv failed to start straight after the install on our box, because there was no module for the running kernel. Reboot into the new kernel before testing anything. On a fresh host it is simpler to run sudo dnf upgrade --refresh and reboot before installing VirtualBox, so the running kernel is already the newest one. For other ways to install it, including Oracle’s RPM and the extension pack, see the VirtualBox on Fedora guide.
sudo systemctl reboot
“Key was rejected by service” with Secure Boot on
If mokutil --sb-state prints SecureBoot disabled on your host, skip to the module check at the end of this section. On ours, after the reboot the module existed, but the kernel still would not load it. With Secure Boot enabled, the kernel only accepts modules signed by a key the firmware or shim trusts, and on this host that list held the Fedora CA and nothing else:
mokutil --sb-state
sudo modprobe vboxdrv
Every Secure Boot user hits the second line with out-of-tree modules.
SecureBoot enabled
modprobe: ERROR: could not insert 'vboxdrv': Key was rejected by service
akmods signs what it builds with a per-host key under /etc/pki/akmods/, and that key has to be enrolled in the MOK list. There was a second problem first. The key on our host was created at 18:21, a minute after the module was built at 18:20, so the module carried no signature and modinfo -F signer vboxdrv printed nothing. Rebuild, then check that the module now names the host key as its signer.
sudo akmods --force --rebuild
sudo modinfo vboxdrv | grep -E '^(signer|sig_key|sig_hashalgo)'
A signed module shows the akmods key name, which is the host name plus a timestamp.
Checking kmods exist for 7.2.11-300.fc45.x86_64 [ OK ]
Building and installing VirtualBox-kmod [ OK ]
signer: bench01_1792434097_ed8225b4
sig_key: 11:D2:B8:CB:AF:32:3D:3E:35:85:49:ED:73:2F:BD:7F:0A:23:79:93
sig_hashalgo: sha256
Queue the public key for enrollment. mokutil asks for a one-time password twice; you type it again at the firmware screen on the next boot. MokManager reads the keyboard as US QWERTY, so on an AZERTY or QWERTZ layout pick a password made of letters and digits that sit in the same place on both.
sudo mokutil --import /etc/pki/akmods/certs/public_key.der
sudo systemctl reboot
Stay at the console for this reboot. Shim shows a blue “Shim UEFI key management” screen with a 10 second countdown, and if it runs out the request is discarded. On our first attempt the host booted normally, mokutil --list-new came back empty and the import had to be run again. Press a key inside the countdown, then work through MokManager in this order:
- Perform MOK management: select Enroll MOK.
- [Enroll MOK]: select Continue (View key 0 shows the akmods certificate if you want to check it).
- Enroll the key(s)?: move from No to Yes and press Enter.
- Password: type the password you gave
mokutil. - Perform MOK management: select Reboot.
Watch the confirmation screen. It defaults to No, which is where people press Enter out of habit and enroll nothing.

After that reboot the key is enrolled and the three VirtualBox modules load on their own.
sudo mokutil --test-key /etc/pki/akmods/certs/public_key.der
systemctl is-active vboxdrv
lsmod | grep -E "^vbox" | awk '{print $1}'
All three should be listed, and vboxdrv should report active:
/etc/pki/akmods/certs/public_key.der is already enrolled
active
vboxnetadp
vboxnetflt
vboxdrv
Enrollment is a one-time job per host. Every later kernel update gets a module signed with the same akmods key, so you do not repeat MokManager unless you reinstall the host or delete /etc/pki/akmods.
One more observation from the test host: kvm_intel stayed loaded with enable_virt_at_load set to Y, and VirtualBox guests still started next to it. That host was itself a VM, so a bare-metal host may behave differently.
Step 2: Download the Fedora CoreOS OVA
Fedora CoreOS publishes a VirtualBox OVA for each stream, and coreos-installer downloads it and checks its signature in one go. Install it alongside Butane, which turns the YAML config from Step 3 into Ignition JSON. Both are in the Fedora repositories:
sudo dnf install butane coreos-installer
Keep everything for the VM in one directory, then fetch the latest OVA on the stable stream.
mkdir -p ~/fcos && cd ~/fcos
coreos-installer download -s stable -p virtualbox -f ova
Its last lines report a good signature from the Fedora key and the file name, which carries the build number you need in Step 5:
gpg: Signature made Tue 13 Oct 2026 07:32:31 PM UTC
gpg: using RSA key 36F612DCF27F7D1A48A835E4DBFCF71C6D9F90A6
gpg: Good signature from "Fedora (44) <[email protected]>" [ultimate]
./fedora-coreos-44.20260928.3.1-virtualbox.x86_64.ova
On our link the download took 3 minutes 21 seconds for 1.1 GB. The stable stream was still on the previous Fedora release, even on the newer host, because FCOS moves each stream to a new Fedora release on its own schedule; -s next gets the newer base sooner. Stream choice and switching are covered in the Fedora CoreOS on KVM and OpenStack guide.
Step 3: Write the Butane config
FCOS has no installer questions and no default password, so the Butane config is the only way in. This one adds your SSH key to the core user, sets a hostname and runs the Prometheus node exporter as a Quadlet container, which gives you something to measure from the host. If you do not have a key yet, create one with ssh-keygen -t ed25519 and paste the contents of ~/.ssh/id_ed25519.pub in place of ours. Open the file.
vim fcos-vbox.bu
This is the final file. Both guests in the final runs booted with it; the very first boot used PublishPort instead of the host namespace lines, for the reason in the list below.
variant: fcos
version: 1.7.0
passwd:
users:
- name: core
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAtXS0q6lw2xIviUo6nVnsYuRg+jqlOZVOHNPHnF21Mt klinsmann@bench01
storage:
files:
- path: /etc/hostname
mode: 0644
contents:
inline: fcos-vbox01
- path: /etc/containers/systemd/node-exporter.container
mode: 0644
contents:
inline: |
[Unit]
Description=Prometheus node exporter
Wants=network-online.target
After=network-online.target
[Container]
Image=quay.io/prometheus/node-exporter:latest
Network=host
PodmanArgs=--pid=host --uts=host
Volume=/:/host:ro,rslave
Exec=--path.rootfs=/host
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
systemd:
units:
- name: podman-auto-update.timer
enabled: true
Three lines in the Quadlet are there because the first version without them was wrong:
- Network=host and PodmanArgs=–pid=host –uts=host: our first boot used
PublishPort=9100:9100instead, and the exporter reportednodename="1b477df99bc1", the container ID, so every metric was labelled with a name that changes on each restart. With the host namespaces it reportsfcos-vbox01. - Restart=always: Quadlet units do not restart on failure unless you ask, so a crashed exporter would stay down until the next boot.
- AutoUpdate=registry plus podman-auto-update.timer: FCOS updates the OS through Zincati, but containers are yours to update. The timer pulls a newer
:latestimage and restarts the unit.
For a guest you only want to log in to, drop the whole storage.files entry for the Quadlet and the systemd block. Everything else in this guide works the same, and the config shrinks well below the guest property limit.
Step 4: Transpile to Ignition and check the size
Transpile with --strict, which turns a misspelled key into an error instead of a warning, and leave out --pretty. Pretty output is easier to read, but on this config it came to 1,154 bytes against 844, a 310 byte difference, and bytes are what you are short of.
butane --strict fcos-vbox.bu > fcos-vbox.ign
wc -c fcos-vbox.ign
Anything at 1,023 bytes or below fits in the guest property. Ours came to 844:
844 fcos-vbox.ign
Error: VERR_TOO_MUCH_DATA from VBoxManage guestproperty set
The first version of the config (with PublishPort instead of the host namespace lines) came to 1,142 bytes pretty-printed and produced this error. The upstream docs list the host command-line length (about 128 KiB on Linux) as the limit, so we first suspected quoting, but the value itself is the problem:
VBoxManage: error: VERR_TOO_MUCH_DATA
VBoxManage: error: Details: code NS_ERROR_INVALID_ARG (0x80070057), component SessionMachine, interface IMachine, callee nsISupports
VBoxManage: error: Context: "SetGuestPropertyValue(Bstr(pszName).raw(), Bstr(pszValue).raw())" at line 151 of file VBoxManageGuestProp.cpp
To find the exact ceiling we wrote values of known length to a test property on the same powered-off VM:
for n in 1000 1023 1024 1025 1100; do v=$(head -c $n /dev/zero | tr "\0" a); VBoxManage guestproperty set fcos-vbox01 /test/len "$v" >/dev/null 2>&1 && echo "$n bytes: ok" || echo "$n bytes: VERR_TOO_MUCH_DATA"; done; VBoxManage guestproperty delete fcos-vbox01 /test/len
At 1,024 bytes the property is refused every time, so plan your configs around 1,023.
1000 bytes: ok
1023 bytes: ok
1024 bytes: VERR_TOO_MUCH_DATA
1025 bytes: VERR_TOO_MUCH_DATA
1100 bytes: VERR_TOO_MUCH_DATA
Real damage comes when a provisioning script ignores that exit code and starts the VM anyway. The guest boots, Ignition finds no property, carries on with an empty config, and you get a running FCOS that nobody can log in to. If compact output is still over the limit, go straight to Step 7.
Step 5: Import the OVA and attach the Ignition config
Set the VM name and the OVA file name once, using the name coreos-installer printed in Step 2. The import overrides the 4096 MB in the OVA with 2048, which the memory figures above show is enough for this workload.
VM_NAME=fcos-vbox01
OVA=fedora-coreos-44.20260928.3.1-virtualbox.x86_64.ova
VBoxManage import "$OVA" --vsys 0 --vmname "$VM_NAME" --cpus 2 --memory 2048
A successful import ends with one line, and the VM is registered but not started.
Successfully imported the appliance.
A 10 GB OVA disk fills quickly once container images land on it, so grow it before the first boot and FCOS expands the root filesystem into the space. The same block attaches the Ignition config, forwards host port 2222 to SSH and 9100 to the exporter, and sends the serial console to a log file you can read if the first boot goes wrong.
VBoxManage modifymedium disk "$HOME/VirtualBox VMs/$VM_NAME/disk.vmdk" --resize 20480
VBoxManage guestproperty set "$VM_NAME" /Ignition/Config "$(cat fcos-vbox.ign)"
VBoxManage modifyvm "$VM_NAME" --natpf1 "guestssh,tcp,,2222,,22" --natpf1 "nodeexp,tcp,,9100,,9100"
VBoxManage modifyvm "$VM_NAME" --uart1 0x3F8 4 --uartmode1 file "$HOME/fcos/$VM_NAME.log"
guestproperty set prints nothing on success, so check the screen for the VERR_TOO_MUCH_DATA error before going further. Start the VM without a window.
VBoxManage startvm "$VM_NAME" --type headless
VirtualBox Manager shows the imported VM as well, so you can open its console from there, but the guest property itself can only be set from the command line. Ignition reads it on the first boot only. If you start the VM before setting it, power the guest off, delete it with VBoxManage unregistervm "$VM_NAME" --delete and import again, because a second boot will not run Ignition.
Step 6: Log in and confirm Ignition ran
SSH answered 76 seconds after startvm on both of our runs, and systemd-analyze put 54 of those seconds on the boot itself, 26.7 of them in the initrd where Ignition runs. Log in through the forwarded port.
ssh -p 2222 core@localhost
Inside the guest, the Ignition journal shows where the config came from and what it wrote. Check it once on every new VM.
sudo journalctl -b -t ignition --no-pager | grep -iE "guest property|writing file"
A healthy first boot reads the guest property early in the initrd, writes the hostname and the Quadlet, and about 12 seconds later removes the property again:
Oct 19 18:49:48 localhost ignition[566]: reading Ignition config from VirtualBox guest property
Oct 19 18:50:01 localhost ignition[870]: files: createFilesystemsFiles: createFiles: op(3): [started] writing file "/sysroot/etc/hostname"
Oct 19 18:50:01 localhost ignition[870]: files: createFilesystemsFiles: createFiles: op(3): [finished] writing file "/sysroot/etc/hostname"
Oct 19 18:50:01 localhost ignition[870]: files: createFilesystemsFiles: createFiles: op(4): [started] writing file "/sysroot/etc/containers/systemd/node-exporter.container"
Oct 19 18:50:01 localhost ignition[870]: files: createFilesystemsFiles: createFiles: op(4): [finished] writing file "/sysroot/etc/containers/systemd/node-exporter.container"
Oct 19 18:50:01 localhost ignition[870]: files: createResultFile: createFiles: op(9): [started] writing file "/sysroot/etc/.ignition-result.json"
Oct 19 18:50:01 localhost ignition[870]: files: createResultFile: createFiles: op(9): [finished] writing file "/sysroot/etc/.ignition-result.json"
Oct 19 18:50:13 fcos-vbox01 ignition[1092]: deleting Ignition config from VirtualBox guest property
That deletion matters if you ever put anything sensitive in a config. On the host, VBoxManage guestproperty get fcos-vbox01 /Ignition/Config returned No value set! after that first boot, so the config does not stay readable to anyone who can list the VM properties. Everything else can be checked from the host, without an interactive session:
VBoxManage list runningvms
ssh -p 2222 core@localhost "hostnamectl hostname; systemctl is-active node-exporter"
ssh -p 2222 core@localhost "rpm-ostree status --booted | grep -E 'Version|ostree-image'"
curl -s localhost:9100/metrics | grep -o '^node_uname_info{[^}]*nodename="[^"]*"'
Run on our host with both guests up, the four checks printed this:

Our guest booted from the signed stable container image shown above, so updates arrive as signed images and Zincati applies them on its own (rpm-ostree status in the guest lists Zincati as the active update driver). The nodename label confirms the host namespaces from Step 3 took effect. Layering packages and rolling back work as on any rpm-ostree system, which the rpm-ostree package management guide walks through with real output.
Step 7: Load larger configs with a pointer config over bridged networking
When the real config is over 1,023 bytes, the guest property carries a small pointer config instead, and Ignition fetches the full one over the network during first boot. We tested it with bridged networking, which also gives the guest its own address on the LAN, and a plain web server on the host. Build a full config that is deliberately over the limit (the same file, pretty-printed, with a new hostname), then record its SHA-512 so Ignition can verify what it downloads:
sed "s/fcos-vbox01/fcos-vbox02/" fcos-vbox.bu > fcos-vbox02.bu
butane --pretty --strict fcos-vbox02.bu > fcos-vbox02.ign
wc -c fcos-vbox02.ign
sha512sum fcos-vbox02.ign | cut -d" " -f1
At 1,154 bytes this file would fail the guest property, which is the point of the test.
1154 fcos-vbox02.ign
137778020e4c232c6c00c00f4e9aeeda0b6a9ef26a134af7427404b4e8fbcfb2054fa74c04417f4b0babd7ee5d92f48943b2a9cf6d4692ed4d894e106abcc1b1
Serve the directory from the host in a second terminal. Our host had no firewalld running. The default FedoraWorkstation zone already allows ports above 1024, but on Fedora Server, or any host whose default zone does not allow 8080, open it first with sudo firewall-cmd --add-port=8080/tcp (runtime only, gone at the next reboot), or the fetch from the guest will be refused. The server shares the whole directory, OVA included, so stop it as soon as the guest is up.
cd ~/fcos && python3 -m http.server 8080 --bind 0.0.0.0
Now write the pointer config. Use the LAN address of the host in source, which is 10.0.1.50 in this example, and paste the hash from above after sha512-.
vim pointer.bu
Eight lines are enough, because the pointer only names the full config and its digest. Everything the guest ends up with comes from the file it downloads.
variant: fcos
version: 1.7.0
ignition:
config:
replace:
source: http://10.0.1.50:8080/fcos-vbox02.ign
verification:
hash: sha512-137778020e4c232c6c00c00f4e9aeeda0b6a9ef26a134af7427404b4e8fbcfb2054fa74c04417f4b0babd7ee5d92f48943b2a9cf6d4692ed4d894e106abcc1b1
Plain HTTP is acceptable here only because of the hash, since Ignition refuses a file whose digest does not match, so nobody on the LAN can swap the config. The file itself still travels in clear text, so keep secrets out of it or serve it over HTTPS. Transpile the pointer and check that it is small.
butane --strict pointer.bu > pointer.ign
wc -c pointer.ign
At 271 bytes the pointer leaves about 750 bytes of headroom, which is enough for a long HTTPS URL:
271 pointer.ign
Import a second VM, attach the pointer, and switch its first adapter from NAT to bridged on the default host interface. NAT would also work, because a NAT guest can reach the host at 10.0.2.2, but bridging is the setup you want when the guest has to be reachable from other machines.
VM_NAME=fcos-vbox02
OVA=fedora-coreos-44.20260928.3.1-virtualbox.x86_64.ova
VBoxManage import "$OVA" --vsys 0 --vmname "$VM_NAME" --cpus 2 --memory 2048
VBoxManage guestproperty set "$VM_NAME" /Ignition/Config "$(cat pointer.ign)"
ADAPTER=$(ip route ls default | grep -Po "(?<= dev )(\S+)")
VBoxManage modifyvm "$VM_NAME" --nic1 bridged --bridgeadapter1 "$ADAPTER"
VBoxManage modifyvm "$VM_NAME" --uart1 0x3F8 4 --uartmode1 file "$HOME/fcos/$VM_NAME.log"
VBoxManage startvm "$VM_NAME" --type headless
In the web server terminal, one request arrives from the new guest address and gets a 200.
10.0.1.51 - - [19/Oct/2026 18:54:47] "GET /fcos-vbox02.ign HTTP/1.1" 200 -
On the host, the serial log shows how Ignition got there.
grep -aE "ignition\[[0-9]+\]: GET" fcos-vbox02.log
Attempts one to three fail because DHCP has not finished on the bridged adapter. Ignition keeps retrying, and the fourth attempt, about 0.6 seconds after the first, succeeds:
[ 18.254190] ignition[623]: GET http://10.0.1.50:8080/fcos-vbox02.ign: attempt #1
[ 18.334539] ignition[623]: GET error: Get "http://10.0.1.50:8080/fcos-vbox02.ign": dial tcp 10.0.1.50:8080: connect: network is unreachable
[ 18.417995] ignition[623]: GET http://10.0.1.50:8080/fcos-vbox02.ign: attempt #2
[ 18.506135] ignition[623]: GET error: Get "http://10.0.1.50:8080/fcos-vbox02.ign": dial tcp 10.0.1.50:8080: connect: network is unreachable
[ 18.650400] ignition[623]: GET http://10.0.1.50:8080/fcos-vbox02.ign: attempt #3
[ 18.743815] ignition[623]: GET error: Get "http://10.0.1.50:8080/fcos-vbox02.ign": dial tcp 10.0.1.50:8080: connect: network is unreachable
[ 18.863392] ignition[623]: GET http://10.0.1.50:8080/fcos-vbox02.ign: attempt #4
[ 18.957150] ignition[623]: GET result: OK
The web server log already showed the guest address, and the serial log prints it on the login banner:
grep -a "enp0s3:" fcos-vbox02.log
The second line is the one you want:
[ 14.106681] virtio_net virtio0 enp0s3: renamed from eth0
enp0s3: 10.0.1.51 fe80::d01d:cd1b:32cb:9786
SSH to that address directly on port 22, since there is no port forward on a bridged adapter:
ssh [email protected] "hostnamectl hostname; systemctl is-active node-exporter"
A new hostname proves the guest is running the full config from the web server, not the pointer.
fcos-vbox02
active
Stop the web server once the guest is up. Ignition only fetches the config on first boot, so nothing on the guest depends on it afterwards. If you also run FCOS on VMware, the VMware Workstation setup passes Ignition through guestinfo properties instead.
First-boot messages in the serial log, and what they mean
Your serial log from Step 5 is the only record of a first boot that went wrong, because a guest without your SSH key gives you nothing else to look at. Below are the lines we saw, and what each one told us.
VirtualBox guest property “/Ignition/Config” does not exist; assuming no config
Ignition looked for the property and found nothing, so it booted the guest with only the built-in base config. The console then prints Ignition: no config provided by user and stops at localhost login:, with no hostname from your config and no way in. On our box it came from the VERR_TOO_MUCH_DATA failure in Step 4; the other common cause is starting the VM before running guestproperty set. Either way the disk has already had its first boot, so delete the VM and import the OVA again. If you had logged in to an earlier guest on the same forwarded port, also clear its old host key with ssh-keygen -R "[localhost]:2222", or the next SSH login stops with a host identification warning.
VBoxManage controlvm fcos-vbox01 poweroff
VBoxManage unregistervm fcos-vbox01 --delete
GET error: dial tcp … connect: network is unreachable
Seen with a pointer config, a few of these in the first second are normal: Ignition starts before DHCP has an address and retries until it does. It only becomes a problem if the attempts keep climbing with no GET result: OK, which points at the network rather than the config. Check that the bridged adapter name matches the host’s real interface and that the host’s web server is reachable from another machine on the LAN.
reading Ignition config from VirtualBox guest property
You want to see this line early in the initrd. If it appears and the guest still lacks your SSH key, the problem is inside the config itself, usually a key pasted into the wrong user or a Butane warning ignored without --strict. Fix the .bu file, transpile again, and recreate the VM, since Ignition will not run a second time on the same disk.