Ubuntu 26.04 LTS Arm64 on Azure User Guide
Overview
This guide covers the deployment and configuration of Ubuntu 26.04 LTS on 64-bit Arm (AArch64/arm64) on Microsoft Azure using cloudimg's pre configured virtual machine image from the Azure Marketplace.
Ubuntu is a widely used Linux distribution produced by Canonical. Version 26.04, codenamed Resolute Raccoon, is a long term support (LTS) release with standard security maintenance to 29 May 2031, giving you a stable, modern base with a current kernel, toolchain and package set. This is the native Arm build: it runs on Azure's Arm virtual machine sizes, where many workloads deliver the same performance at a lower cost than on x86.
What's included:
- Ubuntu 26.04 LTS, native 64-bit Arm (arm64 / AArch64)
- Every available security update applied at build time, phased updates included
unattended-upgradesarmed, so the machine keeps applying security updates on its own- Azure Linux Agent (walinuxagent) and cloud-init for Azure integration
chronyfor NTP time synchronisation, synchronised against the Azure host clock- AppArmor in enforcing mode
- The standard Ubuntu archive (ports.ubuntu.com) enabled, so your update path is intact
- Gen2 Hyper V virtual machine support
- 24/7 cloudimg support
Platform: Microsoft Azure (Gen2 Hyper V, Arm64)
Default user: azureuser
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.
PasswordAuthenticationis disabled in the SSH daemon, so every login is by key.PermitRootLoginis set toprohibit-password, so root cannot be reached with a password even if one were later set.- The only key that works is the public key you supply when you create the virtual machine. Azure injects it into
azureuserat first boot, so no two machines you launch ever share a secret. - The machine identity and the SSH host keys are regenerated uniquely on every instance, so no two machines share a host key either.
- AppArmor is left enabled and enforcing, the Ubuntu default.
- Exactly one port listens for off box traffic: TCP 22 for SSH. Link local name resolution (LLMNR and mDNS) is pinned off, so the image opens no 5355 or 5353 listener.
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 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
- Sign in to the Azure Portal.
- Select Create a resource and search the Marketplace for Ubuntu 26.04 LTS Arm64 by cloudimg.
- Select Create.
- On the Basics tab:
- Choose your subscription and resource group.
- Enter a virtual machine name.
- Choose a region that offers Arm sizes.
- For Size, select See all sizes, filter on the Arm64 architecture and choose
Standard_D2ps_v6or larger. - Set the Authentication type to SSH public key.
- Set the Username to
azureuserand supply your public key. - On the Disks tab, choose your OS disk type. Standard SSD is a good default.
- On the Networking tab, allow inbound SSH (22) from your own address range rather than from the whole internet.
- Select Review + create, then Create.
Option B: Azure CLI
az vm create \
--resource-group myResourceGroup \
--name myUbuntuArm64VM \
--image cloudimg1702aborea:ubuntu-26-04-arm64:arm64:latest \
--size Standard_D2ps_v6 \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_rsa.pub \
--public-ip-sku Standard
The size must be an Arm size. If you pass an x86 size, Azure rejects the deployment because the image is published for the Arm64 architecture.
Step 2: Connect via SSH
ssh azureuser@<public-ip>
The image accepts key based logins only, so there is no password to enter and none to change.
Step 3: Confirm the release and the architecture
Check that the machine is the Ubuntu 26.04 LTS release you expect, and confirm it is running on Arm:
grep -E '^(NAME|VERSION|ID|VERSION_ID|PRETTY_NAME)=' /etc/os-release
uname -srm
dpkg --print-architecture
lscpu | grep -E '^Architecture|^Vendor ID|^Model name'
Expected output:
PRETTY_NAME="Ubuntu 26.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="26.04"
VERSION="26.04.1 LTS (Resolute Raccoon)"
ID=ubuntu
Linux 7.0.0-1012-azure aarch64
arm64
Architecture: aarch64
Vendor ID: ARM
Model name: Neoverse-N2

aarch64 from uname and arm64 from dpkg both confirm this is the native 64-bit Arm build, not an emulated or x86 image. lscpu names the physical Arm core you landed on — Neoverse-N2 is the core used by Azure's Cobalt 100 processor behind the Dpsv6 family. The /etc/os-release fields print in the order the file stores them, which is why PRETTY_NAME appears first.
Step 4: Check the patch level
The image is fully updated at build time. Confirm that no upgrades are pending and that the update path is intact:
apt-get -s -o APT::Get::Always-Include-Phased-Updates=true dist-upgrade | grep -c '^Inst ' | sed 's/^/pending upgrades: /'
apt-mark showhold
dpkg -l | awk '/linux-image-[0-9]/{print $2, $3}'
Expected output shows pending upgrades: 0, no held packages, and exactly one installed Arm kernel:
pending upgrades: 0
linux-image-7.0.0-1012-azure 7.0.0-1012.12

