Booting Debian with UEFI firmware

Introduction

EFI is short for Extensible Firmware Interface, a link between PC hardware and operating systems introduced by Intel about 2000. It became an industry standard in 2005 when a consortium of hardware and software makers agreed on a common or “Unified” EFI specification — hence, UEFI. (This page uses many acronyms; so I'll boldface each one's first appearance.)

“Firmware” means the hardware in a computer's motherboard that stores programs that run when the power is turned on. Originally, it was mostly a set of I/O routines that were used by DOS; so back then it was called the Basic Input-Output System, or BIOS. That relatively simple system handed control of the system to a single 512-byte sector of a bootable disk, the Master Boot Record or MBR. Its small size limited the amount of disk space that could be addressed, and had difficulty coping with the increasing variety of hardware devices that desktop computers had acquired.

The UEFI system reserves a larger amount of disk space for pre-boot software, and employs a more capable disk-partitioning layout that avoids the addressing limitations of 32-bit systems. Today's “firmware” is a much larger set of routines that prepare the hardware to boot an operating system. Because today's machines store these programs and associated tables in Non-Volatile Random-Access Memory (usually, flash memory), it's sometimes referred to as NVRAM. [Don't confuse this with the solid-state disks that are denoted by “nvme”.] Often, people still call the firmware “the BIOS”, which is a serious mistake, because BIOS and EFI are completely different.

When you turn on your computer, it knows nothing about filesystems or operating systems. The firmware runs a short program stored in its non-volatile memory, which contains a list of the peripherals (disks, memory controllers, busses, keyboards, graphics and network-interface cards, monitors, etc.) and also a hardware-testing routine (to make sure they all work): the Power-On Self-Test, or POST.

The part of the boot process that most people think of first is the bootloader; these days, that's usually Grub (formerly known as Grub2). But before it gets to Grub, the firmware has to run the POST, and initialize various peripheral devices. Then it can run an EFI boot manager, which in turn looks for a kernel to load from a hard disk, usually /dev/sda . But if you want to make an additional disk or USB device bootable, you need to deal with the UEFI system yourself.

The booting process has become so convoluted now that a quick overview of it may help. Here's the basic outline:

         (1)          (2)      (3)
POWER ON ——> FIRMWARE ——> GRUB ——> KERNEL
(1) Turning on the power activates the UEFI firmware. After running the POST to check and initialize the hardware, it finds and runs an EFI application (2) that invokes Grub [or some other boot manager (like refind), or a bootloader]. So the firmware has to be told where (i.e., on which disk partition ) that EFI application is stored. (This must be a FAT32 partition, because the UEFI firmware can only read GPT partition tables and FAT32 partitions.) Such a partition is called an “EFI System Partition”, or ESP.

Note: a system can have multiple block devices (such as disks); but each disk device should have no more than one ESP.

Identifying block devices

Because there is no file tree before the system is booted, block devices, such as disks, partitions, and filesystems, are identified by their Universally Unique IDentifiers, or UUIDs. There are several different kinds of UUID in a GPT system; see the Wikipedia page https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_table_header_(LBA_1) for descriptions. Many of these identifiers can be displayed with the lsblk command. (See both   man lsblk  and the output of  lsblk -h  for details.) Among the UUIDs that identify disks, partitions, and filesystems, are the filesystem UUIDs that are printed, together with their mount points (if they are currently mounted), by the command

lsblk -f

The command

lsblk -o +UUID,PARTUUID

adds both this filesystem UUID and the partition UUID to the default output of lsblk . Or, you can list the entries in the subdirectories of  /dev/disk/.

ESP and booting

The location of an ESP is stored in the firmware's non-volatile memory on the motherboard of a desktop system.. The system may have several different disks, each with its own ESP partition, so “configuring the firmware” means writing a partition or filesystem UUID into that memory. The particular ESP partition that runs after the POST is normally set by the motherboard's SETUP menu. If Grub is called by the EFI application, it will try to display a menu (3) of operating systems to boot.

Bootable operating systems may reside on several different disks. To boot a Linux system, Grub only needs to know which partition  holds the kernel to be booted. That partition contains the root ( / ) filesystem, which has not only the kernel (as a compressed vmlinuz file and its associated initrd.img file), but also the /etc/fstab that tells the kernel where to mount all the other partitions when booting the system.

