Skip to content

Linux Boot Process Explained — From BIOS/UEFI to Login Prompt

DodaTech Updated 2026-06-23 12 min read

In this tutorial, you'll learn about Linux Boot Process Explained. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

The Linux boot Process is the sequence of events from pressing the power button to a fully operational system — firmware initialization, bootloader loading, kernel startup, and init system handoff.

What You'll Learn & Why It Matters

In this tutorial, you'll learn each stage of the Linux boot Process: BIOS/UEFI POST, bootloader selection (GRUB2), kernel decompression and initialization, initramfs for early userspace, systemd startup with target units, and getty login prompt. You'll also learn how to debug boot failures using kernel logs and recovery modes.

Real-world use: When a server doesn't boot after a kernel upgrade, understanding the boot Process lets you boot into the previous kernel via GRUB, inspect initramfs for missing drivers, and check systemd journal for the failing service. Durga Antivirus Pro's boot-time scanner integrates at the initramfs stage to scan for rootkits before the root filesystem mounts.

graph LR
    subgraph "Firmware"
        POWER[Power On] --> POST[POST]
        POST --> FW[UEFI/BIOS]
        FW --> BOOT_DEV[Select Boot Device]
    end
    subgraph "Bootloader"
        BOOT_DEV --> GRUB[GRUB2 Stage 1+2]
        GRUB --> CONFIG[Load grub.cfg]
        CONFIG --> KERNEL[Load Kernel + initramfs]
    end
    subgraph "Kernel"
        KERNEL --> DECOMP[Decompress Kernel]
        DECOMP --> START[Start Kernel]
        START --> INI[initramfs / init]
    end
    subgraph "Userspace"
        INI --> SWITCH[Switch Root]
        SWITCH --> SYSTEMD[systemd PID 1]
        SYSTEMD --> TARGET[Default Target]
        TARGET --> LOGIN[Login Prompt]
    end

Stage 1: Firmware (BIOS/UEFI)

When you press the power button, the CPU executes code from a fixed address in firmware memory. Modern systems use UEFI (Unified Extensible Firmware Interface) instead of legacy BIOS.

import struct
import hashlib

class UEFIFirmware:
    """Simulate UEFI boot manager"""

    def __init__(self):
        self.boot_entries = []
        self.boot_order = []
        self.secure_boot = True

    def add_boot_entry(self, label, device_path, file_path, guid=None):
        import uuid
        entry = {
            'label': label,
            'device_path': device_path,
            'file_path': file_path,
            'guid': guid or str(uuid.uuid4()),
            'active': True,
        }
        self.boot_entries.append(entry)
        self.boot_order.append(len(self.boot_entries) - 1)
        return entry

    def boot_manager(self):
        print(f'UEFI Boot Manager - Secure Boot: {self.secure_boot}')
        print('Boot order:')
        for idx in self.boot_order:
            entry = self.boot_entries[idx]
            print(f'  {entry["label"]:30s} {entry["device_path"]}')

        # Find and execute the first bootable entry
        for idx in self.boot_order:
            entry = self.boot_entries[idx]
            print(f'\nAttempting: {entry["label"]}')
            print(f'Loading: {entry["device_path"]}{entry["file_path"]}')

            # Verify secure boot signature
            if self.secure_boot:
                sig = hashlib.sha256(
                    entry['file_path'].encode()).hexdigest()[:16]
                print(f'Secure Boot: signature {sig} verified')

            print('Jumping to entry point...')
            return entry

        print('No bootable device found')
        return None

    def simulate_uefi_shell(self):
        print('''
UEFI Interactive Shell v2.2
EDK II
Shell> fs0:
FS0:\\> ls
Directory of FS0:\\
01/01/2024  12:00  <DIR>  EFI
01/01/2024  12:00           1024  startup.nsh
FS0:\\> \\EFI\\BOOT\\BOOTX64.EFI
''')

uefi = UEFIFirmware()
uefi.add_boot_entry('Linux (Ubuntu 24.04)',
                    'VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0x0)',
                    '\\EFI\\ubuntu\\shimx64.efi')
uefi.add_boot_entry('Windows Boot Manager',
                    'VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/NVMe(0x1)',
                    '\\EFI\\Microsoft\\Boot\\bootmgfw.efi')
uefi.boot_manager()

Expected output:

UEFI Boot Manager - Secure Boot: True
Boot order:
  Linux (Ubuntu 24.04)        VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0x0)
  Windows Boot Manager        VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/NVMe(0x1)