apt-mark showhold prints nothing, which is the correct result: no package is pinned back. The Always-Include-Phased-Updates=true flag matters on Ubuntu because without it a plain check can silently hide phased security updates held back on this particular machine. Only one linux-image package is installed — the superseded kernel is purged at build time, so no stale kernel ships with the image. This image also arms unattended-upgrades, so security updates are downloaded and applied on a timer without any action from you. You can still run updates on your own schedule:
sudo apt-get update && sudo apt-get -y dist-upgrade
Reboot if a new kernel was installed:
sudo systemctl reboot
Step 5: Verify the security posture
Confirm AppArmor is enforcing, root is locked, and SSH is key only:
sudo aa-status --enabled && echo "apparmor: enabled"
sudo passwd -S root
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication) '
ss -tln | awk '$1=="LISTEN"{print $1, $4}' | sort
sudo ufw status | head -1
Expected output:
apparmor: enabled
root L 2009-12-22 -1 -1 -1 -1
permitrootlogin prohibit-password
pubkeyauthentication yes
passwordauthentication no
LISTEN 0.0.0.0:22
LISTEN 127.0.0.53%lo:53
LISTEN 127.0.0.54:53
LISTEN [::]:22
Status: inactive

OpenSSH 10, which ships with Ubuntu 26.04, reports this setting under its modern name prohibit-password. Ubuntu 24.04 and earlier printed the legacy synonym without-password for the same policy, so do not be surprised by the difference if you are comparing against an older machine. Either way the meaning is the same: root may not authenticate with a password. The L from passwd -S is the part that matters: the root account is locked, so there is no root password to use. The 2009-12-22 date and the -1 aging fields that follow it are artefacts of the image build and Azure's deprovisioning step, not a record of a real password change — every virtual machine launched from this image reports exactly those values.
Only port 22 is reachable from off the machine. The two entries on 127.0.0.53 and 127.0.0.54 are systemd-resolved's local DNS stubs: they are bound to loopback addresses, so nothing outside the virtual machine can reach them. Ubuntu 26.04 binds two stubs rather than one — 127.0.0.53 is the normal stub and 127.0.0.54 is the bypass cache stub — and only the first carries the %lo interface suffix.
Step 6: Verify Azure integration and unattended updates
Confirm the Azure Linux Agent, cloud-init and time synchronisation are healthy, that unattended security updates are armed, and check your resources:
systemctl is-active walinuxagent 2>/dev/null || systemctl is-active waagent
cloud-init --version
systemctl is-enabled apt-daily-upgrade.timer
apt-config dump | grep -E 'APT::Periodic::(Update-Package-Lists|Unattended-Upgrade) '
systemctl is-active chrony
timedatectl show -p NTPSynchronized --value
df -h /
free -h
Expected output for the first five checks (the df and free figures depend on the size you deployed):
active
/usr/bin/cloud-init 26.1-0ubuntu3~26.04.1
enabled
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
active
yes