The information Grub needs to display a menu is stored (in its special scripting language) in the file /boot/grub/grub.cfg. If /boot is on a separate partition, Grub can read its partition table and find the file. As the mount points for partitions are listed in the /etc/fstab file, grub can find this as soon as it reads the root partition.

Notice that the root-filesystem partition does not  need to be on the disk that holds the EFI System Partition used by the firmware. So it's possible for a Grub menu entry to boot a Linux (or some other) operating system that resides on another disk, or a bootable CD-ROM, or some bootable USB storage device.

A good basic explanation of UEFI booting is available at https://wiki.archlinux.org/title/Arch_boot_process#Boot_loader. A very clear and detailed explanation of how to handle multiple disks and operating systems is available here; it's written for a Debian derivative, so it works for us. There's also a moderately detailed account of EFI booting a Debian system at https://www.debian.org/doc/manuals/debian-reference/ch03.en.html, though most of that text is obviously written by a non-native writer of English.

Details

After the POST, the firmware runs its own boot-manager to find a bootable device, and a boot-loader (Grub). UEFI firmware keeps a set of EFI variables  in its non-volatile memory to configure the booting process. These variables are usually set from the firmware's SETUP menus; but they are also accessible during normal operation, and can be examined and reset by programs like efivar and efibootmgr. (For lots of useful examples of what efibootmgr can do, see https://www.linuxbabe.com/command-line/how-to-use-linux-efibootmgr-examples.) In particular, the command

efibootmgr

lists the names of all the EFI applications that the firmware has been configured to use. Most of these will invoke Grub, or some other boot loader.

