Just Enough Arch Linux

Last edited on 2026-02-22 Tagged under  #arch   #linux   #encrypt   #btrfs   #limine 

Using the Arch Linux installation image and working my way through the excellent installation guide, I show the choices I make to create an encrypted, minimal Linux system with “just enough” to provide a solid foundation to build upon further: whether that be setting up a desktop, laptop, or server.

ascii kitty

1. Start Here

Throughout this HOWTO, if you see square brackets [] in code blocks, that means the word of code (square brackets included) should be replaced with something else. This is detailed in the instructions before or after the code block.

Arch Linux will be installed as the sole operating system on a single disk using a two-partition layout:

  • Partition root is encrypted with LUKS2 and formatted with the BTRFS file system using subvolumes.
  • Partition esp is formatted with the FAT32 file system and mounted to boot. Because this partition will also be storing kernels and initramfs in addition to EFI-related files - and to future-proof it for whatever else Linux might want to store there - I assign it a generous 4GB of storage.
  • In lieu of creating a partition for swap, the zram kernel module is used to create a compressed block device in RAM to provide swap space.

A few assumptions:

  • Target device is x86_64 architecture using UEFI to boot.
  • Network access during install uses a wired interface.
  • Limine will be used as the system bootloader.

Acquire an installation image

The latest official installation images are available here: Torrents and download mirrors

Download archlinux-[RELEASE]-x86_64.iso and sha256sums.txt. As of February 2026 the latest RELEASE is 2026.02.01.

On a Linux system, verify the integrity of the image by running:

sha256sum -c --ignore-missing sha256sums.txt

Prepare the USB installation medium

Write the installer to an unmounted USB storage device running the dd command as root.

WARNING
Be very careful to note the proper device (which can be identified with lsblk). All contents on the device will be lost!

Example: On a Linux system, if a USB stick appears as sdx1, then write the installer to sdx (omit partition number):

sudo dd if=archlinux-2026.02.01-x86_64.iso of=/dev/sdx bs=4M conv=fsync oflag=direct status=progress; sync

2. Configure the Live Environment

Boot the target device from the Arch installation media. User is automatically logged in as root to the first virtual console.

Set the console keyboard

Default console keymap is us. List available layouts:

localectl list-keymaps

If some other keymap is desired, set a different keymap temporarily:

loadkeys [keymap]

…where [keymap] is the desired keyboard layout.

Example: I configure the system to use my preferred colemak layout:

loadkeys colemak

Set the console font

If the existing font size appears too small, running:

setfont -d

… will double the size.

Console fonts are located in /usr/share/kbd/consolefonts/ and a different font can be set with setfont omitting the path and file extension:

setfont [FONT]-[X][SIZE][STYLE]

…where [FONT] is font name, [X] is a character identifying the code page, [SIZE] is font height, and [STYLE] is n for normal, b for bold, v for CRT VGA bold.

Example: Temporarily load the terminus font in bold:

setfont ter-122b

See /usr/share/terminus-font/README and Fonts for more details.

Verify the boot mode

Confirm target device is using UEFI boot mode:

cat /sys/firmware/efi/fw_platform_size

If the command returns 64, then system is booted in UEFI with 64-bit x64 UEFI and we are good to go.

NOTE
If the file does not exist, the device is not using UEFI. Stop here and consult the official Installation Guide on how to proceed with the install on a device using BIOS boot mode.

Connect to the internet

Wired network interfaces should be auto-enabled and connected at boot.

Verify the network interface is active, has been assigned an address, and the internet is reachable using ip-address and ping commands:

ip addr
ping -c 5 archlinux.org

If this fails, or a wireless interface is required, see the Installation Guide.

Remote login to the installer

Make this manual installation process easier (i.e. cut-n-paste commands) by remotely logging into the installer via ssh from another computer.

Confirm sshd daemon was started at boot:

systemctl status sshd

… otherwise, start the service:

systemctl start sshd.service

Set a password for root:

passwd

Switch to the other computer and ssh into the target device:

ssh root@[ip_address]