The Azure Linux Agent is what lets Azure provision your SSH key, resize the OS disk, run extensions and report VM health, so it should always be active. apt-daily-upgrade.timer being enabled, together with Unattended-Upgrade "1" in the apt periodic config, is what keeps the machine applying security updates on its own after launch.
Time synchronisation on Ubuntu 26.04 is handled by chrony, not by systemd-timesyncd as on Ubuntu 24.04 and earlier — systemd-timesyncd is not installed on this release, so systemctl is-active systemd-timesyncd would report inactive and that is expected, not a fault. chrony synchronises against the Azure host's precision clock, which it reports as the reference PHC0 at stratum 1:
chronyc tracking
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. The image advertises a single inbound port, SSH on 22.
ufw (the Uncomplicated Firewall) is installed on this image but left inactive by default, so it is not filtering traffic and your NSG rules take effect exactly as written, with no second layer to keep in sync. Step 5 above lists exactly what is listening: sshd on port 22, and nothing else that is reachable from off the machine.
Link local name resolution is pinned off in this image, so there is no listener on 5355 (LLMNR) or 5353 (mDNS). Confirm it for yourself:
resolvectl status | grep 'Protocols:' | sed -n '1s/^[[:space:]]*//p'
Expected output:
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
The minus sign in front of LLMNR and mDNS means both protocols are disabled. (sed -n '1s/…//p' rather than grep -m1 is deliberate: grep -m1 exits as soon as it matches, which sends SIGPIPE back to resolvectl and makes the pipeline return 141 under set -o pipefail, even though the line printed correctly.)
If your policy requires a host firewall as well, enable the one that is already installed. Add the SSH rule first, before enabling, so you do not lock yourself out:
sudo ufw allow OpenSSH
sudo ufw allow 443/tcp
sudo ufw enable
Once ufw is running, a port must be open in both the Azure NSG and ufw before traffic reaches your service.
Step 8: Install software
The image ships with the downloaded package lists cleared, which keeps it small but means the very first thing to do on a new machine is refresh them. Until you do, apt-cache search returns nothing and apt-get install reports Unable to locate package:
sudo apt-get update
apt-cache search nginx | sed -n '1,5p'
Expected output (the first few matches; the full list is longer):
libnginx-mod-http-geoip2 - GeoIP2 HTTP module for Nginx
libnginx-mod-http-image-filter - HTTP image filter module for Nginx
libnginx-mod-http-xslt-filter - XSLT Transformation module for Nginx
unattended-upgrades refreshes the lists on its own timer as well, so this is only a concern in the first few minutes of a machine's life. Install a package and enable it in the usual way, for example sudo apt-get install -y nginx followed by sudo systemctl enable --now nginx.
Everything in the archive is built for Arm, so apt resolves arm64 packages automatically — there is nothing extra to configure for the architecture. Arm64 package coverage in Ubuntu's main and universe archives is effectively complete; where a third party vendor ships only an x86 binary you will need their Arm build instead.
To add another administrator, create the account with sudo adduser <name>, add it to the sudo group with sudo usermod -aG sudo <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 sudo
Common Use Cases
- Cost efficient general purpose compute — web servers, application servers and API backends, where the Arm sizes usually deliver the same throughput for less money than the equivalent x86 size.
- Cloud native and container workloads —
arm64container images are now standard across the major registries, so Kubernetes nodes and Docker hosts run natively. - Build and CI infrastructure — native Arm build agents, so you compile and test
arm64artefacts without emulation. - Data services — databases, caches and message brokers that benefit from the Arm sizes' memory bandwidth and consistent per core performance.
- A modern LTS base image — a current kernel and toolchain with security maintenance to 2031, as the foundation for your own golden images.
Troubleshooting
Cannot connect via SSH
Check that the Azure NSG allows inbound TCP 22 from your address, that you are connecting as azureuser, and that you are offering the private key matching the public key you supplied at deployment:
ssh -v azureuser@<public-ip>
The image has password authentication disabled, so a password prompt never appears. If you see Permission denied (publickey), the key you offered is not the one Azure injected.
The VM will not create or will not boot
This is an Arm64 image. It only runs on Arm virtual machine sizes — the Dpsv6 and Dplsv6 families and other p-suffixed sizes. If the deployment was rejected, or the machine never reaches the SSH stage, confirm the size you selected is an Arm size. Confirm the architecture from inside the machine with:
uname -m
which must print aarch64.
Azure agent not running
sudo systemctl status walinuxagent
sudo systemctl enable --now walinuxagent
The agent handles key provisioning, disk resizing and extensions, so a stopped agent breaks Azure side management even though the machine itself keeps running. enable --now is safe to run against an agent that is already healthy — it starts the unit only if it is stopped.
Package manager issues
Refresh the package lists and check the archive is reachable:
sudo apt-get update
apt-cache policy | head -20
The Arm build uses ports.ubuntu.com rather than archive.ubuntu.com; both are Canonical's own mirrors and no change is needed.
The clock is wrong, and systemd-timesyncd is not running
That is expected on this release. Ubuntu 26.04 uses chrony for time synchronisation and does not install systemd-timesyncd at all. Check the service that is actually there:
systemctl is-active chrony
chronyc tracking
A service is blocked and the logs mention AppArmor
AppArmor is enforcing by default. Check for denials, and be aware of one known benign signature on this release:
COUNT=$(sudo journalctl -k -b | grep 'apparmor="DENIED"' | grep -v 'profile="systemd-detect-virt"' | wc -l)
echo "unexpected apparmor denials: ${COUNT}"
Expected output on a healthy machine:
unexpected apparmor denials: 0
Ubuntu 26.04 logs three denials per boot from the systemd-detect-virt profile (two journal socket sends and one capable perfmon). That is a packaging defect in the stock 26.04 profile, it has no functional effect, and it is why the check above filters that one profile out rather than counting every denial. A count of zero after that filter is the healthy result; counting unfiltered would report 3 on a perfectly healthy machine. (wc -l rather than grep -c is deliberate here: grep -c exits 1 when it counts zero matches, so the pipeline would report a non-zero status on a perfectly healthy machine.) If your own service is denied, find the profile and put it into complain mode while you develop a policy:
sudo aa-complain /etc/apparmor.d/<profile>
Important Notes
- This image ships no default credential: root is locked, password authentication is off, and no SSH key is baked in. Access is only ever by the key you supply at deployment.
- SSH host keys and the machine ID are regenerated on every instance, so no two machines you launch share an identity.
- Ubuntu 26.04 LTS receives standard security maintenance until 29 May 2031. Ubuntu Pro is an optional Canonical subscription that extends coverage further; it is not included with, not required by and not attached to this image. You may attach your own Canonical subscription with
sudo pro attach <token>if you hold one. - Ubuntu is free and open source, but it is a collection of thousands of independently licensed packages rather than a single licence. Each package's terms are readable with
dpkg -s <package>and under/usr/share/doc/<package>/copyright. - This image is provided by cloudimg. It is not affiliated with, endorsed by or sponsored by Canonical Ltd. "Ubuntu" is a trademark of Canonical Ltd., used here nominatively to identify the distribution the image contains.
Support
For technical assistance with this image, contact cloudimg support at support@cloudimg.co.uk.