When the firmware calls a particular EFI application that can invoke a loader, it looks for the EFI System Partition, or ESP, on the corresponding device. The ESP contains executable boot-loader code, and must be a FAT32 partition that is flagged as esp or boot. The flags for each partition are stored in an 8-byte field of its entry in the GUID Partition Table. (See https://www.linux.org/threads/gparted-partition-and-filesystem-flags.11640/ for a discussion of possible flag values, and https://www.ionos.com/digitalguide/server/configuration/what-is-a-guid-partition-table-gpt/ for an explanation of a GPT.)

A good guide to EFI booting of Debian-based systems is at https://help.ubuntu.com/community/UEFI.

A thorough guide to converting an MBR system to EFI booting (but for a Red Hat system) is at https://www.redhat.com/sysadmin/bios-uefi. It explains how to convert the filesystem from MBR to GPT style, as well as how to install Grub in the new partition mounted on /boot/efi.

From firmware to Grub: the EFI Boot Manager

Before the firmware can pass control to Grub's boot-loader, the firmware must know how to find Grub's files. Because there is no file tree until after  the kernel is booted, the locations of these files cannot be specified by their usual *ix paths, so we must use the UUIDs of their partitions. That information is stored in EFI variables in the firmware's NVRAM, which can be read and set by "root", using the commands  efivar  and  efibootmgr .

Once the Grub files (i.e., its modules and the  grubx64.efi  file) are installed in the proper subdirectory of  /boot , you must tell the firmware where they are. The modules are installed in  /boot/grub/x86_64-efi/  by the  grub-install  command, which also puts the executable  grubx64.efi  in  /boot/efi/EFI/debian/ .

To do that, root must use efibootmgr to make an entry for Grub in the EFI variables. (This is called “registering Grub with the EFI”.) The command will look like

efibootmgr -c -d /dev/sda -p 1 -l /EFI/debian/grubx64.efi -L EntryLabel

where the option -c is the short form of --create ; -d is short for --disk ; the number after -p is the partition number on that disk where the EFI directory lives (i.e., the number of the FAT32 partition) ; and the “EntryLabel” text is the label that will identify this entry in the EFI boot order when that order is displayed by a plain efibootmgr command with no options. The man page for efibootmgr isn't very clear about the -l option: it calls the argument of this flag a “loader”, but the example shows that it's really the path from the root of the ESP to the EFI application where a loader (here, grubx64.efi ) resides.

But this does not tell the firmware's boot manager where Grub should be called in the firmware's boot order.

A peculiar bug in some firmwares is that they fail to detect the EDD 3.0 extensions properly, but can be made to do so by adding the option -e 3 to the efibootmgr command line above; see https://github.com/rhboot/efibootmgr/issues/86 and https://wiki.archlinux.org/title/Unified_Extensible_Firmware_Interface#Userspace_tools_are_unable_to_modify_UEFI_variable_data for examples and discussion.

Sometimes the firmware gets confused by malformed entries in the boot order list, and they should be removed. Often such unbootable entries can be identified by their lack of a correct “loader” entry in the listing printed by efibootmgr -v. [Typically, a correct entry begins with its  Boot00x  order number (where  'x'  here is one or two hexadecimal digits); followed by its identifying text string, and then a long string beginning with  “HD(1,GPT,”  and the root partition's UUID number, followed by  “)/File(\EFI\DEBIAN\GRUBX64.EFI)”.]

To remove an entry from the EFI Boot Order, first run either plain   efibootmgr , or   efibootmgr -v   to read the hexadecimal boot-ID number (usually something like "0007" or "000A") of the item to be removed. Then delete it with a command like

efibootmgr -b A -B  

which will remove item 000A from the Boot Order list. Notice that you can omit the leading zeros from the order number.

Understanding Grub

Fortunately, there is a short account of setting up grub in The Debian Administrator's Handbook  at http://debian-handbook.info/browse/stable/sect.config-bootloader.html. A longer account, reasonably well written, is at https://help.ubuntu.com/community/Grub2/Setup on the Ubuntu website. An even longer and more complete account of how Grub works is in its Wikipedia article. Finally, there's the full Grub Manual, available as the  info grub  info file.

Probably the best guide to recovering from Grub disasters, as well as the use of the normal Grub-shell command line, is How to Rescue a Non-booting GRUB 2 on Linux/, which clearly explains how to use the infamous Grub Rescue shell. (I have a short treatment of it here.)

Besides the long and confusing  info manual, the complete Grub documentation is available at http://www.gnu.org/software/grub/manual/, but it's huge: the PDF version is 130 pages long. However, it's somewhat clearer than the info page for Grub, because the cross-references are easier to follow in PDF format than in info.

Structure of Grub

Overview

Grub's grand strategy for booting a computer involves the ability to start any of several different operating systems. To handle that level of complexity, it has to be able to read any of several different filesystems, from any of several different storage devices. So the initial stages concentrate on getting the Grub system up to a moderate level of competence.

But the big problem is that when the system is booting, none of the facilities of a running system are available. There is no filesystem, just some storage devices that may have pieces of one or more filesystems on them. The files may be fragmented into disjoined blocks. There is no shell like bash available. Somehow, the bootloader has to find the blocks for the system's files, concatenate them in memory, and then initialize and transfer control to the operating system.

To make the process manageable, the authors of Grub devised a shell-like interpreter, and a scripting language devoted to booting. (This reminds me of PostScript: the PS interpreter concentrates on putting ink marks on a page, while the Grub interpreter concentrates on finding partitions on disks, and files on those partitions, as well as the special requirements of several common operating systems.) The Grub documentation awkwardly describes this scripting as its “command-line interface”; but it would be more sensible to just call it “Grubscript”. It's intended to look like standard POSIX shell-script, with common commands like echo and ls; but it's actually considerably different, so that the similarities are misleading rather than helpful. (The full syntax of Grubscript is hidden in section 6.3 of the Manual, “Writing full configuration files directly”.)


Note: The numbering of some subsections of Section 6 has changed in recent years, because a new Section 6.2, “Root Identification Heuristics”, was inserted after Section 6.1. The new subsection describes how the shell script grub-mkconfig tries to determine the root file system.

Because Grub's need for a fairly high-level understanding of disk partitions (and the filesystems on them) can't be met with the low-level approach of absolute block-number addresses that is the only thing available early in the booting process, it has to get the filesystem stuff and the script interpreter up quickly. That lets it (or the user) do everything else in the higher-level language of Grubscript, which controls the actual booting of the OS kernel.


Note: The syntax  of Grubscript is very similar to that of the POSIX shell; but the commands  (like ls and lsmod) that have names like those in the running system actually work quite differently from the corresponding shell commands.

The booting subsystem

Now let's look a bit closer.

I'll divide Grub into two areas. The first is the executable part that runs at boot time. Because Grub can boot any of several operating systems, it has to ask you which one to boot. So it presents a menu of possibilities that you can choose from; one will boot by default after a delay of a few seconds, if you make no choice. If it has problems finding the pieces needed to boot your selection, it drops you into a primitive shell that allows you to locate those pieces manually, and put together a workable Grubscript command line to boot Linux. (This is the dreaded command-line interface of the Grub “rescue shell”; a detailed discussion of dealing with these problems is in the Ubuntu Community Help Wiki.)

Notice that the executable stuff that runs at boot-time — not only the primary and secondary stages of the boot-loader, but also the stand-alone program that displays the menu, and uses your menu selection to boot the selected OS; and the Grubscript interpreter, and all its commands — all have to run before  a regular operating system is available. In particular, the various filesystems don't get mounted on their mount points until the kernel has been booted and can read /etc/fstab ; Grub sees just unmounted filesystems on isolated disk partitions. So modules that can do all these things by themselves, and read your filesystems to find them when they're needed, have to be part of Grub's boot-time apparatus. These modules are in the /boot/grub/ directory if you have the old MBR booting hardware, or in /boot/grub/x86_64-efi if your amd64 hardware boots in the modern EFI mode.

The installation subsystem

The other area of Grub is the part that sets up  the part that runs at boot time. This is Grub's installation system: it organizes the boot-time menu, collects the executable modules that actually boot your machine, and tells the boot-time stuff where (and how) to find everything, such as the kernel to be booted, and its initrd filesystem. Fortunately, this all runs when a system is available, and ordinary shell scripts can be used to do the work.

The boot-time parts are installed by ordinary executable programs and shell scripts, such as the commands:

grub-install         which writes the primary bootloader, boot.img, to the MBR or the EFI partition
grub-mkconfig      which prints a new copy of the grub.cfg script to its standard output
update-grub           which saves that new copy of grub.cfg at /boot/grub/grub.cfg

(There are so many of these isolated pieces that make up the installer part of Grub that it would help to show their relationships here.)

Scripts:

The basic specifications for configuring Grub are in shell scripts (or rather, fragments of shell scripts) in two areas of the /etc directory:

/etc/grub.d                       which contains numbered pieces of shellscript that are executed in numerical order,
                                                                and whose output is blocks of Grubscript text;   and
/etc/default/grub         which sets various configuration parameters used by Grub.

These pieces might well have been combined ; and in fact they are  combined into a single shell script by the update-grub command. That command (which is really just a front-end for grub-mkconfig) runs the resulting shell script, whose output is the Grubscript input file read by Grub's bootloader. The default destination for that file is /boot/grub/grub.cfg, which is described more fully below.

That file, /boot/grub/grub.cfg, is pure Grubscript text. It tells the part of Grub that runs at boot time how to display a menu of possible ways to boot your system, as well as how to carry out the choice you select from the menu — or else, how to carry out a default boot sequence, if you do nothing within a few seconds. The part of Grub that interprets the grub.cfg script is the normal module that lives in a subdirectory of /boot/grub/.


The configuration parameters
Usually, you can expect a configuration file in /etc to show all the default values of a program's configuration variables. But the items that appear in /etc/default/grub are just a few of the 40-odd variables that can be set for Grub. For example, a very useful one that doesn't appear in the usual list is GRUB_CMDLINE_LINUX, which will be appended to all  of the linux command lines in the grub.cfg file.

The full list of configuration parameters that can appear in the /etc/default/grub file are listed in section 6.1 of the Grub manual ("Simple configuration"), which is available in the info page of grub. This part of the Manual calls these variables "keys".

Notice that /etc/default/grub itself is just a piece of ordinary shell-script code that does nothing but initialize a set of shell variables whose names all begin with GRUB_ . These variables’ values are used by the whole shell script assembled from the numbered sections in /etc/grub.d when it is used by grub-mkconfig to generate a new grub.cfg file.


What's where

One reason Grub is so confusing is that pieces of it are scattered all over your disk. Some of them are in the /boot/grub directory, which holds many Grub executable modules and some Grubscript configuration files; some are shellscript fragments in the /etc/grub.d directory, where you might expect to find Grub's configuration files; and some are environmental parameters for those scripts that are set in /etc/default/grub .

Likewise, there is no grub command, and no grub manpage. Instead, there are about 20 different commands that set up various pieces of the whole system, each with its own man page. These installation commands are in /usr/sbin. However, there is a huge Grub Manual available with info grub. It's like LaTeX : gloriously powerful and adaptable for the expert, but a nightmare for the casual user.

Connecting the pieces:  grub.cfg

Fortunately, there is a single file that tells Grub what to do when it boots your machine: /boot/grub/grub.cfg . This is a simple Grubscript text file that describes Grub's whole configuration. If you learn to read this configuration file, you can understand how Grub carries out the booting process.

Because grub.cfg is generated by the shellscript pieces in /etc/grub.d, using their environmental parameters in /etc/default/grub , you can think of grub.cfg as the link between Grub's installation subsystem and its booting subsystem. It's the main output from the installation process, and the main input to the booting process.


Because the file grub.cfg depends on the numbered files in the /etc/grub.d directory, as well as the parameters set in the /etc/default/grub  file, it is essential to run the update-grub command whenever you change any  of these files.

If you configure Grub correctly, the menu items presented by the booting subsystem will work correctly, and you'll never have to deal with the weird conventions and obscure commands of the rescue shell.

Understanding  grub.cfg

So let's examine a typical  grub.cfg file. Remember, this is written in Grubscript; so it only resembles  a normal shell script.

At the top, there are some general sections written by the /etc/grub.d/00_header and /etc/grub.d/05_debian_theme shell‑scripts in /etc/grub.d . These set up some basic information and make a few subroutines (i.e., "modules") available to Grub's bootloader. For example, if your hard disk uses the traditional MS-DOS partitioning scheme, you'll see the line insmod part_msdos, which inserts a module that can read an MS-DOS partition table. If you have a GPT partition table, you'll need insmod part_gpt instead.

If your system uses the ext2/ext3/ext4 group of filesystems, you'll see insmod ext2, which can read those filesystems. The timeout interval appears at the end of the 00_header section, copied directly from the value set in /etc/default/grub. Such things are detected automatically by the installation subsystem, and we don't expect to see problems here.

The interesting stuff begins with /etc/grub.d/10_linux, which sets up the menu entry for the default booting sequence. The word menuentry is followed by a text string in single quotes, describing the system to load; this string (called "title" in the Grub Manual) will be displayed in the menu at boot time. Then come some --class declarations that tell the booting subsystem what kind of OS it's expected to boot. This first line ends with a left (opening) brace; everything that follows, up to a closing brace, is included in this menu item.

First comes load_video and some more module-insertions, which are pretty obvious. The first tricky item in a traditional MBR system is

set root='(hd0,msdos1)'

which illustrates two of Grub's strange quirks. First of all, “root” here does not mean the root filesystem. Instead, it means the value of Grub's ill-named “rootvariable — a pointer to the disk partition that contains the filesystem where the grub.cfg file lives. (A better name would have been grub's home.) So if you have a separate /boot filesystem, this really is a pointer to that filesystem's partition; then “root” here really means “boot”. But if your /boot directory is in the same disk partition that contains the root directory , then Grub's root parameter would point to your root partition .

Furthermore, this pointer, (hd0,msdos1), is an example of Grub's peculiar notation. The hd0 part means the disk identified in the important file /boot/grub/device.map, which contains a persistent identifier for the disk Grub calls (hd0). Of course, the msdos1 part indicates the first primary partition on this disk. Though the hd0 part resembles the old scheme for naming IDE disks in the /dev directory, it's different — because this is Grubscript, not shell-script. (And these days, Linux calls the disk /dev/sda, or something like that; the old /dev/hdX names are gone.)


Device names in Grub: Fortunately, Grub comes with a utility that can write the important file /boot/grub/device.map. It's called grub-mkdevicemap , and it has a man page.
Unfortunately, if you have more than one disk, the "hdX" desigations can change from one boot to the next: what was (hd0) at one boot can be called (hd1) at the next. So a modern system that uses Grub2 and a GPT partition table will identify the device to boot by its UUID, and will search for that particular block device.

Note: even if you give Grub the right root device, it can happen that the firmware cannot find a boot-order entry that refers to the right partition, and the system refuses to boot.

search: finding Grub's root partition

Next comes a line beginning with search, which tells the boot-subsystem to look for something in this partition:

search --no-floppy --fs-uuid --set=root d074378b

The line contains the flag --fs-uuid, which says the UUID of the filesystem partition is coming up, and then --set=root and the actual UUID itself (a string of gibberish at the end of the line). This line is a Grubscript command that tells the loader where to find the grub.cfg file, the kernel to boot, and its initrd filesystem.

UUIDs solve the problem of unreliable disk references, but they are inconvenient to use if you need to enter disk designations by hand. But there is a simpler way to identify disks if you have taken the time to add labels to partitions: use the search --label method of identifying a partition.

You can get help on Grub's search command by typing   help search   at a   grub >   prompt.

Finding the kernel to boot, and its root filesystem

After echoing a comment telling the user at boot-time that Grub is trying to boot the specified Linux kernel, the menu entry has a line beginning with linux that specifies the kernel's command-line:

linux /vmlinuz-3.2.0-4-686-pae root=UUID=0899983c…  ro video=630x480 quiet

There is the name of the kernel file, followed by root=UUID= and the UUID of the kernel's root filesystem. The line ends with the arguments that will be fed to the kernel, like ro (meaning “mount the root FS read-only”), and a video mode. So, in the “linux” line, “root” means the root filesystem — unlike the usage a few lines earlier. (That's because the root= in the “linux” line is an argument to the kernel itself, and not a Grub variable being set.)

To specify the partition that holds the root filesystem, you can put

	root=LABEL="partition label"
instead of  root=UUID=...  after the kernel on the "linux" line. (Notice that the word "LABEL" must be all-caps.)

Kernel command-line arguments

Some of the arguments are really kernel parameters, and some are Grub variables; so be careful.

Kernel parameters

Some of the possible kernel parameters are described by  man bootparam . A few other parameters that deal with block devices are described in  man mount .

NOTE: a partial list of the possible arguments in the kernel command line is available at https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html .

You can see the parameters that were actually used to boot a running kernel with the command  cat /proc/cmdline . This can be helpful in debugging , especially if you had to alter these parameters at boot time by editing the command line.

An obscure kernel parameter that may be needed to boot a kernel on a USB device is rootwait. USB devices are often slow to become available, and may be reported by Grub as “not found” when they are merely not ready for use when Grub searches for them.

Fixing  tiny print
It's often necessary to specify the screen size on the command line, because today's high-resolution displays make text too small to read when you're using a console. Grub sets the screen size to modest dimensions like 640x480 or 800x600 pixels, which makes its menus easy to read. But if the screen defaults to 1920x1080 after booting, the boot messages are tiny — and so is the login prompt in a text console.

The solution is to set the dimensions of the screen to a size that makes text readable. You can find the possible dimensions by asking Grub to display a command prompt, and entering the command "videoinfo" at the grub > prompt. Several sets of WxH dimensions will be displayed, and the one Grub is using will be marked by an asterisk. Pick a size you like, and add "video=800x600" (or whatever it is) to the linux command lines.

An easy way to do this is to set GRUB_CMDLINE_LINUX="video=800x600" in /etc/default/grub.


The menu entry ends by naming the initial ramdisk (initrd.img file) the kernel will use. Its name, like that of the vmlinuz file, is preceded by a slash, meaning that it lives in the top directory of the Grub-root partition (i.e., /boot ) if this is a separate disk partition; if /boot is a directory in the partition that contains the root of the filesystem, then the names of the kernel and initrd files must begin with /boot , not just / .

The full syntax of a menuentry is hidden in section 17.1.1 of the Grub manual (or its info version.) Some more detailed discussion of menuentry construction is in the Ubuntu wiki.

Debugging Grub

Most of the installation system works well automatically; so only a few changes usually need to be made to make Grub do what you want. The trick is to learn what parts need to be tweaked. You can experiment by adding menuentry items to the end of /etc/grub.d/40_custom , and then running update-grub , and re-booting.

Better yet, you can change any of the scattered Grub configuration files, and then safely  see how this will change a new grub.cfg , with the simple command line

grub-mkconfig | less ,

which lets you examine the  grub.cfg  file that would  have been generated and installed in  /boot/grub/grub.cfgif  you had run Debian's  update-grub  command. (The default output of  grub-mkconfig  goes to standard output; so your working  grub.cfg  file doesn't get changed this way.) So your actual configuration file is undisturbed, while you can see the effects of changes to files in /etc/grub.d/  or  /etc/default/grub .

It's also useful to use grub-script-check to make sure your new  grub.cfg  is free of syntax errors. This program reads from its standard input, so it's easy to couple it to grub-mkconfig:

grub-mkconfig | grub-script-check .

If everything is OK, grub-script-check produces no output —- though you will see some messages from grub-mkconfig .

Remember to keep an un altered copy of any file you change, so you can quickly reverse the changes if they don't do what you want. And be especially careful not to leave an incorrect set of Grub configuration files in place when installing a new kernel, as the upgrade will invoke  update-grub  automatically, and overwrite your  grub.cfg .

Other debugging tools

Remember that booting problems may have nothing to do with Grub. You might need to load a kernel module that's required to run some piece of hardware. Often a clue can be found in the kernel's log, which is normally not displayed during the boot process if the quiet parameter is used on the kernel's command line. The whole boot log is saved, and can be replayed with the dmesg command when the system is running.

You can see the details of this process by running the command:   journalctl -xb   as  root.


Before the boot process gets to Grub and its menu, it's initiated by settings in the firmware on the motherboard. You can interrupt the boot process and check the startup settings in the firmware by pressing some special key, such as <F12>, at the start of the boot. Even when Grub has started, the /etc/grub.d/30_uefi-firmware menu item may provide access to those startup menus.

Make sure you are telling the hardware to boot from the right device, and in the right mode (BIOS/Legacy vs. UEFI). If you change the grub.cfg file but don't see its changes in the menus at boot time, you may be using the wrong disk's ESP; check the SETUP menu to make sure the right block device is being used.

Understanding  grub-mkconfig  and  update-grub

These two commands are almost identical, because update-grub is just a 1-line shell script that writes the output of grub-mkconfig to /boot/grub/grub.cfg .

And grub-mkconfig itself is just a POSIX shell script that executes the numbered scripts in /etc/grub.d in numerical order. (And you can intervene, if you want, by adding more numbered scripts to this directory.) So, if you can read standard Bourne shell code, you can figure these things out. However, there are lots of tricky cross-references that can make the details difficult to follow. In particular, the operation is guided by the various Grub environmental variables that are set in /etc/default/grub.

NOTE: these variables are not  the “Special environment variables” described in Chapter 15.1 of the Grub manual. Instead, the variables set in /etc/default/grub are the ones described as “keys” in Section 6.1 of the Manual, “Simple configuration handling”.

One important feature of the standard operation is that the currently running kernel is the one that will be selected in the first (i.e., number zero) menuentry stanza in the grub.cfg that is produced. And, as the default menu selection is also number zero, that means that Grub will normally boot the current OS again when you re-boot.

Or, if you decide to make a different menu the default item, you only need to run (as root) update-grub to rewrite /boot/grub/grub.cfg while you are running on the OS version you prefer. Then, when you re-boot, that version will automatically be the default.

Upgrade woes

Debian invokes update-grub automatically whenever a kernel is upgraded. The new-kernel installation scripts always generate a new grub.cfg file with a default that boots the new kernel.

But if you have put some fancy booting arrangements of your own on the /etc/grub.d/40_custom  shell script, that means your customized booting sequence won't use the new kernel. Worse yet, you might remove some old outdated kernels and their associated initrd files after several kernel upgrades; then the stanzas in grub.cfg that referred to them won't work at all.

So it's essential to check your /boot/grub/grub.cfg file a few times a year to make sure the kernels mentioned there are still available in /boot. Better yet, fix up your additions to /etc/grub.d/40_custom  to use the newest kernel version. Then run  update‑grub to make those changes effective.

And if a new kernel was installed to fix security issues, make sure the older ones are removed from both /boot and the configuration files in /etc/grub.d !

 

Copyright © 2022 – 2025 Andrew T. Young


Back to the . . .
main LaTeX page

or the alphabetic index page

or the GF home page

or the website overview page