CachyOS Server Setup Guide
This guide walks through installing and configuring a secure CachyOS server, including btrfs subvolume setup, optimized kernel installation, and SSH key-based authentication.
Contents
- Introduction
- Assumptions and scope
- Boot into Arch ISO
- Enter chroot before first boot
- Finish configuration after first boot
- Set up UFW firewall
- Finally
Introduction
CachyOS is a performance-focused Arch Linux distribution that already provides excellent support for server workloads. The project offers a dedicated linux-cachyos-server↗ kernel variant tuned specifically for servers, featuring a 300Hz tickrate, no preemption, and the stock EEVDF scheduler. The 300Hz tickrate reduces CPU overhead from timer interrupts compared to the desktop kernel’s 1000Hz, while disabling preemption improves throughput for server workloads that don’t need the low-latency responsiveness required for desktop interactivity. Combined with CachyOS’s optimized package repositories compiled for modern CPU architectures (x86-64-v3, x86-64-v4, and Zen4), you can expect noticeable performance improvements over standard Arch Linux.
While CachyOS does not yet have a dedicated server edition ISO that would streamline the installation process, this guide walks through setting up CachyOS on a server from an Arch ISO.
Assumptions and scope
This guide assumes:
- A Hetzner dedicated server (AX-Line with AMD CPU) using BIOS boot (the
default for Hetzner). EFI instructions are provided as comments where
applicable, but the primary flow targets BIOS systems. Intel-based EX-Line
servers work the same way but need
intel-ucodeinstead ofamd-ucode. Other VPS providers should work with minimal changes, called out in the guide. - SSH key-based authentication only with passwords disabled. This is a deliberate security choice — if you prefer password authentication, you’ll need to modify the SSH configuration accordingly.
- btrfs with subvolumes matching the default CachyOS desktop installer layout, providing snapshot compatibility with tools like Timeshift.
- Mount options that balance performance and data safety. This guide uses
commit=30(the btrfs default) for servers. While CachyOS desktop usescommit=120to prioritize desktop responsiveness, servers benefit from the default’s shorter data loss window: 30 seconds vs 2 minutes on unexpected power loss. The btrfs default has been 30 seconds since the option was introduced in kernel 3.12, and I haven’t been able to find benchmarks that demonstrate meaningful performance differences at values above or below 30, aside from apocryphal claims↗, so it seems best to stick with the defaults for now.
Boot into Arch ISO
Boot into an Arch ISO image.
Create filesystem mounts:
pacman -S gdisk
lsblk
# confirm that the right disk is chosen based on lsblk output,
# /dev/sda is the example chosen here:
gdisk /dev/sda
Run these gdisk commands:
o # delete partitions
n # new partition
# hit enter on first sector
last sector: +100M # BIOS boot partition (or EFI System Partition if using EFI)
code: ef02 # BIOS boot partition; use ef00 instead if using EFI
n # new partition
# hit enter on first sector
last sector: +600M # /boot partition
# code: 8300 (the default)
n # new partition
# hit enter on all options to max out remaining partition
# code: 8300 (the default)
w # save changes and quit
Create filesystems (adjust device names based on your lsblk output):
# Adjust these according to the earlier lsblk output
export EFI_PARTITION=/dev/sda1
export BOOT_PARTITION=/dev/sda2
export DATA_PARTITION=/dev/sda3
# For BIOS boot, this FAT32 partition is optional but harmless to create.
# For EFI boot, this is required for the EFI System Partition.
mkfs.fat -F 32 ${EFI_PARTITION}
mkfs.ext4 ${BOOT_PARTITION}
mkfs.btrfs ${DATA_PARTITION}
Set up and mount the btrfs volumes and the EFI partition:
mount ${DATA_PARTITION} /mnt
btrfs subvolume create /mnt/@ # /
btrfs subvolume create /mnt/@home # /home
btrfs subvolume create /mnt/@root # /root
btrfs subvolume create /mnt/@srv # /srv
btrfs subvolume create /mnt/@swap # /swap
btrfs subvolume create /mnt/@cache # /var/cache
btrfs subvolume create /mnt/@log # /var/log
btrfs subvolume create /mnt/@tmp # /var/tmp
umount /mnt
# Use commit=30 (btrfs default); adjust if needed
export COMMIT_INTERVAL=30
mount -o compress=zstd,subvol=@,noatime,commit=${COMMIT_INTERVAL} ${DATA_PARTITION} /mnt
mkdir -p /mnt/home
mount -o compress=zstd,subvol=@home,noatime,commit=${COMMIT_INTERVAL} ${DATA_PARTITION} /mnt/home
mkdir -p /mnt/root
mount -o compress=zstd,subvol=@root,noatime,commit=${COMMIT_INTERVAL} ${DATA_PARTITION} /mnt/root
mkdir -p /mnt/srv
mount -o compress=zstd,subvol=@srv,noatime,commit=${COMMIT_INTERVAL} ${DATA_PARTITION} /mnt/srv
mkdir -p /mnt/swap
mount -o subvol=@swap,noatime ${DATA_PARTITION} /mnt/swap
# Adjust swap size based on your workload; 1GB is minimal
btrfs filesystem mkswapfile --size 1g --uuid clear /mnt/swap/swapfile
mkdir -p /mnt/var/cache
mount -o compress=zstd,subvol=@cache,noatime,commit=${COMMIT_INTERVAL} ${DATA_PARTITION} /mnt/var/cache
mkdir -p /mnt/var/log
mount -o compress=zstd,subvol=@log,noatime,commit=${COMMIT_INTERVAL} ${DATA_PARTITION} /mnt/var/log
mkdir -p /mnt/var/tmp
mount -o compress=zstd,subvol=@tmp,noatime,commit=${COMMIT_INTERVAL} ${DATA_PARTITION} /mnt/var/tmp
mkdir -p /mnt/boot
mount -o noatime,commit=${COMMIT_INTERVAL} ${BOOT_PARTITION} /mnt/boot
# Only do these if your boot type is EFI; should not be done with BIOS boot type
# mkdir -p /mnt/boot/efi
# mount ${EFI_PARTITION} /mnt/boot/efi
Run pacstrap:
# pacstrap installs the base packages into the new root filesystem. The -K flag
# adds the signing keys, -M skips the mount check. After this, genfstab
# generates an fstab file based on the current mount points.
#
# Starting with the 3rd line of dependencies, feel free to pick any other tools
# you need for the server.
pacstrap -K -M /mnt base base-devel btrfs-progs grub grub-btrfs linux \
linux-firmware git inotify-tools man openssh sudo systemd vim zsh \
bind btop clang cmake diffutils eza fd fzf ghostty-terminfo gnutls \
htop man-db man-pages mise net-tools nmap openbsd-netcat ripgrep \
texinfo usage
genfstab -U /mnt >> /mnt/etc/fstab
echo 'tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0' >> /mnt/etc/fstab
echo '/swap/swapfile none swap defaults 0 0' >> /mnt/etc/fstab
vim /mnt/etc/fstab
# delete empty lines and ensure it looks as expected
Enter chroot before first boot
Start a chroot and configure a few more things:
arch-chroot /mnt
# arch-chroot switches the root directory to /mnt, allowing you to run commands
# as if the new system was your current running system. This is where you
# configure the installed system before first boot.
# Set these to your desired values:
# - MYUSER: the username for your non-root account
# - MYHOSTNAME: the hostname for this server
# - MYDOMAIN: your domain name (used in /etc/hosts)
export MYUSER=myuser
export MYHOSTNAME=myserver
export MYDOMAIN=example.com
# Initialize the swapfile by activating and deactivating it
swapon /swap/swapfile
swapoff /swap/swapfile
ln -sf /usr/bin/vim /usr/bin/vi
systemd-machine-id-setup
rm -f /etc/localtime
systemd-firstboot --force --prompt-timezone
# Choose America/Detroit or whichever closest city is in your timezone
vi /etc/inputrc # uncomment "set bell-style none"
vi /etc/locale.gen
# Add this to top of file:
# en_US.UTF-8 UTF-8
locale-gen
echo 'LANG=en_US.UTF-8' > /etc/locale.conf
echo "$MYHOSTNAME" > /etc/hostname
vi /etc/hosts
# Add this line (replacing the variables with your values):
# 127.0.0.1 $MYHOSTNAME.$MYDOMAIN $MYHOSTNAME
hostnamectl set-hostname "$MYHOSTNAME"
useradd -m -s /usr/bin/zsh "$MYUSER"
# The following lets your user call `sudo` whenever they want without
# typing in a password; skip if you'd rather not do that.
# If skipping, make sure to set a password with `passwd $MYUSER`
mkdir -p /etc/sudoers.d/
echo "$MYUSER ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/local
chmod u=rw,g=r,o= /etc/sudoers.d/local
chmod u=rw,g=r,o= /etc/sudoers
sudo echo pass
# confirm that it worked
pacman-key --init
pacman-key --populate archlinux
# Hetzner-specific network configuration:
# See https://docs.hetzner.com/robot/dedicated-server/network/network-configuration-using-systemd-networkd/#ipv4-and-ipv6-1
# Other VPS providers may have different requirements.
ip address
# for Match.MACAddress, use the first `if` link/ether arg on correct interface
# for Network.Address, use the IPv6 address from your Hetzner admin console,
# but make sure that it ends with ::2 for the first address, not ::1
# for Gateway, use fe80::1 (Hetzner's standard IPv6 gateway)
cat << EOF > /etc/systemd/network/ether.network
[Match]
MACAddress=12:34:56:78:9a:bc
[Network]
DHCP=ipv4
Address=1:2:3:4::5/64
Gateway=fe80::1
EOF
# Alternative: For a simple IPv4-only setup over DHCP (non-Hetzner),
# you can use:
cat << EOF > /etc/systemd/network/ether.network
[Match]
Type=ether
[Network]
DHCP=yes
EOF
systemctl enable systemd-networkd
systemctl enable systemd-resolved
# `systemctl enable` configures services to start automatically at boot.
# `systemd-networkd` handles network configuration, and systemd-resolved
# provides DNS.
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/99-local.conf
vi /etc/ssh/sshd_config.d/99-archlinux.conf
# Ensure the following lines are present:
# KbdInteractiveAuthentication no
# UsePAM yes
# PrintMotd no
systemctl enable sshd
Set up basic SSH key access (replace with your own public key):
mkdir -p "/home/${MYUSER}/.ssh"
# Replace `$MYKEY` with your own public key file content,
# which should end with an email address:
cat <<EOF | tee "/home/${MYUSER}/.ssh/authorized_keys" > /dev/null
ssh-ed25519 $MYKEY
EOF
cat <<EOF | tee "/home/${MYUSER}/.ssh/config" > /dev/null
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null
EOF
chmod -R og= "/home/${MYUSER}/.ssh"
chown -R "${MYUSER}:${MYUSER}" "/home/${MYUSER}/.ssh"
# If you want to be able to log in to `root` with your ssh key, do
# the following. Consider adding `PermitRootLogin no` to
# /etc/ssh/sshd_config.d/99-local.conf instead if you prefer
# disabling root login entirely, or if you have set a root password
# which isn't advised.
mkdir -p /root/.ssh
# Replace `$MYKEY` with your own public key file content:
cat <<EOF | tee /root/.ssh/authorized_keys > /dev/null
ssh-ed25519 $MYKEY
EOF
cat <<EOF | tee /root/.ssh/config > /dev/null
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null
EOF
chmod -R og= /root/.ssh
Switch over to optimized CachyOS repositories:
cd
curl https://mirror.cachyos.org/cachyos-repo.tar.xz -o cachyos-repo.tar.xz
tar -xvf cachyos-repo.tar.xz && cd cachyos-repo
./cachyos-repo.sh
# CachyOS repos now provide paru (AUR helper) as a binary package
pacman -Sy paru
# Rank mirrors for both Arch and CachyOS repos based on your location.
# This uses rate-mirrors (Rust-based) to test mirror speeds and updates:
# /etc/pacman.d/mirrorlist (Arch)
# /etc/pacman.d/cachyos-mirrorlist (CachyOS)
# /etc/pacman.d/cachyos-v3-mirrorlist (CachyOS x86-64-v3)
# /etc/pacman.d/cachyos-v4-mirrorlist (CachyOS x86-64-v4)
# On desktop, CachyOS Hello calls this; for servers we run it manually.
paru -Sy cachyos-rate-mirrors
cachyos-rate-mirrors
paru -Sy cachyos-zsh-config grub-btrfs-support grub-hook linux-cachyos-server
paru -R linux
# Install CPU microcode for security patches.
# Use intel-ucode for Intel CPUs, amd-ucode for AMD CPUs.
# Check your CPU with: grep -m1 'vendor_id' /proc/cpuinfo
# Hetzner EX-Line servers use Intel, AX-Line servers use AMD.
# paru -Sy intel-ucode # for Intel CPUs (EX-Line)
paru -Sy amd-ucode # for AMD CPUs (AX-Line)
# BIOS boot (Hetzner default):
grub-install /dev/sda
# EFI boot (uncomment these two lines instead if using EFI):
#
# paru -Sy efibootmgr
# grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=grub
grub-mkconfig -o /boot/grub/grub.cfg
Finish configuration after first boot
Reboot and install dotfiles as the user:
# hit ctrl+d to exit chroot
for i in /mnt/var/* /mnt/* /mnt; do umount $i; done
shutdown -h now
# unmount the Arch ISO from the provider's dashboard
# reboot the computer from the provider's dashboard
# log in from other computer after rebooting
# confirm that ssh access works
ssh "${MYUSER}@${MYHOSTNAME}"
# Set these to your desired values (same as in chroot section)
export MYUSER=myuser
export MYHOSTNAME=myserver
export MYDOMAIN=example.com
# Set up any dotfiles that typically need:
#
# git clone https://github.com/${MYUSER}/dotfiles
#
# or
#
# scp $OTHERSERVER:.vimrc ~/.vimrc
ssh "${MYUSER}@localhost" echo pass
# confirm that it worked
sudo timedatectl set-ntp true
ip address
# confirm that you see the public interface with scope=global for both
# IPv4 and IPv6
resolvectl status
# confirm that the correct dns servers are used for your provider; if they
# aren't, edit /etc/systemd/resolved.conf
sudo vi /etc/systemd/resolved.conf
# Add the following lines after [Resolve]:
# DNSSEC=yes
# MulticastDNS=no
# LLMNR=no
sudo systemctl restart systemd-resolved
# Examine which ports are exposed
sudo nmap -sU 127.0.0.1
sudo ss -atpuwS # everything
sudo ss -lntup # just tcp/udp listeners
# confirm that you're satisfied with the ports being exposed to the outside;
# consider setting up `ufw` in the next section for more control
Set up reverse DNS for both the IPv6 and IPv4 addresses in the admin console, so that services like email (if you provide them) are less likely to put the server on a blocklist.
Set up UFW firewall
You can choose to install and configure UFW (Uncomplicated Firewall) to control incoming connections if you’d like to have more explicit control over incoming connections, and protection from unexpected port exposures from system changes that may be more tailored for desktop users.
For SSH brute-force protection, consider installing fail2ban. However, with
key-only authentication (passwords disabled), brute-force attacks cannot succeed
anyway, so fail2ban is only necessary if you’ve enabled password authentication
with passwd $MYUSER.
paru -Sy ufw
# Set default policies: deny all incoming, allow all outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (critical: do this before enabling UFW or you'll lock yourself out)
sudo ufw allow ssh
# Allow other services as needed, for example:
# sudo ufw allow http
# sudo ufw allow https
# Enable UFW and ensure it starts on boot
sudo ufw enable
sudo systemctl enable ufw
# Verify the rules are active
sudo ufw status verbose
From a different computer with working IPv6 access:
ssh "${MYUSER}@<your-ipv6-address>" echo pass
# confirm that it still works
Finally
Now you can reboot into a working CachyOS server.