Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

How to install Arch Linux on a Hetzner Cloud server

profile picture
Author
Patrick Michl
Published
2024-09-20
Time to read
10 minutes reading time

Introduction

In this article we will install a barebones Arch Linux system on a server that is currently running an OS from RAM or a live ISO. As long as the target disks are not in use, any linux distribution will work. For this tutorial I will be using a Hetzner Cloud server booted into the Rescue System.

Important information

This tutorial will need you to execute commands in different chroots. This allows us to pivot our apparent root directory to a new location and execute commands as if we have booted the system.

I will inform you whenever we switch into a new root. I will also use the prompt text to indicate which chroot we are currently in.

  • root@rescue ~ # <cmd> will indicate a command running without chroot
  • [root@bootstrap /]# <cmd> will be inside the bootstrap environment
  • [root@chroot /]# <cmd> is inside the Arch Linux installation on the disk

Step 1 - Setting up the bootstrap environment

We will start by getting the current bootstrap image from a trusted Arch Linux mirror. This will provide us with the necessary tools for an arch installation. While we are at it, we will also get the corresponding signature and verify the image before continuing our installation.

root@rescue ~ # curl -LO 'https://geo.mirror.pkgbuild.com/iso/latest/archlinux-bootstrap-x86_64.tar.zst'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  164M  100  164M    0     0   115M      0  0:00:01  0:00:01 --:--:--  115M
root@rescue ~ # curl -LO 'https://archlinux.org/iso/latest/archlinux-bootstrap-x86_64.tar.zst.sig'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   331  100   331    0     0   4728      0 --:--:-- --:--:-- --:--:--  4728
root@rescue ~ # gpg --keyserver keyserver.ubuntu.com --keyserver-options auto-key-retrieve --verify archlinux-bootstrap-x86_64.tar.zst.sig
gpg: Signature made Sun 01 Sep 2024 02:43:27 PM CEST
gpg:                using EDDSA key 3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C
gpg:                issuer "pierre@archlinux.org"
gpg: key 7F2D434B9741E8AC: public key "Pierre Schmitz <pierre@archlinux.org>" imported
gpg: key 76A5EF9054449A5C: public key "Pierre Schmitz <pierre@archlinux.org>" imported
gpg: Total number processed: 2
gpg:               imported: 2
gpg: no ultimately trusted keys found
gpg: Good signature from "Pierre Schmitz <pierre@archlinux.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 3E80 CA1A 8B89 F69C BA57  D98A 76A5 EF90 5444 9A5C

When no error occurs, we can move on to unpacking the image.

root@rescue ~ # tar xf archlinux-bootstrap-x86_64.tar.zst
tar: Ignoring unknown extended header keyword 'LIBARCHIVE.xattr.security.capability'
tar: Ignoring unknown extended header keyword 'LIBARCHIVE.xattr.security.capability'

After extraction we need to bind mount the directory over itself. We do this to prevent pacstrap from thinking that there is no space left on this device.

root@rescue ~ # mount --bind root.x86_64 root.x86_64

Step 2 - Setting up boot disk and bootstrapping Arch Linux

From here on we need to work from within the bootstrap environment we just set up. We do this by using arch-chroot

root@rescue ~ # ./root.x86_64/usr/bin/arch-chroot root.x86_64

Firstly we need to partition our disk. I am going to use gdisk here, but you can also use any other GPT capable partitioning tool. However gdisk is not installed in the boostrap image. To do this we need to first configure a mirror and setup the pacman keyring and then install gdisk:

[root@bootstrap /]# echo 'Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch' > /etc/pacman.d/mirrorlist
[root@bootstrap /]# pacman-key --init
gpg: /etc/pacman.d/gnupg/trustdb.gpg: trustdb created
gpg: no ultimately trusted keys found
gpg: starting migration from earlier GnuPG versions
gpg: porting secret keys from '/etc/pacman.d/gnupg/secring.gpg' to gpg-agent
gpg: migration succeeded
==> Generating pacman master key. This may take some time.
gpg: Generating pacman keyring master key...
gpg: directory '/etc/pacman.d/gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/etc/pacman.d/gnupg/openpgp-revocs.d/AFB2F3A83C28CC51C20E0752282AC1A0C5C2A266.rev'
gpg: Done
==> Updating trust database...
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
[root@bootstrap /]# pacman-key --populate archlinux
==> Appending keys from archlinux.gpg...
==> Locally signing trusted keys in keyring...
  -> Locally signed 5 keys.
==> Importing owner trust values...
gpg: setting ownertrust to 4
gpg: setting ownertrust to 4
gpg: setting ownertrust to 4
gpg: inserting ownertrust of 4
gpg: setting ownertrust to 4
==> Disabling revoked keys in keyring...
  -> Disabled 45 keys.
==> Updating trust database...
gpg: Note: third-party key signatures using the SHA1 algorithm are rejected
gpg: (use option "--allow-weak-key-signatures" to override)
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   5  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: depth: 1  valid:   5  signed: 101  trust: 0-, 0q, 0n, 5m, 0f, 0u
gpg: depth: 2  valid:  77  signed:  22  trust: 77-, 0q, 0n, 0m, 0f, 0u
gpg: next trustdb check due at 2024-11-09
[root@bootstrap /]# pacman --noconfirm -Sy gdisk

Now we can continue partitioning our disk. Hetzner Cloud servers only ship with one disk attached and this disk is called /dev/sda. Your disk naming might differ, so make sure to adjust the command accordingly. Please note that this will delete all partitions and therefore render the data on there useless.

Our partitioning here is very simple. At minimum we need a boot partition (with 1MB size) and a root partition (the rest of the drive in our case).