…where [ip_address] is the target device’s address obtained with the ip addr command above.

Update the system clock

The systemd-timesyncd service is enabled by default by the installer and the time and date will be synchronized automatically once access to the internet is established.

Verify the system clock is synchronized:

timedatectl

3. Prepare the DISK

Setup a custom partition layout on a single disk before implementing the Arch base installation.

Define DISK variables

Identify the disk where Arch will be installed by listing block devices:

lsblk -f

Set DISK variables for either a SATA or NVMe disk:

SATA

Example disk: sda

export DISK="/dev/sda"
export ESP_PART="1"
export ROOT_PART="2"
export ESP_DISK="${DISK}${ESP_PART}"
export ROOT_DISK="${DISK}${ROOT_PART}"

NVMe

Example disk: nvme0n1

export DISK="/dev/nvme0n1"
export ESP_PART="1"
export ROOT_PART="2"
export ESP_DISK="${DISK}p${ESP_PART}"
export ROOT_DISK="${DISK}p${ROOT_PART}"

Erase DISK

Erase existing file systems and partition table on DISK:

wipefs -af $DISK && sgdisk --zap-all --clear $DISK

Notify the system of changes to the partition table:

partprobe $DISK

NOTE
If DISK was previously configured with LVM, this operation might fail with an error such as Device or resource busy. This is because the volume group might have been set up on boot. In such cases, first bring down the volume group:

vgchange -an

After that, wipefs and sgdisk should work as expected.

Partition DISK

Create a GPT partition table on DISK with the following layout:

NumberSizeCodeFormatUse as
14gef00vfatESP partition
2->END8309luksEncrypted root partition

Create the ESP partition:

sgdisk -n "${ESP_PART}:1m:+4g" -t "${ESP_PART}:ef00" -c 0:esp $DISK

Create the encrypted root partition:

sgdisk -n "${ROOT_PART}:0:0" -t "${ROOT_PART}:8309" -c 0:root $DISK

Display layout:

partprobe $DISK && sgdisk -p $DISK

Format the ESP partition

NOTE
Labels on file systems are optional, but helpful. They allow for easy mounting without a UUID.

Create a FAT32 file system:

mkfs.fat -n ESP -F 32 $ESP_DISK

Encrypt the root partition

Encrypt the partition using luks2:

cryptsetup luksFormat -y --type luks2 $ROOT_DISK

The newly-created LUKS device is opened and mapped to /dev/mapper/root, as suggested by the Discoverable Partitions Specification:

cryptsetup open $ROOT_DISK root

Define a variable for the root device:

export ROOT_DEV="/dev/mapper/root"

Format the root device

Create a BTRFS file system:

mkfs.btrfs -L arch $ROOT_DEV

Mount the root device:

mount $ROOT_DEV /mnt

Create subvolumes

Changing BTRFS subvolume layouts is made simpler by not mounting the top-level subvolume as / (which is the default).

As an alternative, create a BTRFS subvolume that contains the actual data, and mount that to /. Use @ for the name of this new subvolume (which is the default name used by Snapper, a tool for making file system snapshots):

btrfs subvolume create /mnt/@

Create additional subvolumes to facilitate system rollbacks that leave logs, databases, and home files untouched:

btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@cache
btrfs subvolume create /mnt/@log
btrfs subvolume create /mnt/@tmp
btrfs subvolume create /mnt/@srv
btrfs subvolume create /mnt/@snapshots

Define variable for subvolume mount options

For an installation on a NVMe disk, these are the subvolume mount options I use:

  • noatime: Disables writing “last accessed” timestamps. Extends SSD lifespan and improves read speeds.
  • compress=zstd:1: Compression algorithm/setting “sweet spot” for NVMe (default is 3). For non-NVMe disks, omit this setting and accept the default.
  • space_cache=v2: Method to track free blocks. Significantly more efficient than v1.
  • commit=120: Default commit interval is 30 seconds. Increasing the interval allows BTRFS to bundle small writes in memory into fewer, larger sequential writes. If the system is connected to a UPS or healthy battery, the commit interval can be increased because the risk of a sudden power-loss shutdown is much lower.

