Hardening Arch, beginner-friendly

arch / published / guide #47 / ≈ 5 min read

Arch installs with very little extra security on top — that's the philosophy. The flip side is that any hardening is something you set up. This guide is the gentle walkthrough: what to do, in what order, on a fresh Arch install you're putting on the internet (or just want to be sane about).

This pairs with the post-install: the real checklist guide — that one covers the basics (sudo user, time, locale, network). This one is the next layer: SSH, firewall, intrusion protection, kernel knobs, AppArmor.

The big picture

  1. SSH: keys only, no root login, restricted users.
  2. A firewall (nftables) that blocks by default.
  3. fail2ban (or sshguard) to discourage brute force.
  4. A handful of kernel sysctls flipped.
  5. AppArmor or SELinux turned on.
  6. Automatic updates — or at least a habit of running them often.

Step 1: SSH the same as everywhere

If you've done this on Debian or Mint, the process is identical. From your laptop:

ssh-keygen -t ed25519          # if you don't have one
ssh-copy-id alice@server

Test logging in with the key (no password). Then on the server:

sudo nano /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
AllowUsers alice
sudo systemctl reload sshd

Keep your existing SSH session open until you've confirmed a fresh one still works.

Step 2: a firewall with nftables

nftables is part of the kernel itself — no separate install needed beyond the userland tool:

sudo pacman -S nftables

A minimal, sane ruleset:

sudo nano /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        iif lo accept
        ct state established,related accept
        ct state invalid drop

        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        tcp dport 22 accept
        # uncomment for a web server:
        # tcp dport { 80, 443 } accept
    }
    chain forward {
        type filter hook forward priority 0; policy drop;
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}

Check the syntax before activating (this never lets a broken file lock you out):

sudo nft -c -f /etc/nftables.conf

Enable for boot and start now:

sudo systemctl enable --now nftables

Verify with sudo nft list ruleset — should echo your rules. (See nftables on Trixie for the line-by-line walkthrough; same syntax on Arch.)

Step 3: brute-force protection

The Arch-friendly pick is sshguard — small, no extra Python, works with nftables out of the box:

sudo pacman -S sshguard
sudo systemctl enable --now sshguard

That's it. sshguard parses the journal, and after a few failed SSH attempts from one IP it adds a temporary nftables block. Defaults are reasonable.

To verify it's wired up:

sudo nft list set inet sshguard attackers     # the set sshguard fills with banished IPs
journalctl -u sshguard -n 20

Prefer fail2ban? Same idea, more Python and more knobs: sudo pacman -S fail2ban, enable the service.

Step 4: kernel hardening sysctls

Drop this file:

sudo nano /etc/sysctl.d/99-hardening.conf
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv6.conf.all.accept_ra = 0           # only set on servers with static routes
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.yama.ptrace_scope = 1
kernel.unprivileged_bpf_disabled = 1

Apply:

sudo sysctl --system

What each is doing (very briefly): no smurf amplification, no source routing, log spoofed source addresses, reject impossible source routes, no IPv6 router advertisements (server use), hide kernel pointers and dmesg from normal users, restrict process tracing, and disable unprivileged BPF.

Step 5: AppArmor

Arch doesn't enable a mandatory access control system by default. AppArmor is the simpler of the two (the other is SELinux). To turn it on:

sudo pacman -S apparmor

Edit your bootloader to add a kernel parameter:

If you use systemd-boot: edit /boot/loader/entries/arch.conf, add to the options line:

options ... lsm=landlock,lockdown,yama,integrity,apparmor,bpf

If you use GRUB: edit /etc/default/grub, append to GRUB_CMDLINE_LINUX_DEFAULT:

GRUB_CMDLINE_LINUX_DEFAULT="... lsm=landlock,lockdown,yama,integrity,apparmor,bpf"

Then sudo grub-mkconfig -o /boot/grub/grub.cfg.

Enable the service and reboot:

sudo systemctl enable apparmor
sudo reboot

After reboot:

sudo aa-status

Should report apparmor loaded and list any active profiles. Profiles for common daemons (sshd, nginx, dovecot) live under /etc/apparmor.d/. Many are in "complain" mode by default — they log violations without enforcing. Switch one to enforce:

sudo aa-enforce /etc/apparmor.d/usr.sbin.sshd

Step 6: automatic-ish updates

Arch doesn't encourage hands-off updates the way Debian does — partial upgrades break Arch in interesting ways. But you can automate the safer half: keep an eye on news and pending updates.

From the AUR: informant is a wrapper that refuses to upgrade until you've acknowledged any new Arch news:

yay -S informant
informant check && sudo pacman -Syu

Schedule a daily check that emails you if updates are pending — not auto-applied, just a nudge. (See cron and systemd timers for the timer pattern.)

Step 7: a few small extras

  • Limit who can use sudo. In /etc/sudoers, the %wheel group is your sudoers list. Don't add other groups; don't loosen requirements.
  • Set a password timeout for sudo. Default is 5 minutes (the duration sudo "remembers" your password). Lower it with Defaults timestamp_timeout=1.
  • Disable unused services. systemctl list-units --type=service --state=running — anything you don't recognise, look up, then disable if unneeded.
  • If on a laptop: enable full-disk encryption at install time. After install, see changing the LUKS passphrase for ongoing management.

The done check

sudo nft list ruleset                              # rules look right?
systemctl is-active sshguard nftables apparmor     # all "active"?
sudo aa-status | head                              # apparmor loaded?
sudo sysctl -a 2>/dev/null | grep dmesg_restrict   # = 1?
sudo ss -tulpn                                     # nothing surprising listening?

Five commands. If they all look reasonable, your Arch install is in a quietly defensible state.

← back to index