AlmaLinux 9 Arm64 on Azure User Guide
Overview
This guide covers the deployment and configuration of AlmaLinux 9 on 64-bit Arm (AArch64/arm64) on Microsoft Azure using cloudimg's pre configured virtual machine image from the Azure Marketplace.
AlmaLinux is a community owned enterprise Linux distribution, produced by the AlmaLinux OS Foundation to be binary compatible with Red Hat Enterprise Linux. This is the native Arm build of AlmaLinux 9, running the 5.14 kernel and following the RHEL 9 lifecycle. It runs on Azure's Arm virtual machine sizes (the Ampere Altra based Dpsv6 and Dplsv6 families), where many workloads deliver the same performance at a lower cost than on x86.
What's included:
- AlmaLinux 9, native 64-bit Arm (arm64 / AArch64), binary compatible with RHEL 9
- Every available update applied at build time
- Automatic updates armed and enabled, so the machine keeps patching itself
- Azure Linux Agent (waagent) and cloud-init for Azure integration
- Chronyd for NTP time synchronisation
- SELinux in enforcing mode
- BaseOS and AppStream repositories enabled, so your update path is intact
- No swap on the operating system disk
- Gen2 Hyper V virtual machine support
- 24/7 cloudimg support
Platform: Microsoft Azure (Gen2 Hyper V, Arm64)
Default user: azureuser
This is the AlmaLinux 9 major release, on Arm
This listing tracks the AlmaLinux 9 major release, not a frozen minor. RHEL family security backports roll into the latest minor stream, so the image is built from whichever AlmaLinux 9 point release is current at build time and brought fully up to date. Your own dnf update then rolls the machine forward within AlmaLinux 9 in the normal way, so you keep receiving security fixes. If your change control names an exact minor version instead, cloudimg also publishes point release pinned AlmaLinux 9 images on x86.
Because this is the Arm build, it is a genuinely separate product from the x86 AlmaLinux 9 images: it carries arm64 packages and an aarch64 kernel, and it only runs on Arm virtual machine sizes.
Security posture
This image ships with no known credential. There is no default password and no baked in SSH key:
- The root account password is locked, so no one can log in as root with a password.
- No account on the image has a usable password at all.
PasswordAuthenticationis disabled in the SSH daemon, so every login is by key.PermitRootLoginis set towithout-password, so root cannot be reached with a password. No root key ships in the image, so in practice root has no remote login until you create one deliberately.- SSH host keys are removed before capture and regenerated on each machine at first boot, so no two machines you launch share a host identity.
/etc/machine-idis cleared before capture and regenerated per machine.- The only key that works is the public key you supply when you create the virtual machine. Azure injects it into
azureuserat first boot. - SELinux is left in enforcing mode, the AlmaLinux default.
Prerequisites
Before deploying this image, ensure you have:
- An active Microsoft Azure subscription
- Access to the Azure Portal or Azure CLI
- An SSH key pair for Linux VM access
- Familiarity with Azure VM management
Recommended VM Size: Standard_D2ps_v6 (2 vCPU, 8 GB RAM) or larger. This is an Arm64 image, so it must be launched on an Arm virtual machine size — the Dpsv6/Dplsv6 (Ampere Altra) families, or another p-suffixed Arm size. An x86 size such as Standard_B2s will not boot this image.
Step 1: Deploy the Virtual Machine
Option A: Azure Portal
- Navigate to the Azure Marketplace and search for "AlmaLinux 9 Arm64 cloudimg"
- Select the image and click Create
- Configure the basics:
- Subscription: Select your Azure subscription
- Resource Group: Create new or select existing
- Virtual Machine Name: Enter a name for your VM
- Region: Select your preferred Azure region
- Size:
Standard_D2ps_v6recommended (an Arm64 size) - Under Administrator Account, select SSH public key and enter your key
- Under Inbound Port Rules, allow SSH (port 22)
- Click Review + Create, then Create
Option B: Azure CLI
Run this on your own workstation, substituting your resource group, VM name and preferred region:
az vm create \
--resource-group myResourceGroup \
--name my-almalinux-9-arm64-vm \
--image cloudimg:almalinux-9-arm64:arm64:latest \
--size Standard_D2ps_v6 \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
The --size must be an Arm64 family (Standard_D2ps_v6 above). Azure rejects an x86 size for an Arm image at create time.
Step 2: Connect via SSH
Run these on your own workstation, not on the VM. Find the public IP, then connect:
az vm show --resource-group myResourceGroup --name my-almalinux-9-arm64-vm --show-details --query publicIps -o tsv
ssh azureuser@<vm-ip>
There is no password to enter. If SSH asks you for one, the key you supplied at create time is not the key your client is offering.
Step 3: Confirm the release and the architecture
Check that the machine is AlmaLinux 9, and confirm it is running on Arm.
cat /etc/almalinux-release
AlmaLinux release 9.8 (Olive Jaguar)
The os-release fields carry the same information in a machine readable form, which is what configuration management tools read:
grep -E '^(NAME|VERSION|ID|VERSION_ID|PLATFORM_ID)=' /etc/os-release
NAME="AlmaLinux"
VERSION="9.8 (Olive Jaguar)"
ID="almalinux"
VERSION_ID="9.8"
PLATFORM_ID="platform:el9"
The RHEL compatibility lineage is asserted by the distribution itself:
cat /etc/redhat-release
AlmaLinux release 9.8 (Olive Jaguar)
And the kernel, which is where the architecture shows up:
uname -srm
Linux 5.14.0-687.36.1.el9_8.aarch64 aarch64