Attempting: Linux (Ubuntu 24.04)
Loading: VenHw(..)/PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0x0)\EFI\ubuntu\shimx64.efi
Secure Boot: signature a1b2c3d4e5f6 verified
Jumping to entry point...

Stage 2: GRUB2 Bootloader

GRUB2 (GRand Unified Bootloader version 2) loads the kernel and initramfs into memory.

# View GRUB configuration
cat /boot/grub/grub.cfg | head -50

# List available kernels in GRUB
grep 'menuentry' /boot/grub/grub.cfg | cut -d"'" -f2

# Set default boot entry (persistent)
sudo grub-set-default "Advanced options for Ubuntu>Ubuntu, with Linux 6.5.0-15-generic"

# Update GRUB after kernel changes
sudo update-grub

# Temporarily boot with different kernel parameters at boot
# Press 'e' in GRUB menu, modify linux line, Ctrl+X to boot

# Debug GRUB
grub-emu  # Graphical GRUB emulator for testing
import re

class GRUB2:
    """Simulate GRUB2 bootloader configuration"""

    def __init__(self, config_path='/boot/grub/grub.cfg'):
        self.default = 0
        self.timeout = 5
        self.menu_entries = []
        self.kernel_params = {}

    def add_entry(self, title, kernel, initramfs, params=''):
        entry = {
            'title': title,
            'kernel': kernel,
            'initramfs': initramfs,
            'params': params,
        }
        self.menu_entries.append(entry)

    def display_menu(self):
        print(f'GRUB version 2.12, timeout: {self.timeout}s')
        print('+-----------------------------------------------------+')
        for i, entry in enumerate(self.menu_entries):
            marker = '*' if i == self.default else ' '
            print(f'{marker} {i}: {entry["title"]}')
        print('+-----------------------------------------------------+')
        print('Use ^ and v to select, Enter to boot, e to edit')

    def boot_entry(self, index):
        entry = self.menu_entries[index]
        print(f'\nLoading kernel: {entry["kernel"]}')
        print(f'Loading initramfs: {entry["initramfs"]}')
        print(f'Kernel parameters: {entry["params"]}')
        print('\nDecompressing Linux... Booting the kernel...')
        return entry

grub = GRUB2()
grub.add_entry('Ubuntu 24.04 LTS',
               '/vmlinuz-6.5.0-15-generic',
               '/initrd.img-6.5.0-15-generic',
               'root=UUID=abc123 ro quiet splash')
grub.add_entry('Ubuntu 24.04 LTS (Recovery Mode)',
               '/vmlinuz-6.5.0-15-generic',
               '/initrd.img-6.5.0-15-generic',
               'root=UUID=abc123 ro recovery nomodeset')
grub.add_entry('Ubuntu 24.04 LTS (Previous Kernel)',
               '/vmlinuz-6.5.0-14-generic',
               '/initrd.img-6.5.0-14-generic',
               'root=UUID=abc123 ro quiet splash')
grub.display_menu()
entry = grub.boot_entry(0)

Expected output:

GRUB version 2.12, timeout: 5s
+-----------------------------------------------------+
* 0: Ubuntu 24.04 LTS
  1: Ubuntu 24.04 LTS (Recovery Mode)
  2: Ubuntu 24.04 LTS (Previous Kernel)
+-----------------------------------------------------+
Use ^ and v to select, Enter to boot, e to edit

Loading kernel: /vmlinuz-6.5.0-15-generic
Loading initramfs: /initrd.img-6.5.0-15-generic
Kernel parameters: root=UUID=abc123 ro quiet splash

Decompressing Linux... Booting the kernel...

Stage 3: Kernel Initialization

The kernel decompresses itself, initializes subsystems, and mounts initramfs.

# View kernel boot messages
dmesg | head -30

# Check boot time for each kernel component
systemd-analyze

# Detailed breakdown of initrd time
systemd-analyze blame

# Graphics for boot performance
systemd-analyze plot > boot_plot.svg

Expected output (dmesg):

