Supported customizations
What FOG preserves for you across an install or update, what it deliberately does not, and where to put things so they survive.
Note
This describes FOG 1.6. Little of it applies to 1.5, which has no
customizations/directory, no vhost managed block and no iPXE manifest.It used to live as
docs/SUPPORTED_CUSTOMIZATIONS.mdin the code repository. That path still exists there, as a pointer to this page: theFOG MANAGED BLOCKmarker written into every generated vhost names it, and that marker cannot be changed without every existing server’s block ceasing to be recognised.
How to read this document
Automatic means installfog.sh preserves it on every run with no action
from you. That includes a bare ./installfog.sh upgrade — it is not limited
to updates driven through bin/updatefog.sh.
Supported, but yours to place means FOG will not overwrite it and provides a defined place for it, but does not create or manage it.
Adopted means you put a file in a defined place and FOG wires it up on the
next installfog.sh run, with nothing to configure. Certificates you bring
work this way.
Not preserved means exactly that. Those cases are listed at the end rather than left for you to discover.
“Yours to place” is about names, not locations. A file survives because what FOG saves is everything in the live directory that the shipped source tree does not contain — so it is the name that earns the protection, not where you put it.
Warning
If you give your file the same name as one FOG ships, FOG wins. Your
bzImageis not yours; that is the name of the kernel FOG installs, and it is replaced on every run. Use a name of your own, or mark the file to keep.
The two customizations directories
There are two. They are not interchangeable, and they run in opposite
directions. Each carries a readme.txt saying which is which.
/opt/fog/customizations/ | /etc/fog/customizations/ | |
|---|---|---|
| Written by | FOG | you |
| What it is | copies FOG makes of your files before it rebuilds the tree they live in, and restores from afterward | an input FOG only ever reads |
| Conflict rule | restores by absence — a saved file is put back only when the rebuilt tree no longer has it | adopts by presence — what you put there overrides what FOG would otherwise generate |
| Holds | ipxe-bg/, ipxe-legacy/, kernel-backups/ | pki/ |
Strictly the first is $fogprogramdir/customizations. It sits outside the web
root, which is why what it holds survives — the installer rebuilds
/var/www/html/fog wholesale on every run. Everything listed as automatic
below is copied there and back.
Why two rather than one. Certificates and private keys are small, secret
and irreplaceable, so they belong under /etc — what a backup policy and a
config-management run already capture. Kernels and boot images are large,
rebuildable binaries, and the filesystem standard does not put binaries under
/etc. FOG’s own PKI moved to /etc/fog/pki for the same reason.
The conflict rules are the deeper reason, and they are why one directory could not do both jobs. Adopting by presence would let a backup FOG made for itself outrank a freshly shipped file; restoring by absence would let a certificate you deliberately placed be ignored the moment FOG shipped one of the same name. One rule each is what keeps “FOG’s copy” and “yours” distinguishable.
Note
Each
readme.txtis FOG’s own note and a later version may rewrite it, so that it can correct itself when something changes. Edit one and FOG leaves it alone from then on, permanently.
iPXE boot menu background
Automatic, including when you have renamed the file.
FOG_IPXE_BG_FILE (Web UI → FOG Configuration → iPXE Menu Settings) names
the background image. FOG reads that setting’s actual value, so a renamed
file is protected — not just the stock bg.png.
| Customization | How it is preserved | Where the copy lives |
|---|---|---|
Replaced bg.png in place | Backed up before the web tree is rebuilt, restored after | /opt/fog/customizations/ipxe-bg/bg.png |
Renamed background via FOG_IPXE_BG_FILE | Same, under whatever name the setting holds | /opt/fog/customizations/ipxe-bg/<yourname>.png |
Legacy refind.* files | Backed up and restored if present | /opt/fog/customizations/ipxe-legacy/ |
Place the image in <webroot>/service/ipxe/ and set FOG_IPXE_BG_FILE to
its filename. A path is not accepted — only a filename.
If FOG finds your background but cannot copy it to safety, the install stops before rebuilding the web tree, with your file still untouched. That is deliberate: aborting is recoverable, proceeding is not.
Web server virtual host (Apache / nginx)
Automatic for anything outside FOG’s block. FOG owns what is between the markers and will rewrite it every run.
The generated vhost is wrapped in:
# === FOG MANAGED BLOCK -- DO NOT EDIT BETWEEN THESE LINES ... ===
... everything FOG generates ...
# === END FOG MANAGED BLOCK ===
Put your own directives outside those markers — above or below — and they survive every update untouched. FOG refreshes only the inside, which is how you keep getting its cipher, header and rewrite-rule fixes without losing your own configuration.
| Customization | How it is preserved |
|---|---|
Extra directives, headers, location/Directory blocks | Keep them outside the markers; never touched |
| Extra hostnames / DNS aliases | Use --extra-server-name (repeatable) so they land in both the vhost and the certificate SAN |
| Primary hostname | Use --hostname; remembered in the `.fogsettings` file |
| Custom certificate paths | Point the vhost’s cert directives at your paths outside the block, or replace the files FOG already references |
On the first run after upgrading into this scheme, a vhost with no markers is appended to, never overwritten — your existing file is left in place with FOG’s block added after it. Review it once and move anything you want FOG to stop managing outside the markers.
--no-vhost (installfog.sh -F) still skips the vhost entirely. It is
rarely what you want now: it also skips FOG’s own security fixes to the parts
it owns.
Kernels and inits (bzImage, init.xz, …)
Default-named files are replaced on purpose. Custom-named files are preserved automatically. Previous versions are kept for you to roll back to.
Picking up a newer kernel is the point of an update, so bzImage,
bzImage32, arm_Image, init.xz, init_32.xz and arm_init.cpio.gz are
always replaced with the release being installed.
| Customization | How it is preserved |
|---|---|
| Per-host custom kernel/init (a host’s Host Kernel / Host Init fields) | Restored automatically — FOG never re-downloads a file it did not ship |
| Previously working kernel set | Kept as a numbered generation; restore with bin/restorekernel.sh in your FOG checkout |
./restorekernel.sh --list # what is stored, and which release each came from
./restorekernel.sh --generation 1 # roll back to the set from before the last updategen-1 is the most recent snapshot, taken at the start of the last
install — so it holds what was running before it. Three generations are
kept by default; change that with installfog.sh --kernel-backup-count N.
Restoring re-signs the kernels if Secure Boot is configured, since a restored kernel carries its old signature and the signing key may have rotated.
Marking a boot file to keep
The kernel and init update pages list what is actually on disk, with a Keep
button on each row. Marking a file copies it into
/opt/fog/customizations/kernel-backups/keep/, and a later run puts it back if
it has gone missing from the live tree. Unmark it and the copy is removed.
This is the one thing under /opt/fog/customizations written on your
instruction rather than as part of a rebuild. FOG still does the writing, so it
stays a directory you never need to touch by hand.
It exists because two reasonable-sounding expectations do not hold on their own:
- A per-release sibling such as
bzImage.5.15.0is deliberately not part of a numbered generation — a generation would multiply the same bytes by the generation count — so without a mark it survives only until the next upgrade. - Marking one of the six default names achieves close to nothing, since picking up the new kernel is the point of an update. Mark the sibling, or a file under a name of your own.
FOG decides what a boot file is by reading its header rather than its name, so a hand-compiled kernel under any name appears in the Host Kernel dropdown with nothing to configure. Being found and being kept are separate questions: that answers the first, and the naming rule or a mark answers the second.
Web certificates you bring
Adopted. Put the pair in /etc/fog/customizations/pki/ under these two
names and re-run the installer:
/etc/fog/customizations/pki/web-leaf.pem
/etc/fog/customizations/pki/web-leaf.key
That is the whole procedure. FOG finds them, points its own canonical paths at
them, and stops re-issuing that certificate — there is nothing to set in
.fogsettings and no symlink to make. The installer says so when it happens:
* Detected a web certificate managed outside FOG:
/etc/fog/customizations/pki/web-leaf.pem is a certificate you supplied, with a matching key
* FOG will keep managing this vhost, but will not re-issue or
re-key that certificate.
FOG goes on managing the rest of the vhost — the HTTPS redirect, the iPXE exclusions, HSTS — because nobody wants to hand-maintain those. Only the certificate becomes yours.
It has to be beside FOG's PKI tree, not inside it
/etc/fog/customizations/pki/is a sibling of/etc/fog/pki/, and that is what makes the whole thing work rather than being a tidiness preference. FOG decides “is this leaf mine or the admin’s” by asking whether the path resolves inside its own web zone. A certificate written into/etc/fog/pki/web/leaf/is read as one FOG issued, and the next run regenerates over it.Note that
/opt/fog/pkiis a symlink to/etc/fog/pki, so those two are the same directory — writing to either is writing inside FOG’s tree.
Both files are required, and they have to be a genuine pair. FOG compares the certificate’s public key against the private key. If the key is missing or does not match, the pair is not adopted and FOG carries on with its own certificate — which is deliberate: adopting a mismatched pair would point the web server at a certificate it cannot start with, whereas declining leaves a server that still serves.
Only those two filenames are looked for. FOG does not guess among other names, because guessing wrong would repoint your vhost without telling you. If your files are named something else, or live somewhere else entirely, record the paths instead — see .fogsettings and the PKI zones reference.
Renewal is yours. FOG never renews a certificate it did not issue. Point your ACME client’s install hook or reload command at these paths and let it rewrite them in place; FOG re-reads them on each run and leaves them alone. A worked example, including Cloudflare DNS-01, is in Set up Let’s Encrypt on a FOG server.
FOG also leaves the permissions on your private key alone, so a renewal hook running as something other than root keeps working.
Secure Boot certificates
Automatic, for both FOG-generated and admin-supplied keys.
FOG’s own signing key is generated once at
/opt/fog/pki/secureboot/MOK.{key,pem} (or, once a Secure Boot intermediate
CA exists, /opt/fog/pki/secureboot/leaf/sign.{key,pem}) and never
regenerated, because a new key silently invalidates enrollment on every
machine that already trusted the old one.
| Customization | How it is preserved |
|---|---|
| FOG’s generated signing key | Lives outside the web root; nothing in the installer deletes it |
Your own key via --secure-boot-key / --secure-boot-cert | Copied to /opt/fog/pki/secureboot/admin-MOK.{key,pem} and used from there |
| Platform keys (PK/KEK) | Same; generated once, never regenerated |
Supplying your own pair does not overwrite FOG’s generated one — they sit side by side, so you can go back. Your original file is never modified; FOG uses a copy. This matters if you keep the pair somewhere the installer rebuilds, such as under the web root: without the copy it would be deleted mid-install.
Note
--no-secure-bootdeclines enrollment, not signatures. It stops the server publishingMOK.derand thePK/KEK/dbvariable updates, and with them the Enroll Secure Boot Key PXE menu entry, which is gated onMOK.derexisting. The signing key is still generated and the binaries are still signed.That is deliberate: an appended PE signature is inert on a machine booting with Secure Boot off — which is every machine on a server that passed this flag — so signing costs nothing. Leaving the binaries unsigned instead would only mean that the day you do enroll, or move one of these files onto a machine that already has Secure Boot on, the file is useless and nothing on the server can fix it short of a re-install.
Local ESP boot files
Not a customization point — generated archives, rebuilt on every run.
Machines whose firmware has no PXE boot option, and machines you would rather not reorder the boot menu on for every task, can be booted into FOG from an iPXE binary on their own EFI System Partition. The installer publishes ready-to-copy archives for that here, so you can fetch one over HTTP instead of hand-rolling a symlink from the TFTP tree into your web root:
<webroot>/service/localboot/
manifest.json index of everything below
fog-esp-x86_64.zip
fog-esp-i386.zip
fog-esp-arm64.zip
One archive per architecture, and nothing else. Each is packed flat, so its
contents are its top level, and holds one folder per boot route —
fog-ipxe\, secureboot-upstream\, secureboot-fog\, refind\, plus
-customca\ variants where this server rebuilt iPXE with its own CA.
How to choose a folder, boot it, and enroll this server’s certificate is documented at Local ESP boot, with the capability matrix per Secure Boot state. This page covers only what the installer does to these files, which is the part that concerns preservation.
Note
Where the
zippackage is missing the installer falls back to.tar.gz.manifest.jsonalways names the file that was actually produced, so fetch the name it gives rather than assuming an extension.
This is not a Secure Boot feature and does not need Secure Boot keys. Local ESP boot predates Secure Boot by years; Secure Boot only added the requirement for a signature. The archives are published either way — a server with no key publishes the same set unsigned, which is what every machine booting with Secure Boot off needs anyway.
manifest.json
A static file written at install time, so you can script against it without guessing filenames:
{
"schema": 3,
"generated": "2026-08-19T14:02:11Z",
"fogVersion": "1.6.0-beta.123",
"ipxeVersion": "v2.0.0-fog.8",
"archives": [
{ "path": "fog-esp-x86_64.zip", "arch": "x86_64",
"size": 6812345, "sha256": "…",
"contents": [ { "name": "local/fogipxe.efi", "size": 1012345, "sha256": "…",
"role": "fog-ipxe", "origin": "fog", "fogSigned": true,
"note": "FOG's build with all of iPXE's own NIC drivers…" } ] }
],
"kernels": [
{ "name": "bzImage", "path": "../ipxe/bzImage", "arch": "x86_64",
"kind": "kernel", "size": 12345678, "sha256": "…" }
]
}schema is 3. Schema 1 had a variant field on each archive, for the -10sec
set that no longer exists, and named every file by bare basename;
contents[].name is the path relative to the archive root, so
fog-ipxe/, secureboot-upstream/, secureboot-fog/ and refind/ files are
named as such. Schema 2 carried a root key naming a wrapper directory inside
each archive — that wrapper is gone, so the key is gone with it, and every folder
was renamed to say what it holds. If you script against this, both changes affect
the paths you build; the absence of root is the signal that there is nothing to
strip.
One role value is worth knowing about if you consume this: a file named
secureboot-fog/ipxe.efi carries role: "fog-ipxe-as-shim-stage" and
origin: "fog", not upstream-loader. It wears an upstream filename so that the
shim beside it will load it, but the bytes are FOG’s build — matching on the
basename alone would misclassify it.
Paths are relative to the manifest’s own URL, so it resolves under whatever
hostname and webroot you reached it by. Every sha256 is of the bytes as
published — including after signing — so it is a real integrity check.
fogSigned says whether FOG’s signature is on the file. It is false for
the upstream shim and loaders, which carry Microsoft’s and iPXE’s signatures
instead; that is correct, not a gap.
kernels lists the FOS kernel and initrd set that is already published under
service/ipxe/. Nothing is copied — the archives do not contain a kernel. They
are listed so this manifest is a single index of everything fetchable for a local
boot. A kernel and initrd on an ESP would not boot FOG on their own in any case:
FOS reads per-host, per-task arguments that boot.php generates.
Notes
Directory listing is off; fetch files by name, or read manifest.json.
Everything here is an archive. No individual .efi has its own URL, which
means these cannot be used as a UEFI HTTP Boot target or an iPXE chain
destination. If you need that, unpack an archive and serve the file yourself.
The directory is deleted and rebuilt on every install, so nothing you put in it survives. It is a publication of files from the TFTP tree, not a place to keep things — edit the originals under your TFTP directory instead, and they will be re-signed and republished on the next run.
Nothing here is secret. These are the same binaries TFTP already serves
unauthenticated, upstream’s signed shim and loader (downloadable from fog-ipxe’s
release assets anyway), and certificates plus signatures over them — FOG already
serves the signed FOS kernel over HTTP from service/ipxe/. The private keys
never leave the PKI zone directory. If you do not want the archives published,
delete the directory after an install; only local-ESP boot depends on it, and it
comes back on the next run.
Custom iPXE binaries in the TFTP tree
Automatic, since 1.6.
FOG ships around 45 binaries into your TFTP root (/tftpboot on most
distributions): snponly.efi, ipxe.efi, undionly.kkpxe, the i386-efi/
and arm64-efi/ variants, and 10secdelay/, which holds BIOS builds only. The
autoexec/ tree is retired — every EFI binary in the root reads autoexec.ipxe
now, so the duplicate set served no purpose, and the installer removes it.
If you replace one of those with your own build, it is no longer overwritten
on the next install or update. FOG records the checksum of every file it
writes, in .fog-ipxe-manifest at the root of the tree, and skips any file
whose contents no longer match what FOG last put there. Files it skips are
listed by name at the end of the run.
Note
Protection starts from the first run after upgrading to this version. Before that there is no manifest, so a binary you replaced earlier is overwritten once, and protected from then on. Keep a copy elsewhere if that matters to you.
To go back to FOG’s version, delete your file — the next run reinstalls it.
A file under a name FOG does not ship — custom.ipxe, your own
myloader.efi — has always been safe and still is. Nothing removes files from
the TFTP root.
| Customization | What happens |
|---|---|
| Replaced one of FOG’s binaries | Kept; named in the run’s output |
| Added a file under a new name | Kept; FOG never touches it |
| Deleted your replacement | FOG’s version is reinstalled next run |
stock/ — the binaries FOG published
When you use --rebuild-ipxe-with-my-ca, FOG compiles iPXE with your CA
embedded and those builds take the normal top-level names, so your DHCP
configuration needs no change. The binaries FOG downloaded are kept
alongside, in stock/, so you can compare against them or fall back to one
without re-running the installer.
secureboot/ is deliberately not copied there: those are Microsoft’s and
iPXE’s signed shim and loader, and a second copy outside the signing sweep’s
exclusion would get a FOG signature added to it.
Signing
Every .efi under the TFTP root that does not already carry this server’s
signature is signed for Secure Boot, including binaries you built yourself.
sbsign appends, so your own signature survives and the binary gains one
this server’s MOK also vouches for — which is what lets a custom build boot on
a machine enrolled against FOG.
secureboot/ is excluded, always. Those two stages are what the whole chain
hangs off and are already signed by their vendors.
Reports you have written
Automatic, on both a full server and a storage node.
Reports you add under <webroot>/management/reports/ are copied aside before
the web tree is rebuilt and copied back afterward, so they survive every
install and upgrade.
| Customization | How it is preserved |
|---|---|
| A report file you wrote or edited | Backed up before the web tree is rebuilt, restored after |
| A report that ships with FOG, edited in place | Restored over the new release’s copy — so your edit wins, and you stop receiving FOG’s changes to that file |
That second row is the trade-off worth knowing about: the restore does not try to tell your file apart from a newer FOG one of the same name. If you want FOG’s version back, delete yours and re-run the installer.
Before FOG 1.6 these were backed up and never restored — the backup was written and nothing read it, so an administrator’s own reports were lost on every run (GH-1580). If you are upgrading from an affected version, your reports are in
<backuppath>/fog_web_<version>.BACKUP/management/reports.
What is NOT automatically preserved
Listed plainly so none of it is a surprise.
- Edits inside the FOG-managed vhost block. They are overwritten on the next run. Move them outside the markers.
- Direct edits to
default.ipxe. Regenerated every run; there is no supported hook point for pre-boot customization yet. This is the one file in the TFTP root the manifest above does not protect, because FOG has to rewrite it to keep the netboot URL correct. - A kernel-signing key rotated after a generation was captured. Restoring that generation re-signs with the current key, which is correct, but any client enrolled against the old key still needs re-enrollment.
- More than
--kernel-backup-countgenerations back. The oldest is evicted on each run; the default keeps three. On FOG before GH-1579 the rotation never ran, so only ever one generation existed no matter what this was set to — ifrestorekernel.sh --listshows a single generation on an older server, that is why. php.ini/ MariaDB config beyond FOG’s own lines. FOG patches only the specific directives it manages and leaves the rest of those files alone, so your edits generally survive — but they are not backed up, and are not restored if something else removes them.- Anything under the web root that FOG does not ship. The tree is rebuilt
wholesale on every run. Only the categories above are copied to safety
first; a snapshot of the previous tree is left at
<backuppath>/fog_web_<version>.BACKUP—/home/by default, settable withinstallfog.sh -B— for manual recovery.