aarch64 from uname confirms this is the native 64-bit Arm build, not an emulated or x86 image. The version reported is the current AlmaLinux 9 point release the image was patched to; this listing tracks the 9 major release, so dnf update rolls it forward across future point releases.
Step 4: Check the patch level and automatic updates
Every available update was applied when this image was built. dnf check-update exits 100 when updates are pending and 0 when none are:
dnf -q check-update >/dev/null 2>&1; echo "exit=$?"
exit=0
Nothing is waiting on a reboot either:
dnf needs-restarting -r
No core libraries or services have been updated since boot-up.
Reboot should not be necessary.
Your update path is intact. The base repositories are enabled:
dnf repolist --enabled
repo id repo name
appstream AlmaLinux 9 - AppStream
baseos AlmaLinux 9 - BaseOS
extras AlmaLinux 9 - Extras
CRB (CodeReady Builder) ships configured but disabled, which is the AlmaLinux default. Enable CRB only if you are building software that needs its development headers:
sudo dnf config-manager --set-enabled crb
Automatic updates
Unlike a stock AlmaLinux cloud image, this machine keeps patching itself. dnf-automatic is installed and its timer is enabled, so the machine applies updates on its own schedule:
systemctl is-enabled dnf-automatic.timer
enabled
systemctl cat dnf-automatic.timer | grep -E 'OnCalendar|RandomizedDelaySec'
OnCalendar=*-*-* 6:00
RandomizedDelaySec=60m
The policy applies all available updates, not only those tagged as security errata, and never reboots automatically — so a kernel update waits for a window you choose rather than restarting a production machine without warning:
grep -E '^(apply_updates|upgrade_type|download_updates|reboot)' /etc/dnf/automatic.conf
upgrade_type = default
download_updates = yes
apply_updates = yes
reboot = never
upgrade_type is deliberately default rather than security. On an image whose errata metadata is not always populated, a security only policy can silently resolve to applying nothing, so default is the setting that actually keeps the machine current. Because reboot = never, check periodically whether a restart is owed after a kernel update, using the needs-restarting -r command above. To opt out entirely, run sudo systemctl disable --now dnf-automatic.timer.

Step 5: Verify the security posture
Confirm SELinux is enforcing:
getenforce
Enforcing
Confirm the root account is locked. The LK in the second field means locked:
passwd -S root
root LK 2009-12-22 -1 -1 -1 -1 (Alternate authentication scheme in use.)
Confirm no account on the machine has a usable password. This prints nothing on a clean image:
sudo awk -F: '($2 !~ /^[!*]/) && ($2 != "") {print $1}' /etc/shadow
Read the SSH daemon's effective policy rather than the config file, since the running daemon is what a client actually meets:
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication) '
permitrootlogin without-password
pubkeyauthentication yes
passwordauthentication no
There is no swap on the operating system disk. Azure manages swap on the ephemeral resource disk if you want it, and a swap file baked into a Marketplace image is not permitted, so this prints nothing:
swapon --show
Confirm what is listening. On a freshly launched machine sshd on port 22 is your remote access; the base OS also runs rpcbind on port 111 and chronyd bound to the loopback address for time synchronisation:
ss -tulnH | awk '{print $1, $5}' | sort -u
tcp 0.0.0.0:111
tcp 0.0.0.0:22
tcp [::]:111
tcp [::]:22
udp 0.0.0.0:111
udp 127.0.0.1:323
udp [::1]:323
udp [::]:111
The Azure Network Security Group is the control plane for inbound traffic: only the ports you explicitly open in the NSG are reachable from outside the VM, regardless of what is bound locally. If you do not use RPC based services and want to close port 111 on the host as well, sudo systemctl disable --now rpcbind.socket rpcbind.service stops it.

