After using EndeavourOS, an Arch-based distro, for some time with much pleasure and appreciating Arch mechanisms (packages and AUR), I decided it was time to try the “real thing” and install Arch the “hard way” 🙂 Spoiler: it’s not that hard!
I thought it was hard. For sure, it’s more complicated than other distro installation procedures, but, to be honest, after using Linux for more than 20 years, I thought there was not much to be scared of 😉
I now use Arch (besides other distros) on my machines greatly. Of course, I did many experiments with virtual machines before installing Arch on bare metal. I know there are many guides and tutorials, but I’d like to summarize my steps for installing Arch (with a SWAP partition, an EXT4 partition for data to be shared among distros, and a BTRFS primary partition). In particular, in this blog post, I’ll describe my steps for installing Arch on a virtual machine, which, as I’ve just said, it’s the best way to get confident with Arch and not be scared of installing Arch on a real computer. Moreover, on many guides, I noticed a few missing points, which, instead, are essential.
Of course, the best reference is the excellent official guide, and I’ll use the official guide as a reference while following along, https://wiki.archlinux.org/title/installation_guide. Note that there are still a few parts in the guide that refer to other parts of the excellent Arch wiki, and I had a few minor problems the first time I tried the Arch installation.
Here we go!
Create and configure the virtual machine with enough disk space (dynamically allocated so you won’t waste space on your disk), let’s say 100Gb. Make sure you enable EFI in the virtual machine configuration. Of course, insert the Arch Linux ISO as a live CD in the virtual machine. I’m going to use archlinux-2022.09.03-x86_64.iso.
As described in a previous post, I’d suggest performing the installation by connecting via SSH to the virtual machine. This way, you’re using a local terminal: copy and paste will work (since you’re on a local terminal). In particular, since the Arch installer is textual, being able to copy and paste commands from a local terminal makes everything easier. Moreover, the keyboard layout will be the host system’s keyboard layout. Thus, the keyboard layout will be already configured correctly. While in the virtual machine, you’d have to configure the keyboard layout.
(On a side note, even when installing Arch on a real computer, I prefer doing that via SSH, of course, from another computer.)
Before starting the virtual machine, we must map the SSH port of the virtual machine to a local port to connect from our computer. This requires knowing the name you gave to your virtual machine. In this example, I called the virtual machine “Arch Gnome” (because I’ll then install Gnome on the Arch installation). We must run these instructions from the host computer:
VBoxManage modifyvm "Arch Gnome" --natpf1 "SSH,tcp,127.0.0.1,2522,10.0.2.15,22"
Port 2522 is the one we’ll have to use later for connecting to the virtual machine via localhost. Of course, feel free to use another free port number as long as you’ll use it consistently from now on.
Start the virtual machine:
Inside the live environment, the SSH server is already up and running. However, since we’ll connect with the root account (the only one present), we must give the root account a password. By default, it’s empty, and SSH will not allow you to log in with a blank password. Choose a password. This password is temporary, and if you’re in a trusted local network, you can choose an easy one.
Now, we can connect via SSH to the virtual machine through localhost. if you have already connected via SSH to localhost, you might get an error of the shape:
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
Please contact your system administrator.
Add correct host key in <YOUR HOME>/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in <YOUR HOME>/.ssh/known_hosts:<A LINE NUMBER>
Host key for [127.0.0.1]:2522 has changed and you have requested strict checking.
Host key verification failed.
All you have to do is edit the known_hosts file by removing the offending lines and try again. You will have to remove all the lines that start with “[127.0.0.1]:2522”.
Note that we’re using port 2522 because we previously used that for creating the port mapping. Let’s connect to the virtual machine and type the password we have previously specified for the root account inside the virtual machine (Accept the fingerprint when asked.):
ssh -p 2522 email@example.com
In your local terminal, you see that you get the colors of the virtual machine (now, you’re inside the virtual machine):
Let’s set the console keyboard layout (the default layout is US; if that’s fine with you, skip the next step). This step is not strictly required for our local terminal: even if we’re inside the virtual machine, we’re using our local terminal, so we already use the correct layout. However, let’s do that anyway since we want to simulate an actual installation. Moreover, having the proper layout is good if we want to run commands directly from the VirtualBox window.
I already know the layout I want for my Italian keyboard, so I run:
If you don’t know the exact layout, you can list the available ones with
You can verify in the VirtualBox window that the layout is applied correctly.
Since we are in a virtual machine, the machine should already be able to access the Internet if your host is correctly connected (and that’s required to install Arch Linux). However, if you want to simulate what you would do with an actual installation on bare metal, you can ping a remote host and verify that everything’s OK:
Before going on, as suggested in the official guide, it’s better to make sure the system clock is accurate by enabling network synchronization NTP:
Partitioning the disk
How to partition the disk is your choice. In this example, I will partition the disk according to my needs. However, you need at least two partitions: one for booting in UEFI mode and one for the root filesystem.
In this example, I’ll create four partitions:
- the one for booting in UEFI mode, formatted as FAT32, 300Mb (it should be enough for UEFI, but if unsure, go on with 512Mb)
- a swap partition, 20Gb (I have 16Gb, and if I want to enable hibernation, i.e., suspend to disk, that should be enough)
- a partition meant to host common data that I want to share among several Linux installations on the same machine (maybe I’ll blog about that in the future), formatted as EXT4, 30Gb
- the root partition, formatted as BTRFS, the rest of the disk
To do that, I’m using cfdisk, a textual partition manager, which I find easy to use.
Now, it is time to get to know the device name of our disk using the command lsblk:
root@archiso ~ # lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 683.2M 1 loop /run/archiso/airootfs
sda 8:0 0 100G 0 disk
sr0 11:0 1 794.3M 0 rom /run/archiso/bootmnt
As you might guess, sda is the disk we are installing Arch Linux upon. In a virtual machine, that’s probably always like that. On a real machine, it might be different (for example, if you have an NVME SSD, it will be something like nvme0n1). It’s needless to say that using the correct device name is crucial, especially on a real machine, or you might end up wiping away essential data. The nice thing about a virtual machine is that you’re in a “sandbox,” so, at worst, you’ll break your virtual machine.
So I run
If it is a new virtual machine, you’ll be asked a partition table: choose gpt.
Start creating your partitions. Just use the menus of cfdisk; it’s easy (on the bottom, you will find some help). Once you create a partition, set the “Type” correctly. By default, the type is “Linux filesystem”. For UEFI, you have to specify the type “EFI System,” and for the swap partition, “Linux swap”.
That’s my final result:
Let’s “Write” the partition table to disk and “Quit”. We can also verify with lsblk that the result is as expected:
root@archiso ~ # lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 683.2M 1 loop /run/archiso/airootfs
sda 8:0 0 100G 0 disk
├─sda1 8:1 0 300M 0 part
├─sda2 8:2 0 20G 0 part
├─sda3 8:3 0 30G 0 part
└─sda4 8:4 0 49.7G 0 part
sr0 11:0 1 794.3M 0 rom /run/archiso/bootmnt
Of course, you must know which partition is meant for what. In my example, sda1 is for UEFI, sda2 for swap, sda3 for my shared data, and sda4 for root.
Format the partitions
According to my intended layout shown above, I’ll format the four partitions with the following commands:
mkfs.fat -F 32 /dev/sda1
Mount the partitions
This is also delicate, so you must use the correct device names. What follows is, of course, correct according to my layout.
First of all, let’s deal with the swap partition:
The presence of the BTRFS file system for the root partition makes things a bit more interesting (or a bit more complicated, as you prefer 😉
First, we must mount the BTRFS filesystem on /mnt. Note that we are mounting the BTRFS on /mnt only temporarily and to create the subvolumes (in a minute, we will mount the subvolumes in their final shape on /mnt, together with the other partitions):
This will allow us to create the subvolumes. Again, what follows is the BTRFS subvolume layout I prefer. You might want to choose a different one. To use Timeshift, you must have at least @ for / and @home for /home. This is how I create the subvolumes I want:
btrfs su cr /mnt/@
btrfs su cr /mnt/@home
btrfs su cr /mnt/@cache
btrfs su cr /mnt/@log
btrfs su cr /mnt/@machines
btrfs su cr /mnt/@portables
As I said, this mount was temporary, just for creating subvolumes. In fact, we now unmount /mnt:
And we mount every single subvolume in its final “position” inside /mnt by also specifying a few additional options like the general “noatime” and the BTRFS-specific “compress” to enable the ztsd compression:
mount -o subvol=/@,defaults,noatime,compress=zstd /dev/sda4 /mnt
mount -o subvol=/@home,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/home
mount -o subvol=/@cache,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/var/cache
mount -o subvol=/@log,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/var/log
mount -o subvol=/@machines,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/var/lib/machines
mount -o subvol=/@portables,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/var/lib/portables
The “-m” option makes mount create the target directory if it does not exist.
Finally, we can mount the remaining partitions. The UEFI one should be mounted to “/boot/efi” inside “/mnt”. I like to mount the “common” partition in “/media/bettini/common” inside “/mnt” because that’s where I’ll use it (relying on the fact that I’ll create a user “bettini” for myself). Again, choose something else for yourself. These are the commands:
mount -o defaults,noatime -m /dev/sda1 /mnt/boot/efi
mount -o defaults,noatime -m /dev/sda3 /mnt/media/bettini/common
This is the final layout of /mnt, which, remember, is where our system will be installed:
│ └── efi
│ └── bettini
│ └── common
│ └── lost+found
│ ├── machines
│ └── portables
Select the mirrors
This part, documented in the official installation guide, is usually skipped in several blog posts I found online. Instead, this step is essential.
The mirrors are specified in the file /etc/pacman.d/mirrorlist.
The guide says:
On the live system, after connecting to the internet, reflector updates the mirror list by choosing 20 most recently synchronized HTTPS mirrors and sorting them by download rate.
The higher a mirror is placed in the list, the more priority it is given when downloading a package. You may want to inspect the file to see if it is satisfactory. If it is not, edit the file accordingly, and move the geographically closest mirrors to the top of the list, although other criteria should be taken into account
You can verify that by inspecting the file /etc/pacman.d/mirrorlist. In my case, the Italian mirror is the last one, so it will be given the lowest priority. This sounds wrong to me. In particular, the documentation also points out:
This file will later be copied to the new system by pacstrap, so it is worth getting right.
Thus, I prefer to run the program reflector myself (see the reflector documentation for the single arguments; of course, I’m using “Italy” as the country because that’s where I leave; I could also specify several values separated by a comma, e.g., “Italy,Germany”):
--country Italy \
--age 12 \
--protocol https \
--fastest 5 \
--latest 20 \
--sort rate \
The following step does not seem to be required in the installation guide. However, to make sure we have an updated PGP keyring (for checking signatures of packages), at this point, I also run:
pacman -Sy --noconfirm archlinux-keyring
Now, it’s time to install the base packages, Linux kernel, and firmware for standard hardware using the pacstrap script. You specify the target directory, which, as you might guess, it’s /mnt, and the packages.
This is the command I run (I prefer to use the LTS kernel; if you want the latest kernel, use “linux” package instead of “linux-lts”; you can also install them both and then select one from the grub menu):
pacstrap /mnt base linux-lts linux-firmware \
nano vim \
This command will download about 500Mb, which might take time depending on your Internet speed.
Configuring the system
Since we have already manually mounted all our partitions (on /mnt), the Arch ISO can generate for us the file fstab automatically through the command genfstab:
genfstab -U /mnt >> /mnt/etc/fstab
The “-U” option tells genfstab to use UUID to refer to partitions (alternatively, “-L” can be used to use labels instead).
You can have a look at the result (of course, UUID will be different in your case):
# Static information about the filesystems.
# See fstab(5) for details.
# <file system> <dir> <type> <options> <dump> <pass>
UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 / btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=256,subvol=/@ 0 0
UUID=84BE-A81A /boot/efi vfat rw,noatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 2
UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /home btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=257,subvol=/@home 0 0
UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /var/cache btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=258,subvol=/@cache 0 0
UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /var/log btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=259,subvol=/@log 0 0
UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /var/lib/machines btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=260,subvol=/@machines 0 0
UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /var/lib/portables btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=261,subvol=/@portables 0 0
UUID=8751420a-136e-4076-b748-bb74675bf979 /media/bettini/common ext4 rw,noatime 0 2
UUID=c1e636ae-6452-4724-9197-062f19968bdd none swap defaults 0 0
Note that although we used the option “compress=zstd” when mounting our BTRFS subvolumes, genfstab turned that into “compress=zstd:3” because “3” is the default compression value for zstd in BTRFS. If we wanted to make the compression value explicit, e.g., “1”, we should have done that when mounting the subvolumes. Of course, you can always tweak the generate fstab as you see fit.
Now, we can “enter” our installation with “chroot” or, better, with the enhanced arch-chroot, which automatically binds other things like /dev and /proc:
The root directory is what’s inside /mnt, so / refers to what’s inside /mnt. Also, the prompt has changed to reflect this:
root@archiso ~ # arch-chroot /mnt
We now set the timezone of the installed system. You must use Region/City according to your location. Timezones are available in the directory /usr/share/zoneinfo/. In my case (Italy), I run:
ln -sf /usr/share/zoneinfo/Europe/Rome /etc/localtime
Then, we use hwclock to set the Hardware Clock from the System Clock:
Then, we edit /etc/locale.gen and uncomment the locales we need; in my case, en_US.UTF-8 UTF-8 and it_IT.UTF-8. Since I already know the two locales, instead of editing the file (where all locales are commented out), I append the two locales to the end of that file:
echo en_US.UTF-8 UTF-8 >> /etc/locale.gen
echo it_IT.UTF-8 UTF-8 >> /etc/locale.gen
And we generate the locales:
We must also create the /etc/locale.conf file, and set the LANG variable accordingly. This can be done as follows:
echo LANG=en_US.UTF-8 >> /etc/locale.conf
We also make permanent the initial changes to the console layout (remember, I used “it”; in your case, you need to use the code you previously specified):
echo KEYMAP=it >> /etc/vconsole.conf
We’ll deal with the network configuration (of the installed system) in a minute. But we can already create the files /etc/hostname and /etc/hosts. You have to choose your preferred hostname. In this example, I’m going to use “arch-vm-gnome”. So I generate the two files with the following two commands:
echo arch-vm-gnome >> /etc/hostname
cat >> /etc/hosts << EOF
127.0.1.1 myhostname.localdomain arch-vm-gnome
The boot loader
Since we use BTRFS, we might want to tweak the file /etc/mkinitcpio.conf with these two modules:
And regenerate the initramfs images:
Let’s now install the bootloader. I prefer GRUB. So let’s install a few packages:
pacman -S --noconfirm --needed grub efibootmgr
During the installation, you might want to take note of the recommendation for installing and configuring grub and the optional dependencies:
:: Install your bootloader and generate configuration with:
$ grub-install ...
$ grub-mkconfig -o /boot/grub/grub.cfg
Optional dependencies for grub
freetype2: For grub-mkfont usage
fuse2: For grub-mount usage
dosfstools: For grub-mkrescue FAT FS and EFI support
lzop: For grub-mkrescue LZO support
efibootmgr: For grub-install EFI support [pending]
libisoburn: Provides xorriso for generating grub rescue iso using
os-prober: To detect other OSes when generating grub.cfg in BIOS systems
mtools: For grub-mkrescue FAT FS support
Now we install grub in the UEFI partition. Note that, unlike standard GUI Linux installations, you can specify the “–bootloader-id”, which will be the identifier of this grub installation in UEFI. This is useful if you have several bootloaders on your machine. In this example, I’m using ArchGnome:
grub-install --target=x86_64-efi --bootloader-id=ArchGnome --efi-directory=/boot/efi
Hopefully, the installation should succeed, and we can generate the grub menu:
grub-mkconfig -o /boot/grub/grub.cfg
Here’s the output of these two commands:
[root@archiso /]# grub-install --target=x86_64-efi --bootloader-id=ArchGnome --efi-directory=/boot/efi
Installing for x86_64-efi platform.
Installation finished. No error reported.
[root@archiso /]# grub-mkconfig -o /boot/grub/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-linux-lts
Found initrd image: /boot/intel-ucode.img /boot/initramfs-linux-lts.img
Found fallback initrd image(s) in /boot: intel-ucode.img initramfs-linux-lts-fallback.img
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
Adding boot menu entry for UEFI Firmware Settings ...
Don’t forget to set the following passwords, or you will not be able to log in to the installed system once you reboot later.
We can now set the root password. This is the effective password for root in the installed system. This should be chosen carefully:
I prefer to use “sudo”, so I first install that
This is also the moment to create your own user account; in my case, it is “bettini”.
useradd -m bettini
usermod -aG wheel,sys,rfkill bettini
I add the user to a few essential groups, in particular, “wheel” which makes my user a superuser account. Just relying on the group “wheel” is not enough: we must allow members of the group wheel to execute any command. This is done by uncommenting this line in the /etc/sudoers: “%wheel ALL=(ALL:ALL) ALL”. A sed command will accomplish that:
sed -i 's/# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' /etc/sudoers
That would be enough to reboot and try our installation. However, we would have no networking (actually, since this is a virtual machine, networking should work out of the box since you don’t need to configure any WiFi network, for example) and no desktop environment. So let’s go on with some further installations and configurations:
In this example, I’m going to install the GNOME desktop environment. Besides GNOME, I’m installing other necessary packages like the “NetworkManager” (for easily configuring networking in GNOME), “firewall”, “firefox”, the package for choosing a power profile (useful for laptops), and other base packages, including the kernel headers (here I’m using “linux-lts-headers” because I installed the LTS kernel; otherwise, use “linux-headers”):
pacman -S --noconfirm --needed \
pipewire-jack pipewire-media-session xdg-desktop-portal-gnome gnome noto-fonts \
firefox power-profiles-daemon networkmanager firewalld \
About 700Mb will be downloaded.
Once done, we have to enable the services at boot (in particular, GDM, the login manager, and the NetworkManager):
systemctl enable gdm.service
systemctl enable NetworkManager.service
systemctl enable power-profiles-daemon.service
systemctl enable firewalld.service
Time to reboot!
Now, it’s time to leave the environment and unmount all the partitions:
And reboot into our new Arch Linux installation.
If everything goes fine, we should see the login manager, and we can enter Gnome:
So, in the end, it’s not so hard to install Arch 😉
Maybe it’s a long procedure, but… most of that can be scripted! That’s will be the subject of another blog post, so stay tuned! 🙂