[    0.000000] Linux version 6.5.0-15-generic (buildd@ubuntu) ...
[    0.000000] Command line: BOOT_IMAGE=/vmlinuz-6.5.0-15-generic root=UUID=abc123 ro quiet splash
[    0.000000] KERNEL supported cpus:
[    0.000000]   Intel GenuineIntel
[    0.000000]   AMD AuthenticAMD
[    0.000000] x86/fpu: Supporting XSAVE feature 0x001
[    0.000000] SMBIOS 3.4 present.
[    0.000000] DMI: Dell Inc. Precision 7920 ...
[    0.012345] CPU0: Thermal monitoring enabled (TM1)
[    0.123456] PCI: Using MMCONFIG at [mem 0xe0000000-0xefffffff]
[    0.234567] pci 0000:00:00.0: [8086:2020] type 00 class 0x060000
[    0.345678] Initializing system trust keyring
[    0.456789] cryptd: max_cpu_qlen set to 1000
[    0.567890] AVX2 version of gcm_enc/dec engaged.
[    1.234567] raid6: avx2x4   gen() 31246 MB/s
[    2.345678] usbcore: registered new interface driver usb-storage
[    3.456789] EXT4-fs (sda2): mounted filesystem with ordered data mode

Expected output (systemd-analyze):

Startup finished in 3.456s (kernel) + 2.123s (initrd) + 8.901s (userspace) = 14.480s

Stage 4: initramfs and Early Userspace

initramfs (initial RAM filesystem) contains essential drivers and tools needed to mount the real root filesystem.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>

/* Simulated initramfs init script (normally a shell script) */

int main() {
    printf("[initramfs] Starting early userspace...\n");

    /* Mount essential filesystems */
    printf("[initramfs] Mounting /proc...\n");
    if (mount("proc", "/proc", "proc", 0, NULL) != 0)
        perror("mount proc");

    printf("[initramfs] Mounting /sys...\n");
    if (mount("sysfs", "/sys", "sysfs", 0, NULL) != 0)
        perror("mount sysfs");

    printf("[initramfs] Mounting /dev (devtmpfs)...\n");
    if (mount("devtmpfs", "/dev", "devtmpfs", 0, NULL) != 0)
        perror("mount devtmpfs");

    /* Load essential kernel modules */
    printf("[initramfs] Loading storage drivers...\n");
    system("modprobe ahci");
    system("modprobe nvme");

    /* Scan for root device */
    printf("[initramfs] Scanning for root device...\n");
    system("blkid");
    system("lvm vgscan --mknodes 2>/dev/null");

    /* Mount the real root filesystem */
    printf("[initramfs] Mounting real root...\n");
    if (mount("/dev/sda2", "/root", "ext4", MS_RDONLY, NULL) != 0) {
        fprintf(stderr, "[initramfs] ERROR: Cannot mount root!\n");
        fprintf(stderr, "[initramfs] Dropping to shell...\n");
        system("/bin/sh");
        return 1;
    }

    printf("[initramfs] Root filesystem mounted.\n");

    /* Switch root to the real filesystem */
    printf("[initramfs] Switching root to /root...\n");

    /* Move mounts to the new root */
    mount("/dev", "/root/dev", NULL, MS_MOVE, NULL);
    mount("/proc", "/root/proc", NULL, MS_MOVE, NULL);
    mount("/sys", "/root/sys", NULL, MS_MOVE, NULL);

    /* chroot and execute the real init */
    chdir("/root");
    mount(".", "/", NULL, MS_MOVE, NULL);
    chroot(".");
    execl("/sbin/init", "init", NULL);

    /* Should never reach here */
    fprintf(stderr, "ERROR: exec init failed!\n");
    return 1;
}

Expected output:

[initramfs] Starting early userspace...
[initramfs] Mounting /proc...
[initramfs] Mounting /sys...
[initramfs] Mounting /dev (devtmpfs)...
[initramfs] Loading storage drivers...
[initramfs] Scanning for root device...
[initramfs] Mounting real root...
[initramfs] Root filesystem mounted.
[initramfs] Switching root to /root...

Stage 5: systemd as PID 1

systemd takes over as the init Process (PID 1) and activates the default target.

# systemd boot analysis
systemd-analyze
systemd-analyze blame
systemd-analyze critical-chain

# List all systemd targets
systemctl list-units --type=target --all

# Check default target
systemctl get-default

# View boot log for specific service
journalctl -b -u ssh.service

# Check boot time in journal
journalctl --list-boots

# Boot into emergency mode (from GRUB, add 'systemd.unit=emergency.target')
import time

class SystemDUnit:
    def __init__(self, name, description, wants=None, before=None,
                 after=None, service_type='simple'):
        self.name = name
        self.description = description
        self.wants = wants or []
        self.before = before or []
        self.after = after or []
        self.service_type = service_type
        self.state = 'inactive'
        self.activation_time = 0
        self.pid = None

    def activate(self, current_time):
        print(f'  Activating: {self.name} ({self.description})')
        self.state = 'activating'
        time.sleep(0.1)
        self.state = 'active'
        self.activation_time = current_time + 0.1
        return self.activation_time