Step 6: Verify Azure integration
The Azure Linux Agent handles provisioning, extensions and the portal's serial console:
systemctl is-active waagent
rpm -q WALinuxAgent cloud-init chrony
active
WALinuxAgent-2.14.0.1-4.el9.noarch
cloud-init-24.4-8.el9_8.1.alma.1.noarch
chrony-4.8-1.el9.aarch64
Cloud-init should report done, which means provisioning finished cleanly:
cloud-init status
status: done
The kernel puts a console on ttyAMA0, the Arm PL011 serial port, which is what makes boot diagnostics and the portal's serial console work when you cannot reach the machine over SSH. (On x86 images this is ttyS0; on Arm it is ttyAMA0.)
grep -o 'console=ttyAMA0[^ ]*' /proc/cmdline
Time synchronisation runs through chronyd against the Azure host clock:
chronyc -n sources | head -4
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
#* PHC0 2 3 377 8 -5662ns[-8956ns] +/- 3607ns
Azure attaches an ephemeral resource disk, which waagent formats and mounts at /mnt. It is fast, local and erased whenever the machine is deallocated or moved, so use it for scratch and caches, never for data you need to keep:
df -h / /mnt
Filesystem Size Used Avail Use% Mounted on
/dev/sda4 29G 1.4G 28G 5% /
/dev/sdb1 7.8G 28K 7.4G 1% /mnt

Step 7: Networking and the firewall
The Azure Network Security Group is the control plane for inbound and outbound traffic, and it is the first and usually the only place you need to open a port.
This image is a minimal build, so firewalld is not installed:
rpm -q firewalld || echo "(firewalld absent - the Azure NSG is your only firewall)"
package firewalld is not installed
(firewalld absent - the Azure NSG is your only firewall)
That is deliberate: with no host firewall in the way, your NSG rules take effect exactly as written, with no second layer to keep in sync. Confirm what is listening before you open anything:
ss -tlnp
If your policy requires a host firewall as well, install and enable it yourself:
sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --reload
Once firewalld is running, a port must be open in both the Azure NSG and firewalld before traffic reaches your service.
Step 8: Install software
AppStream carries the application packages. Search it, then install what you need:
dnf search nginx
Install a package and enable it in the usual way, for example sudo dnf install -y nginx followed by sudo systemctl enable --now nginx. Everything in the repositories is built for Arm, so dnf resolves aarch64 packages automatically — there is nothing extra to configure for the architecture.
To add another administrator, create the account with sudo adduser <name>, add it to the wheel group with sudo usermod -aG wheel <name>, then copy your public key into /home/<name>/.ssh/authorized_keys with mode 600 and the .ssh directory mode 700, owned by that user. The image ships with no human accounts at all, so azureuser is the only login until you add one:
getent group wheel
Common Use Cases
- RHEL 9 compatible application hosting on cost efficient Arm virtual machines
- Container platforms and cloud native workloads on Arm
- A RHEL compatible platform for workloads certified against RHEL 9 without a subscription
- Arm build and CI runners, and fleets managed by Ansible, Puppet or Salt
Troubleshooting
Cannot connect via SSH
Check the Network Security Group allows inbound TCP 22 from your address, confirm the machine is running, and make sure your client is offering the private key that matches the public key you supplied at create time. Password authentication is disabled, so a password prompt means key negotiation already failed.
az vm show --resource-group myResourceGroup --name my-almalinux-9-arm64-vm --show-details --query powerState -o tsv
The VM will not create or will not boot
This is an Arm64 image. It only runs on Arm virtual machine sizes (the Dpsv6/Dplsv6 Ampere families and other p-suffixed sizes). If Azure rejects the size at create time, or the VM never becomes reachable, confirm you selected an Arm size and not an x86 one such as Standard_B2s or Standard_D2s_v5.
Azure agent not running
systemctl status waagent --no-pager
sudo journalctl -u waagent -n 50 --no-pager
If waagent is stopped, extensions and portal features that depend on it stop working. Restart it with sudo systemctl restart waagent.
Package manager issues
Clear the metadata cache and rebuild it:
sudo dnf clean all
sudo dnf makecache
If a repository is unreachable, confirm the machine has outbound HTTPS and that DNS resolves, since AlmaLinux's mirror system is reached over both.
A service is blocked and the logs mention SELinux
SELinux is enforcing, which is the secure default and should stay that way. Look at the denial rather than disabling SELinux:
sudo ausearch -m AVC -ts recent 2>/dev/null || echo "no recent AVC denials"
Most denials on a fresh machine come from software installed into a non standard path. Relabel with sudo restorecon -Rv /path before considering any policy change.
Important Notes
AlmaLinux is free and open source. It is not covered by a single licence: the distribution is a collection of free and open source licences across its package set, predominantly GPL, with LGPL, BSD, MIT, Apache and others. No subscription, entitlement or licence key is required, and no per seat cost applies.
AlmaLinux is a trademark of the AlmaLinux OS Foundation. cloudimg is not affiliated with or endorsed by the AlmaLinux OS Foundation, and uses the name only to identify the distribution this image contains.
AlmaLinux 9 follows the RHEL 9 lifecycle, with support running into 2032. Check the AlmaLinux release notes for the dates that apply to your deployment.
Support
For assistance with this image, contact cloudimg support:
- Website: www.cloudimg.co.uk
- Product Catalogue: www.cloudimg.co.uk/products
- User Guides: www.cloudimg.co.uk/guides
- SLA: 24 hour response guaranteed