Your package manager is current. Unattended upgrades are green. Containers rebuild nightly. Then a laptop BIOS advisory lands for a DMA or SMM bug, your NVMe firmware has a known data-loss fix, and the Thunderbolt dock is still shipping last year’s controller blob.
None of that shows up in apt upgrade.
fwupd is the Linux firmware update daemon. Combined with the Linux Vendor Firmware Service (LVFS) at fwupd.org, it discovers devices, downloads signed metadata and cabinet archives, and deploys updates through vendor protocols (UEFI capsules, DFU, NVMe, HID, Redfish, and many others). Desktop users often meet it through GNOME Software or KDE Discover. On servers and headless hosts, fwupdmgr is the tool you actually want.
This guide is the practical path: install, inventory, refresh, update, verify platform security, automate with care, and handle offline or approval-gated fleets — all from documented interfaces, not folklore.
Why firmware is a separate patch channel
OS packages cover userspace and (usually) the kernel. Firmware lives elsewhere:
- System firmware (UEFI/BIOS) on SPI flash
- Device firmware on SSDs, NICs, docks, webcams, USB controllers, BMCs
- Optional microcode and management-engine style payloads, depending on vendor packaging
Vendors increasingly publish those payloads to the LVFS as signed .cab archives with AppStream metadata. fwupd matches archive GUIDs to local hardware, enforces trust policy, and runs the right plugin write path. That is a different trust and rollout model from Debian/Ubuntu package repos — treat it as one.
What you need
| Piece | Notes |
|---|---|
| Root or PolicyKit-authorized user | Daemon talks over D-Bus; installs need elevated rights |
fwupd + fwupdmgr
|
Packaged on Debian/Ubuntu, Fedora, RHEL-family, Arch, openSUSE |
| Network egress | HTTPS to LVFS (or your mirror) for metadata and cabinets |
| ESP free space (UEFI capsule path) | Capsule updates stage files on the EFI System Partition |
| AC power for many devices | fwupd can refuse updates on battery unless you override policy |
| A maintenance window | Some updates need reboot, shutdown, or device re-plug |
Graphical front ends (GNOME Firmware, GNOME Software, KDE Discover, vendor apps) call the same daemon. Everything below uses the CLI so it works on a jump host over SSH.
Install and start fwupd
Debian / Ubuntu:
sudo apt update
sudo apt install fwupd
sudo systemctl enable --now fwupd.service
Fedora:
sudo dnf install fwupd
sudo systemctl enable --now fwupd.service
Arch:
sudo pacman -S fwupd
sudo systemctl enable --now fwupd.service
Confirm the client can reach the daemon:
fwupdmgr --version
systemctl is-active fwupd.service
On many desktops the daemon is socket- or D-Bus-activated and idles out (IdleTimeout in fwupd.conf). That is normal; the next fwupdmgr call brings it back.
Inventory: what can this machine update?
List devices fwupd can see:
fwupdmgr get-devices
Useful variants:
# Machine-readable for scripts / CMDB
fwupdmgr get-devices --json
# Filter examples (flags vary slightly by fwupd version)
fwupdmgr get-devices --filter updatable
You should see entries such as System Firmware, drives, docks, and USB devices, each with a version string and one or more GUIDs. No updatable devices does not always mean “broken fwupd” — it often means the OEM never published firmware for that SKU on the LVFS, or a plugin needs a newer fwupd.
Hardware IDs (CHIDs / HWIDs) used for matching:
fwupdmgr hwids
# or, for offline/debug tooling:
fwupdtool hwids
Refresh metadata, then look for updates
fwupd does not continuously scrape the LVFS. You (or a timer/GUI) refresh signed metadata from enabled remotes:
# Download latest metadata from enabled remotes
sudo fwupdmgr refresh
# Force refresh even if the cache still looks fresh
sudo fwupdmgr refresh --force
See configured remotes:
fwupdmgr get-remotes
Typical remote IDs:
-
lvfs— stable public firmware (enable this) -
lvfs-testing— testing stream (opt-in only) -
vendor/vendor-directory— local/OEM bundles - custom mirror remotes you add under
/etc/fwupd/remotes.d/
Enable or disable explicitly:
sudo fwupdmgr enable-remote lvfs
# Only if you intentionally want pre-stable firmware:
# sudo fwupdmgr enable-remote lvfs-testing
Remote files live in /etc/fwupd/remotes.d/ (for example lvfs.conf). From fwupd-remotes.d(5), important knobs include:
| Key | Meaning |
|---|---|
Enabled= |
Whether refresh/update consider this remote |
MetadataURI= |
AppStream metadata URL (https:// or file://) |
FirmwareBaseURI= |
Optional separate base URL for .cab downloads |
ApprovalRequired= |
If true, only checksums on the approved list are installable from that remote |
AutomaticReports= |
Upload success/failure reports after updates |
RefreshInterval= |
How long front ends may treat metadata as fresh |
List available upgrades:
fwupdmgr get-updates
# alias:
fwupdmgr get-upgrades
Inspect every known release for one device (older, current, newer):
fwupdmgr get-releases DEVICE_ID_OR_GUID
Search metadata even when the device is not plugged in:
fwupdmgr search "CVE-2022-21894"
fwupdmgr search Framework
Apply updates safely
The happy path for interactive hosts:
sudo fwupdmgr refresh
fwupdmgr get-updates
sudo fwupdmgr update
update walks each device with a newer release, prompts for confirmation (unless you pass non-interactive flags supported by your version), writes firmware, and may request a reboot.
Target a single device:
sudo fwupdmgr install DEVICE_ID
# or install/reinstall/downgrade flows when the remote allows them:
sudo fwupdmgr reinstall DEVICE_ID
sudo fwupdmgr downgrade DEVICE_ID
Local cabinet (air-gapped laptop, vendor USB stick, change ticket attachment):
sudo fwupdmgr local-install ./firmware.cab
# many builds also accept:
sudo fwupdmgr install ./firmware.cab
Inspect a .cab without applying it:
fwupdmgr get-details ./firmware.cab
After UEFI capsule style updates:
fwupdmgr check-reboot-needed
# then reboot when maintenance allows
sudo systemctl reboot
Operational habits that prevent pain:
-
Plug in AC power before system firmware or large device flashes.
IgnorePower=falseis the default infwupd.conffor a reason. - Do not yank docks/USB devices mid-write unless the release notes say “update on disconnect.”
-
Keep ESP space free for capsule payloads.
RequireESPFreeSpaceunder[uefi_capsule]can enforce a floor; by default fwupd wants enough room for the payload with margin. -
Read the release notes in
get-updates/get-releases— some updates clear settings, need a second pass, or only activate after shutdown rather than reboot. -
Keep
OnlyTrusted=trueon production machines. That setting (daemon default) refuses firmware not signed with a trusted key.
Automate without turning firmware into unattended roulette
Desktop environments already schedule metadata refresh. On servers, be deliberate.
Manual or ticket-driven (recommended default)
#!/usr/bin/env bash
set -euo pipefail
fwupdmgr refresh
fwupdmgr get-updates --json > "/var/log/fwupd/updates-$(date -u +%Y%m%d).json"
# Human or orchestration decides whether to run:
# fwupdmgr update --assume-yes # flag name varies; check fwupdmgr update --help on your version
Always check fwupdmgr update --help on the installed package before scripting confirmation flags — CLI stability is not guaranteed across major versions; --json is the parsing contract called out in fwupdmgr(1).
systemd timer for refresh only
Refreshing metadata is low risk. Auto-flashing BIOS overnight is not. A reasonable split:
# /etc/systemd/system/fwupd-refresh-lvfs.service
[Unit]
Description=Refresh fwupd LVFS metadata
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/fwupdmgr refresh
# /etc/systemd/system/fwupd-refresh-lvfs.timer
[Unit]
Description=Weekly fwupd metadata refresh
[Timer]
OnCalendar=weekly
Persistent=true
RandomizedDelaySec=1h
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now fwupd-refresh-lvfs.timer
Pair the timer with monitoring: parse fwupdmgr get-updates --json and open a ticket when non-empty. Apply during a change window with fwupdmgr update.
Some distributions ship their own fwupd refresh timer units — prefer the distro unit when present, and avoid double refresh storms against LVFS. The LVFS documents download limits and expects well-behaved user agents; do not build a parallel scraper.
Enterprise controls: approvals, branches, offline
Approved firmware allow-list
When a remote sets ApprovalRequired=true, only cabinet checksums you allow-list are offered as installable:
# SHA-1 or SHA-256 of the .cab archive
sudo fwupdmgr set-approved-firmware CHECKSUM1,CHECKSUM2
# fwupd >= 1.7.1 can load checksums from a file
sudo fwupdmgr set-approved-firmware /etc/fwupd/approved-firmware.txt
fwupdmgr get-approved-firmware
This is how you mirror LVFS metadata broadly but still stage only QA-signed builds to laptops.
Offline / air-gapped patterns (from LVFS docs)
-
Vendor directory in the image: drop
.cabfiles into/usr/share/fwupd/remotes.d/vendor/firmware, runfwupdmgr enable-remote vendor-directory, and optionallyfwupdmgr disable-remote lvfs. -
Internal HTTPS mirror: sync with Pulp or the LVFS
sync-pulp.pyhelper againstPULP_MANIFEST, export metadata + cabinets, point a custom remote’sMetadataURI/FirmwareBaseURIat your mirror. -
Manual:
fwupdmgr install foo.cabfrom change-ticket artifacts (Ansible, Puppet, etc.).
Example custom remote sketch:
# /etc/fwupd/remotes.d/myprivateserver.conf
[fwupd Remote]
Enabled=true
MetadataURI=https://fw.example.com/mirror/firmware.xml.zst
FirmwareBaseURI=https://fw.example.com/mirror
(If your fwupd/libxmlb build lacks zstd metadata support, use a .xml.xz metadata URI as documented by LVFS.)
Best Known Configuration (BKC)
Sites that pin a known-good set of firmware versions can use BKC tags with HostBkc= in fwupd.conf and fwupdmgr sync so machines converge to the tagged set (including deliberate downgrades when policy requires it). Use this only with a written firmware baseline — it is a fleet tool, not a laptop convenience switch.
Firmware branches
Some devices publish multiple branches (for example vendor vs community). Switching is explicit:
sudo fwupdmgr switch-branch DEVICE_ID
LVFS policy requires a conscious opt-in for alternate branches; do not script this casually on production fleets.
Platform security: fwupdmgr security
fwupd can evaluate Host Security ID (HSI) attributes — BootGuard, SPI locks, Secure Boot state, capsule update enablement, and many CPU/OEM-specific checks — without attaching a programmer or loading unsigned kernel modules:
fwupdmgr security
# newer builds may offer guided remediation:
# fwupdmgr security-fix
HSI levels (simplified from the HSI specification):
| Level | Rough meaning |
|---|---|
| HSI:0 | Insecure / insufficient measurable protections |
| HSI:1 | Minimum critical protections present |
| HSI:2 | Harder local attacks mitigated |
| HSI:3 | Stronger firmware protection / recovery posture |
| HSI:4 | Robust secure state (e.g. memory encryption class controls) |
| HSI:5 | Attested / proven (tests not generally available yet) |
Runtime suffixes (for example !) flag OS-side problems such as Secure Boot off, tainted kernel, missing lockdown, or unencrypted swap. HSI is not a marketing score you chase blindly — it is a structured checklist. Use it to open OEM tickets (“capsule updates disabled in firmware setup”) and to compare laptop SKUs before bulk purchase.
Optional BIOS setting access where supported:
fwupdmgr get-bios-settings
# sudo fwupdmgr set-bios-setting KEY VALUE
Daemon policy worth knowing
Main file: /etc/fwupd/fwupd.conf (fwupd.conf(5)). Prefer drop-in style edits only if your package documents them; many installs still use the single conf file. Notable keys under [fwupd]:
| Key | Why it matters |
|---|---|
OnlyTrusted=true |
Production default — do not disable to “make it work” |
IgnorePower=false |
Keep false unless you accept brick risk on low battery |
IgnoreRequirements=false |
Development escape hatch; not for fleets |
ApprovedFirmware= |
Static allow-list alternative to set-approved-firmware
|
HostBkc= |
Comma-separated BKC tags for fwupdmgr sync
|
EspLocation= |
Override ESP path if UDisks mis-detects it |
DisabledDevices= / DisabledPlugins=
|
Block broken devices/plugins by GUID or name |
UpdateMotd=true |
Surface pending firmware in MOTD |
P2pPolicy= |
Optional peer caching via Passim (nothing / metadata / firmware) |
Under [uefi_capsule]:
| Key | Why it matters |
|---|---|
EnableGrubChainLoad= |
Use GRUB chainload helper instead of NVRAM/Capsule-on-Disk paths |
DisableShimForSecureBoot= |
Only if you knowingly self-sign fwupd.efi
|
RequireESPFreeSpace= |
Enforce minimum ESP free space (MiB) |
RebootCleanup=true |
Clean staged capsule artifacts after success |
Redfish-capable servers can point [redfish] at a BMC URI with credentials for out-of-band device coverage — useful in datacenters where the OS is a guest on managed hardware.
Verification and history
# What happened last?
fwupdmgr get-history
fwupdmgr get-results DEVICE_ID
# Export or upload reports (privacy: know what you share)
fwupdmgr report-export
# fwupdmgr report-history
# After a capsule update and reboot, confirm versions moved
fwupdmgr get-devices | less
Exit codes from fwupdmgr(1): 0 success with work done, 2 success with nothing to do, 3 resource not found, 1 generic failure. Use that in timers so “no updates” is not an alert storm.
Troubleshooting quick hits
| Symptom | Things to check |
|---|---|
| No devices |
fwupd running? Virtual machine without passthrough? Container without hardware access? |
| No updates ever |
get-remotes enabled? refresh succeeded? OEM on LVFS? Try search by model |
| UEFI update fails | Capsule updates enabled in firmware setup; ESP mounted and writable; Secure Boot/shim path; free ESP space |
| Blocked on battery | Plug in AC; avoid IgnorePower=true except lab experiments |
| “Not trusted” | Keep OnlyTrusted=true; fix signatures/remotes instead of disabling trust |
| Needs activation | Some devices need fwupdmgr activate or a re-plug/shutdown cycle |
| Want older fwupd behavior logs |
journalctl -u fwupd -b; daemon --verbose / plugin verbose flags for deep dives |
Secure Boot environments need a coherent chain (shim → fwupd EFI helper → capsule). If you custom-sign bootloaders, read the [uefi_capsule] notes before turning off shim.
A minimal weekly checklist
# 1) Metadata
sudo fwupdmgr refresh
# 2) Pending firmware
fwupdmgr get-updates
# 3) Platform posture (laptops / workstations)
fwupdmgr security
# 4) Apply during a window if needed
# sudo fwupdmgr update
# fwupdmgr check-reboot-needed && sudo systemctl reboot
That is enough to stop treating SPI flash and SSD blobs as immortal.
What this does not replace
-
Kernel/userspace CVEs — still your distro security stream and
unattended-upgrades/ dnf-automatic - Full Secure Boot key management and custom MOK enrollment — related, but a different playbook
- BMC/ILO/iDRAC vendor tooling — Redfish plugin helps when configured; some out-of-band stacks remain vendor-specific
- Device recovery after a hard-bricked flash — have vendor recovery docs before you mass-roll BIOS
fwupd narrows the gap between “Linux is patched” and “the firmware the CPU actually runs is patched.”
References
- fwupd project / LVFS portal
- fwupdmgr(1) manual
- fwupd.conf(5) manual
- fwupd-remotes.d(5) manual
- LVFS offline and mirroring documentation
- LVFS download client guidance
- Host Security ID specification
- fwupd plugin tutorial (architecture background)
Firmware CVEs do not care that your container image is immutable. Wire fwupd into the same operational muscle memory as package updates — inventory, refresh, review, apply, verify — and the silent half of your attack surface gets a lot quieter.
Top comments (0)