Define a variable with these options:

SUB_OPTS="noatime,compress=zstd:1,space_cache=v2,commit=120"

For more options and details, see the BTRFS Administration page.

Mount the subvolumes

Unmount the previously mounted root device:

umount /mnt

Mount the subvolumes:

mount -o ${SUB_OPTS},subvol=@ $ROOT_DEV /mnt
mount --mkdir -o ${SUB_OPTS},subvol=@home $ROOT_DEV /mnt/home
mount --mkdir -o ${SUB_OPTS},subvol=@cache $ROOT_DEV /mnt/var/cache
mount --mkdir -o ${SUB_OPTS},subvol=@log $ROOT_DEV /mnt/var/log
mount --mkdir -o ${SUB_OPTS},subvol=@tmp $ROOT_DEV /mnt/var/tmp
mount --mkdir -o ${SUB_OPTS},subvol=@srv $ROOT_DEV /mnt/srv
mount --mkdir -o ${SUB_OPTS},subvol=@snapshots $ROOT_DEV /mnt/.snapshots

Mount the ESP partition

mount --mkdir $ESP_DISK /mnt/boot && df -h

4. Installation

Select the mirrors

Packages to be installed must be downloaded from mirror servers, which are defined in /etc/pacman.d/mirrorlist. Generate a new mirrorlist using reflector.

Backup the existing mirrorlist:

cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak

Example: This command will select the 5 most recently synchronized HTTPS mirrors located in Canada, sort them by download speed, and overwrite the mirrorlist with the new links:

reflector --verbose --protocol https --latest 5 --sort rate --country Canada --save /etc/pacman.d/mirrorlist

Synchronize the pacman package databases using the new mirror list:

pacman -Syy

Install

Identify the processor vendor:

grep vendor_id /proc/cpuinfo

Create a variable for an appropriate microcode package to load updates and security fixes:

UCODE="[vendor]-ucode"

…where [vendor] for Intel processors is intel and AMD processors is amd.

Create a variable for an editor (nano, vim, etc.) used to modify configuration files after we chroot into the new system:

export EDIT="nano"

Use pacstrap to install the base package, Linux kernel, firmware for common hardware, crypt and file utilities, and some nice extras:

pacstrap -K /mnt base base-devel linux linux-firmware btrfs-progs cryptsetup efibootmgr limine man-db networkmanager openssh reflector sudo terminus-font $UCODE $EDIT

5. Configure the System

Fstab

Generate an fstab file:

genfstab -L /mnt >> /mnt/etc/fstab && cat /mnt/etc/fstab

Inspect the results for any possible errors.

Chroot

Chroot into the newly-installed base system:

arch-chroot /mnt /bin/bash

Zram swap

Load the module at boot:

echo "zram" > /etc/modules-load.d/zram.conf

Create the following udev rule adjusting the disksize attribute (1/2 of physical RAM is a good benchmark) as necessary:

$EDIT /etc/udev/rules.d/99-zram.rules

Add rule:

ACTION=="add", KERNEL=="zram0", ATTR{initstate}=="0", ATTR{comp_algorithm}="zstd", ATTR{disksize}="4G", TAG+="systemd"

Save changes and exit.

Add zram0 to fstab with a higher than default priority and the x-systemd.makefs option:

echo -e "/dev/zram0\tnone\tswap\tdefaults,discard,pri=100,x-systemd.makefs\t0 0" >> /etc/fstab

After rebooting the system, check status with:

zramctl

Time

Timezones are located in /usr/share/zoneinfo.

List the timezones:

timedatectl list-timezones

Set the desired timezone:

timedatectl set-timezone [Region]/[City]

…where [Region] is the geographical region (Africa, America, Europe, …) and the [City] within that region.

Example: Timezone where Toronto is located:

timedatectl set-timezone America/Toronto

Update the system clock:

hwclock --systohc

Enable network time synchronization:

timedatectl set-ntp true

Show current time settings:

timedatectl

Localization