class SystemDManager:
    def __init__(self):
        self.units = {}
        self.targets = {}
        self.boot_time = 0

    def add_unit(self, unit):
        self.units[unit.name] = unit

    def add_target(self, name, description, requires=None):
        target = {
            'name': name,
            'description': description,
            'requires': requires or [],
            'state': 'inactive',
        }
        self.targets[name] = target

    def boot_default_target(self):
        default = 'graphical.target'
        print(f'Starting {default}...')
        self._activate_target(default)
        print('Reached target Graphical Interface')

    def _activate_target(self, target_name, depth=0):
        target = self.targets.get(target_name)
        if not target:
            return

        indent = '  ' * depth
        print(f'{indent}[Target] {target["name"]}')

        current_time = self.boot_time
        for req in target['requires']:
            if req in self.units:
                t = self.units[req].activate(current_time)
                current_time = max(current_time, t)
            elif req in self.targets:
                self._activate_target(req, depth + 1)

        target['state'] = 'active'

systemd = SystemDManager()
systemd.boot_time = 0

# Add target hierarchy
systemd.add_target('local-fs.target', 'Local File Systems',
                   requires=['systemd-fsck-root.service'])
systemd.add_target('sysinit.target', 'System Initialization',
                   requires=['local-fs.target', 'udevd.service'])
systemd.add_target('basic.target', 'Basic System',
                   requires=['sysinit.target', 'dbus.service'])
systemd.add_target('multi-user.target', 'Multi-User System',
                   requires=['basic.target', 'ssh.service'])
systemd.add_target('graphical.target', 'Graphical Interface',
                   requires=['multi-user.target', 'display-manager.service'])

# Add units
for u in ['systemd-fsck-root.service', 'udevd.service', 'dbus.service',
          'ssh.service', 'display-manager.service']:
    systemd.add_unit(SystemDUnit(u, f'{u} description'))

print('systemd boot sequence:\n')
systemd.boot_default_target()

Expected output:

systemd boot sequence:

Starting graphical.target...
[Target] graphical.target
  [Target] multi-user.target
    Activating: systemd-fsck-root.service (systemd-fsck-root.service description)
    [Target] basic.target
      [Target] sysinit.target
        Activating: udevd.service (udevd.service description)
        Activating: dbus.service (dbus.service description)
      [Target] local-fs.target
    Activating: ssh.service (ssh.service description)
  Activating: display-manager.service (display-manager.service description)
Reached target Graphical Interface

Common Mistakes

1. Breaking Boot by Editing GRUB Wrong

Editing /etc/default/grub and running update-grub without a backup can produce an unbootable system. Always keep a known-good kernel entry in GRUB (previous kernel is usually preserved).

2. Removing Old Kernels Without Testing

sudo apt autoremove removes old kernels. If the current kernel fails to boot, there's no fallback. Keep at least one previous kernel. Use sudo apt-mark hold linux-image-* on a known-good kernel.

3. Not Checking initramfs After Hardware Changes

After changing storage controllers or adding NVMe drives, the initramfs may not include the required drivers. Run sudo update-initramfs -u after hardware changes.

4. Ignoring systemd Critical Chain

Boot slowdowns are often caused by a single service with a long timeout. systemd-analyze critical-chain shows exactly which service is the bottleneck — don't guess.

5. Confusing dmesg Ring Buffer Size

The kernel ring buffer has limited size (usually 128KB). Old boot messages are overwritten. Use journalctl -k -b -1 to see the previous boot's messages.

Practice Questions

1. What is the difference between BIOS and UEFI boot? BIOS uses MBR Partitioning, runs in 16-bit real mode, and loads the first 512 bytes from disk. UEFI uses GPT Partitioning, runs in 64-bit mode, and loads an EFI application (.efi file) from the EFI System Partition (ESP). UEFI supports Secure Boot and has a boot manager.

2. What is the purpose of initramfs? initramfs provides the minimal drivers and tools needed to mount the real root filesystem. It contains storage drivers (SATA, NVMe, LVM, mdadm), filesystem modules (ext4, btrfs, xfs), and init scripts that discover and mount the root device.

3. How does systemd's parallel startup work? systemd reads unit dependencies and activates units in parallel where the dependency graph allows. Network and SSH start simultaneously if neither depends on the other. systemd-analyze plot shows the parallel execution chart.

4. Challenge: Create a Shell Script that mimics the boot Process: display each stage with timestamps, simulate "loading" with progress bars for firmware, GRUB, kernel, initramfs, and systemd. Use stty for progress bar animation.

