Who should run it
AlmaLinux 9 tracks Red Hat Enterprise Linux 9. Pick it if your configuration management targets RHEL, if a vendor supports only RHEL-family systems, or if your compliance team has a CIS or DISA STIG baseline for RHEL 9 they'd rather not rewrite. The release landed in May 2022. Active support runs to 2027 and security fixes to 2032. Ten years is the longest Linux runway in our catalogue.
If none of that applies, an apt-based image will probably feel lighter. AlmaLinux pays off when the RHEL ecosystem is already part of how you work.
First boot
dnf -y upgrade --refresh
dnf -y install epel-release vim-enhanced tmux policycoreutils-python-utils
hostnamectl set-hostname db-01.lon
timedatectl set-timezone Europe/London
useradd -m -G wheel ops && passwd -l ops
mkdir -p /home/ops/.ssh && cp ~/.ssh/authorized_keys /home/ops/.ssh/
chown -R ops:ops /home/ops/.ssh && chmod 700 /home/ops/.ssh
restorecon -Rv /home/ops/.ssh
That last line matters. Files copied into .ssh can carry the wrong SELinux label, and sshd will then silently refuse the key. restorecon resets the context. When key logins fail on a RHEL-family box, check labels before anything else.
cat >/etc/ssh/sshd_config.d/50-local.conf <<'EOF'
PasswordAuthentication no
PermitRootLogin no
EOF
sshd -t && systemctl reload sshd
firewalld
firewalld is running from first boot with the public zone allowing SSH and little else. Add services by name and make the changes permanent:
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-port=8443/tcp
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=203.0.113.10/32 service name=postgresql accept'
firewall-cmd --reload
firewall-cmd --list-all
Rich rules handle "allow PostgreSQL only from my app server" cleanly. Mirror the coarse rules in the portal firewall as an outer layer.
SELinux in practice
SELinux ships enforcing. Most stock packages from the base repos come with correct policy. Trouble starts when you move data to non-default paths or bind services to non-standard ports:
# nginx proxying to a local app
setsebool -P httpd_can_network_connect on
# PostgreSQL data on a custom path
semanage fcontext -a -t postgresql_db_t '/srv/pgdata(/.*)?'
restorecon -Rv /srv/pgdata
# sshd on port 2222
semanage port -a -t ssh_port_t -p tcp 2222
# what was denied, recently
ausearch -m AVC -ts recent
Five commands cover ninety per cent of SELinux friction. Learn them once and stop reaching for setenforce 0.
Automatic updates
dnf -y install dnf-automatic
sed -i 's/^upgrade_type.*/upgrade_type = security/; s/^apply_updates.*/apply_updates = yes/' \
/etc/dnf/automatic.conf
systemctl enable --now dnf-automatic.timer
dnf needs-restarting -r
dnf needs-restarting -r exits non-zero when a reboot is due. Wire that into your monitoring.
Gotchas
- AppStream module streams. Newer Node, PHP or PostgreSQL versions come as module streams or versioned packages. Check
dnf module listbefore you add a third-party repo. - EPEL is separate. Many tools you take for granted on Debian, such as htop or certbot, live in EPEL.
- Minor releases move the base. 9.4, 9.5 and later arrive as normal updates. You don't reinstall for a point release, but read the release notes for deprecations.
- Port 25. Postfix is often installed, but outbound mail is blocked by default on our network. Request an unblock in the portal if you need it.
Hardening baselines
If you work to a formal baseline, the scap-security-guide package ships OpenSCAP profiles for the RHEL 9 family, including CIS levels. Scan first, then remediate selectively:
dnf -y install openscap-scanner scap-security-guide
oscap xccdf eval --profile cis_server_l1 \
--report /root/cis-report.html \
/usr/share/xml/scap/ssg/content/ssg-almalinux9-ds.xml
Read the HTML report before applying automated fixes. Some CIS rules, such as separate partitions for /var/log, assume a layout that a single-disk VPS doesn't have. Document those as accepted exceptions rather than fighting them.
Sizing
A minimal AlmaLinux 9 install wants a little more memory than Debian. Boost is a comfortable floor for anything beyond a single tiny service. PostgreSQL or MariaDB with real data belongs on Turbo or Nitro. Background reading is in databases.