Open the list of locales:

$EDIT /etc/locale.gen

… and uncomment any desired locales.

Save changes and exit.

Generate the locales by running:

locale-gen

Set the LANG variable:

echo "LANG=[locale]" > /etc/locale.conf

… where [locale] is one of the generated locales (example: en_CA.UTF-8):

echo "LANG=en_CA.UTF-8" > /etc/locale.conf

Console keymap and font

If earlier during the installation a different console keyboard layout than us was selected, make the change persistent by writing the choice to vconsole.conf:

echo "KEYMAP=[keyboard]" >> /etc/vconsole.conf

…where [keyboard] is your desired keymap (example: colemak):

echo "KEYMAP=colemak" >> /etc/vconsole.conf

Same process if the default font was changed:

echo "FONT=[font]" >> /etc/vconsole.conf

… which in this HOWTO would be ter-122b:

echo "FONT=ter-122b" >> /etc/vconsole.conf

Hostname

Create the hostname file:

echo [hostname] > /etc/hostname

…where [hostname] is the desired name of the system (single word, no spaces):

echo "archlinux" > /etc/hostname

Network configuration

Enable NetworkManager to start at boot:

systemctl enable NetworkManager

Enable sshd to start at boot:

systemctl enable sshd

Initramfs

Configure the initramfs image to be generated by opening the mkinitcpio.conf file for editing:

$EDIT /etc/mkinitcpio.conf

Set the necessary MODULES:

MODULES=(btrfs)

… and BINARIES:

BINARIES=(/usr/bin/btrfs)

NOTE
Order of the hooks matters. See Hook List.

HOOKS control the modules and scripts added to the image and what happens at boot time:

HOOKS=(base udev keyboard autodetect microcode modconf kms keymap consolefont block encrypt filesystems fsck)

Save changes and exit.

Recreate the initramfs image with mkinitcpio:

mkinitcpio -P

Root password

Set a password for root:

passwd

Superuser

Create a user account with superuser privileges:

useradd -m -G wheel -s /bin/bash [username]

…where [username] is the desired name for the account.

Set a password for [username]:

passwd [username]

Activate wheel group access for the sudo command:

sed -i "s/# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/" /etc/sudoers

Boot loader

Create the /boot/EFI/BOOT directory and copy the Limine BOOT file there:

mkdir -p /boot/EFI/BOOT
cp /usr/share/limine/BOOTX64.EFI /boot/EFI/BOOT/

Limine does not add an entry for the boot loader in the NVRAM. Use efibootmgr to create an entry:

efibootmgr --create --disk $DISK --part $ESP_PART --label "Arch Linux Limine Boot Loader" --loader '\EFI\BOOT\BOOTX64.EFI' --unicode

Limine does not provide a default configuration file, it is therefore necessary to create one.

Firstly, make note of the LUKS device UUID required by the config file, which is retrieved by running:

cryptsetup luksUUID $ROOT_DISK

Create the limine.conf:

$EDIT /boot/limine.conf

Add entry:

timeout: 3

/Arch Linux
  protocol: linux
  path: boot():/vmlinuz-linux
  cmdline: cryptdevice=UUID=[device-UUID]:root root=/dev/mapper/root rootflags=subvol=@ rw rootfstype=btrfs
  module_path: boot():/initramfs-linux.img

Replace the [device-UUID] above with the UUID of the LUKS device.

Save changes and exit.

6. Finish Up

Exit chroot:

exit

Unmount partitions:

umount /mnt/boot
umount -l -n -R /mnt

Remove encrypted device mapping:

cryptsetup close root

Reboot system:

reboot

User is prompted for the passphrase to unlock the encrypted root partition. Upon success, boot resumes…

archlinux login:

Welcome to Arch!

7. Resources

  • Not just for Arch Linux, but for Linux in general, the Arch Wiki is a tremendous resource.

You can like, share, or comment on this post on the Fediverse 💬

Thanks for reading! Read other posts?

» Next: Configure SSH on Linux for Passwordless Logins to Servers

« Previous: Minimal Alpine Linux