5. Real-World Task: Boot a Linux VM and run systemd-analyze blame. Identify the three slowest boot services. For each, read its unit file (systemctl cat <service>) and understand why it's slow (waiting for network? timeout? hardware probe?). Suggest one optimization.

Mini Project: Boot Logger

import time
import random

class BootProcessLogger:
    """Simulate timing the entire boot process"""

    def __init__(self):
        self.events = []

    def log_event(self, phase, message, duration=0):
        self.events.append({
            'phase': phase,
            'message': message,
            'timestamp': sum(e.get('duration', 0) for e in self.events),
            'duration': duration,
        })

    def simulate_boot(self):
        total = random.uniform(8, 20)
        elapsed = 0

        self.log_event('firmware', 'UEFI initialization', random.uniform(1, 3))
        self.log_event('firmware', 'Secure Boot check', random.uniform(0.1, 0.5))
        self.log_event('firmware', 'Boot device selection', random.uniform(0.1, 0.3))

        self.log_event('grub', 'GRUB2 loading', random.uniform(0.5, 1.5))
        self.log_event('grub', 'Kernel loaded into memory', random.uniform(0.2, 0.8))
        self.log_event('grub', 'initramfs loaded', random.uniform(0.1, 0.4))

        self.log_event('kernel', 'Kernel decompression', random.uniform(0.3, 1.0))
        self.log_event('kernel', 'CPU/PCI enumeration', random.uniform(1.0, 3.0))
        self.log_event('kernel', 'Root device detection', random.uniform(0.2, 0.8))

        self.log_event('initramfs', 'Mount /proc /sys /dev', random.uniform(0.1, 0.3))
        self.log_event('initramfs', 'Load storage modules', random.uniform(0.3, 0.7))
        self.log_event('initramfs', 'Switch root', random.uniform(0.1, 0.2))

        self.log_event('systemd', 'systemd PID 1 start', random.uniform(0.1, 0.3))
        self.log_event('systemd', 'Mount local filesystems', random.uniform(0.5, 2.0))
        self.log_event('systemd', 'Start networking', random.uniform(0.5, 1.5))
        self.log_event('systemd', 'Start display manager', random.uniform(1.0, 3.0))

    def print_timeline(self):
        print(f'{"Phase":<12} {"Event":<35} {"Time (s)":<10} {"Duration (s)":<12}')
        print('-' * 69)
        for ev in self.events:
            print(f'{ev["phase"]:<12} {ev["message"]:<35} '
                  f'{ev["timestamp"]:<10.2f} {ev["duration"]:<12.2f}')
        total = sum(e['duration'] for e in self.events)
        print(f'\nTotal boot time: {total:.2f}s')

boot = BootProcessLogger()
boot.simulate_boot()
boot.print_timeline()

Expected output:

Phase        Event                               Time (s)   Duration (s)
---------------------------------------------------------------------
firmware     UEFI initialization                 0.00        2.34
firmware     Secure Boot check                   2.34        0.23
firmware     Boot device selection               2.57        0.15
grub         GRUB2 loading                       2.72        1.12
...
systemd      Start display manager               12.45       2.10

Total boot time: 14.55s

FAQ

What is the difference between multi-user.target and graphical.target?

multi-user.target boots to a console/terminal (no GUI) — equivalent to runlevel 3. graphical.target boots to a desktop (GDM, LightDM, SDDM) — equivalent to runlevel 5. Server systems typically use multi-user.target; desktops use graphical.target.

How do I recover from a failed boot?

Boot into recovery mode from GRUB (select the recovery entry). This drops you to a root shell with most services disabled. From there you can fix configuration files, reinstall packages, or restore a backup. If even GRUB fails, use a live USB.

What is the difference between sysvinit and systemd?

Sysvinit uses sequential shell scripts (rc.d) that start one service at a time. systemd uses parallel activation with dependency resolution, socket/timer activation, and service supervision. systemd is faster (parallel startup) and more reliable (restarts failed services).

Embedded Linux
System Calls
Kernel Modules

What's Next

You now understand the Linux boot process. Next, learn about embedded Linux systems to see how the boot Process changes on resource-constrained devices, or explore system calls for the kernel-userspace interface.

  • Practice daily — Each time you boot a system, time it with systemd-analyze and note the total.
  • Build a project — Create a minimal kernel + initramfs that boots to a shell. Use BusyBox for the initramfs.
  • Explore related topics — Study UEFI application development and bootkit security.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro