Just Enough Arch Linux
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.
- 1. Start Here
- 2. Configure the Live Environment
- 3. Prepare the DISK
- 4. Installation
- 5. Configure the System
- 6. Finish Up
- 7. Resources
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
rootis encrypted with LUKS2 and formatted with the BTRFS file system using subvolumes. - Partition
espis formatted with the FAT32 file system and mounted toboot. 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_64architecture 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:
| Number | Size | Code | Format | Use as |
|---|---|---|---|---|
| 1 | 4g | ef00 | vfat | ESP partition |
| 2 | ->END | 8309 | luks | Encrypted 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 is3). For non-NVMe disks, omit this setting and accept the default.space_cache=v2: Method to track free blocks. Significantly more efficient thanv1.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 💬
» Next: Configure SSH on Linux for Passwordless Logins to Servers
« Previous: Minimal Alpine Linux