[root@bootstrap /]# gdisk /dev/sda
GPT fdisk (gdisk) version 1.0.10

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.

Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): y

Command (? for help): n
Partition number (1-128, default 1):
First sector (34-40001502, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-40001502, default = 39999487) or {+-}size{KMGTP}: +1M
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): ef02
Changed type of partition to 'BIOS boot partition'

Command (? for help): n
Partition number (2-128, default 2):
First sector (34-40001502, default = 4096) or {+-}size{KMGTP}:
Last sector (4096-40001502, default = 39999487) or {+-}size{KMGTP}:
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to 'Linux filesystem'

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sda.
The operation has completed successfully.

We then format our root partition with the ext4 filesystem:

[root@bootstrap /]# mkfs.ext4 /dev/sda2
mke2fs 1.47.1 (20-May-2024)
/dev/sda2 contains a vfat file system
Proceed anyway? (y,N) y
Discarding device blocks: done
Creating filesystem with 4999424 4k blocks and 1250928 inodes
Filesystem UUID: c14d23b8-5754-49bc-bc27-d1cb48bd76e3
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
        4096000

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

We then mount it to /mnt and generate our fstab from that:

[root@bootstrap /]# mount /dev/sda2 /mnt
[root@bootstrap /]# genfstab -U /mnt >> /mnt/etc/fstab

Now we can install Arch Linux via pacstrap. We will use this opportunity to install OpenSSH, as we will need it to connect to our server later. This may take some time depending on the speed of your internet connection and disks.

[root@bootstrap /]# pacstrap -G -M /mnt base grub linux linux-firmware openssh
==> Creating install root at /mnt
==> Installing packages to /mnt
:: Synchronizing package databases...
 core                                        117.2 KiB   514 KiB/s 00:00 [########################################] 100% extra                                         7.4 MiB  37.0 MiB/s 00:00 [########################################] 100%resolving dependencies...
:: There are 2 providers available for libxtables.so=12-64:
:: Repository core
   1) iptables  2) iptables-nft

Enter a number (default=1):
:: There are 3 providers available for initramfs:
:: Repository core
   1) mkinitcpio
:: Repository extra
   2) booster  3) dracut

Enter a number (default=1):
looking for conflicting packages...

[...]

Total Download Size:   520.45 MiB
Total Installed Size:  992.49 MiB

[...]

(13/13) Reloading system bus configuration...
  Skipped: Running in chroot.

Step 3 - Finalizing the Arch Linux installation

We can now exit out of the bootstrap environment and change root into the installed system to finish setting up:

[root@bootstrap /]# exit
root@rescue ~ # ./root.x86_64/usr/bin/arch-chroot root.x86_64/mnt

We will again start by configuring a mirror and initializing the pacman keyring:

[root@chroot /]# echo 'Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch' > /etc/pacman.d/mirrorlist
[root@chroot /]# pacman-key --init
[root@chroot /]# pacman-key --populate archlinux

Now it's time to install the bootloaded to our drive

[root@chroot /]# grub-install /dev/sda
Installing for i386-pc platform.
Installation finished. No error reported.
[root@chroot /]# grub-mkconfig -o /boot/grub/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-linux
Found initrd image: /boot/initramfs-linux.img
Found fallback initrd image(s) in /boot:  initramfs-linux-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 ...
done

Networking will be managed by systemd-networkd. To enable we need to create the file /etc/systemd/network/ether.network with the following content:

cat << EOF > /etc/systemd/network/ether.network
[Match]
Type=ether

[Network]
DHCP=yes
EOF

This will configure all ethernet network interfaces for DHCP.

Now we activate the required services:

[root@chroot /]# systemctl enable systemd-networkd
Created symlink '/etc/systemd/system/dbus-org.freedesktop.network1.service''/usr/lib/systemd/system/systemd-networkd.service'.
Created symlink '/etc/systemd/system/multi-user.target.wants/systemd-networkd.service''/usr/lib/systemd/system/systemd-networkd.service'.
Created symlink '/etc/systemd/system/sockets.target.wants/systemd-networkd.socket''/usr/lib/systemd/system/systemd-networkd.socket'.
Created symlink '/etc/systemd/system/sysinit.target.wants/systemd-network-generator.service''/usr/lib/systemd/system/systemd-network-generator.service'.
Created symlink '/etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service''/usr/lib/systemd/system/systemd-networkd-wait-online.service'.

[root@chroot /]# systemctl enable systemd-resolved
Created symlink '/etc/systemd/system/dbus-org.freedesktop.resolve1.service''/usr/lib/systemd/system/systemd-resolved.service'.
Created symlink '/etc/systemd/system/sysinit.target.wants/systemd-resolved.service''/usr/lib/systemd/system/systemd-resolved.service'.

Lastly we would like to have access via SSH to our system. For this simply create an authorized keys file at /root/.ssh/authorized_keys and write your public SSH key to it. Don't forget to enable the sshd otherwise the SSH server will not start after rebooting.

[root@chroot /]# mkdir /root/.ssh
[root@chroot /]# echo "<your-ssh-pub-key>" >> /root/.ssh/authorized_keys
[root@chroot /]# systemctl enable sshd
Created symlink '/etc/systemd/system/multi-user.target.wants/sshd.service''/usr/lib/systemd/system/sshd.service'.

We can now exit out from the chroot and reboot our system. After a few seconds you server should boot into your freshly installed Arch Linux and be reachable via SSH on port 22 using your public key.

Conclusion

Congratulations! You now have a minimal Arch Linux installation on your server. From here on you can start installing additional services like Docker, nginx, K8S, etc.

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get €20/$20 free credit!

Valid until: 31 December 2025 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more