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.md in the code repository. That path still exists there, as a pointer to this page: the FOG MANAGED BLOCK marker 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 bzImage is 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 byFOGyou
What it iscopies FOG makes of your files before it rebuilds the tree they live in, and restores from afterwardan input FOG only ever reads
Conflict rulerestores by absence — a saved file is put back only when the rebuilt tree no longer has itadopts by presence — what you put there overrides what FOG would otherwise generate
Holdsipxe-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.txt is 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.

CustomizationHow it is preservedWhere the copy lives
Replaced bg.png in placeBacked up before the web tree is rebuilt, restored after/opt/fog/customizations/ipxe-bg/bg.png
Renamed background via FOG_IPXE_BG_FILESame, under whatever name the setting holds/opt/fog/customizations/ipxe-bg/<yourname>.png
Legacy refind.* filesBacked 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.

CustomizationHow it is preserved
Extra directives, headers, location/Directory blocksKeep them outside the markers; never touched
Extra hostnames / DNS aliasesUse --extra-server-name (repeatable) so they land in both the vhost and the certificate SAN
Primary hostnameUse --hostname; remembered in the `.fogsettings` file
Custom certificate pathsPoint 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.

CustomizationHow 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 setKept 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 update

gen-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.0 is 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/pki is 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.

CustomizationHow it is preserved
FOG’s generated signing keyLives outside the web root; nothing in the installer deletes it
Your own key via --secure-boot-key / --secure-boot-certCopied 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-boot declines enrollment, not signatures. It stops the server publishing MOK.der and the PK/KEK/db variable updates, and with them the Enroll Secure Boot Key PXE menu entry, which is gated on MOK.der existing. 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 zip package is missing the installer falls back to .tar.gz. manifest.json always 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.

CustomizationWhat happens
Replaced one of FOG’s binariesKept; named in the run’s output
Added a file under a new nameKept; FOG never touches it
Deleted your replacementFOG’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.

CustomizationHow it is preserved
A report file you wrote or editedBacked up before the web tree is rebuilt, restored after
A report that ships with FOG, edited in placeRestored 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-count generations 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 — if restorekernel.sh --list shows 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 with installfog.sh -B — for manual recovery.