Operating Systems Azure

Ubuntu 26.04 LTS Sysadmin Tools on Azure User Guide

| Product: Ubuntu 26.04 LTS Sysadmin Tools on Azure

Overview

This guide covers the deployment and configuration of Ubuntu Server 26.04 LTS Sysadmin Tools 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 is a long term support (LTS) release, with standard security maintenance to 2031. What makes this image different from the standard Ubuntu Server 26.04 LTS image is the package profile: a named, version pinned set of 84 system administration packages, every one of them from Ubuntu's own archive, installed and ready. 65 come from main, which carries Canonical's full five year security commitment; 19 come from universe.

Be clear about what this is and what it is not. It is not a monitoring appliance, a hardened build, or a security product. It is the standard Ubuntu Server image with a documented toolbox already on it, saving you the apt install list you would otherwise type on every server you touch. There is no third party software, no vendor repository, and nothing on the image that Canonical does not publish.

Every daemon the profile adds is installed but disabled. rsync has its daemon mode, atop and sysstat have collectors, auditd has its dispatcher, at has atd, smartmontools has smartd — all present, all switched off. A freshly deployed machine listens on port 22 and nothing else. Turning a service on is a deliberate act you take, not a default you have to undo.

Just as deliberately, the daemons that the standard Ubuntu image already runs are left exactly as Canonical ships them. chrony still keeps time, cron still runs jobs, logrotate.timer still rotates logs, fstrim.timer still trims the disk. This variant does not quietly differ from stock Ubuntu in ways you would have to discover for yourself. The split was measured at build time against the stock image, not hand written, and the image ships the result so you can read it.

The image describes itself. Two files are generated from its own package database and its own measured unit state:

  • /usr/share/cloudimg/ubuntu-26-04-pkg-sysadmin.manifest — every package, its version, its component, and what was deliberately left out and why
  • /usr/share/cloudimg/ubuntu-26-04-pkg-sysadmin.units — every systemd unit the profile touches, marked profile-disabled or base-stock

What's included:

  • Ubuntu Server 26.04 LTS with 84 system administration packages preinstalled — 65 from main, 19 from universe, every one from the Ubuntu archive
  • Process and performance: htop, atop, sysstat (sar, iostat, mpstat, pidstat), iotop, nmon, procps, psmisc, lsof, numactl
  • Tracing and debugging: strace, ltrace, linux-tools-common
  • Storage and filesystems: lvm2, mdadm, parted, gdisk, nvme-cli, hdparm, smartmontools, quota, acl, attr, cifs-utils, sshfs, ncdu
  • Terminal and editing: tmux, screen, byobu, vim, nano, less, mc, tree
  • Search and text: ripgrep (rg), fd-find (fdfind), bat (batcat), fzf, jq, moreutils, parallel
  • Transfer and archive: rsync, curl, wget, unzip, zip, bzip2, xz-utils, zstd, pv, git
  • Network diagnostics: iproute2, net-tools, ethtool, tcpdump, mtr-tiny, bind9-dnsutils (dig), whois, socat, netcat-openbsd, ipcalc
  • Hardware inventory: dmidecode, pciutils (lspci), usbutils (lsusb), sysfsutils, cpu-checker
  • System hygiene and safety: auditd, logrotate, cron, at, chrony, needrestart, apt-listchanges, unattended-upgrades, debsums, inotify-tools, molly-guard
  • A shipped manifest and unit disposition file derived from the image's own state, so the description cannot drift from what is installed
  • Every available security update applied at build time, with unattended-upgrades armed so the machine keeps patching itself
  • AppArmor in enforcing mode, Gen2 Hyper V support, and 24/7 cloudimg support

What's deliberately NOT included:

  • Offensive and dual use tooling. nmap, hping3, masscan, zmap, ettercap, aircrack-ng, sqlmap, hydra and their relatives are excluded by design. They are yours to install if your work needs them; they are not something a general purpose image should ship by default, and they are a recurring source of antivirus false positives on cloud marketplaces.
  • glances. A universe Python application that pulls a heavy dependency chain and enables its own service on 127.0.0.1:61209 the moment it is installed. htop, atop and sysstat cover the same ground without a listener.
  • fio and iperf3. Both ship a systemd unit that listens on all interfaces by default (fio --server on TCP 8765, iperf3 on TCP 5201). Neither belongs on an image whose promise is that nothing but SSH is reachable. iperf3 is on the sibling networking profile, where it is the point.
  • ipmitool and lm-sensors. Both are hardware bound. There is no BMC and there are no hardware sensors on an Azure virtual machine, so both would report nothing while lm-sensors enabled a unit and ipmitool recommended a daemon.
  • dstat. Not published on Ubuntu 26.04 at all; it was removed after Ubuntu 24.04. sar and atop are its replacements.

Platform: Microsoft Azure (Gen2 Hyper V, x64) 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.
  • PermitRootLogin is set to no, so root has no SSH login path at all.
  • PasswordAuthentication is disabled, so every login is by key.
  • The only key that works is the public key you supply when you create the virtual machine. Azure injects it into azureuser at 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.
  • Every daemon the profile adds ships disabled and stopped. The off box listening set is exactly {22}.
  • AppArmor is left enabled and enforcing, the Ubuntu default.
  • No package on the image depends on Ubuntu Pro to receive its security fixes.

Prerequisites

Before deploying this image, ensure you have:

  1. An active Microsoft Azure subscription
  2. Access to the Azure Portal or Azure CLI
  3. An SSH key pair for Linux VM access
  4. Familiarity with Azure VM management

Recommended VM Size: Standard_B2s (2 vCPU, 4 GB RAM) or larger.

Disk size: the image ships as a 28 GiB OS disk. The 84 named packages total about 110 MB installed; a stock deployment uses about 3 GB and leaves around 26 GB free.

Step 1: Deploy the Virtual Machine

Option A: Azure Portal

  1. Sign in to the Azure Portal
  2. Search the Marketplace for Ubuntu 26.04 LTS Sysadmin Tools and select the cloudimg offering
  3. Click Create
  4. Choose your subscription, resource group and region
  5. Set the VM size to Standard_B2s or larger
  6. Under Administrator account, choose SSH public key, leave the username as azureuser, and paste your public key
  7. Under Inbound port rules, allow SSH (22) only, and restrict the source to your own management address range
  8. Review and create

Option B: Azure CLI

az vm create \
  --resource-group my-resource-group \
  --name ubuntu-sysadmin \
  --image cloudimg:ubuntu-26-04-pkg-sysadmin:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_rsa.pub \
  --public-ip-sku Standard

Step 2: Connect via SSH

ssh azureuser@<vm-ip>

Replace <vm-ip> with your virtual machine's public IP address.

Step 3: Confirm the release and the profile

Check you have the release you expect:

grep -E '^(NAME|VERSION|ID|VERSION_ID|PRETTY_NAME)=' /etc/os-release
uname -srm
dpkg --print-architecture

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-1014-azure x86_64
amd64

Nothing is held back from updates, and the machine was fully patched at build time:

apt-mark showhold
echo "pending upgrades (phased updates included): $(apt-get -s -o APT::Get::Always-Include-Phased-Updates=true dist-upgrade 2>/dev/null | grep -c '^Inst ')"

apt-mark showhold printing nothing is the good result — it means no package is pinned away from its security updates.

The terminal shows the os-release fields reporting Ubuntu 26.04.1 LTS Resolute Raccoon with VERSION_ID 26.04, uname reporting an x86_64 Azure kernel, dpkg print-architecture reporting amd64, no held packages, zero pending upgrades with phased updates included, and chrony reporting active

Step 4: Read the shipped manifest

The image tells you what it is. The manifest is generated from the machine's own package database at build time, so it cannot drift from what is actually installed:

awk -F'\t' 'NF>=3{print $3}' /usr/share/cloudimg/ubuntu-26-04-pkg-sysadmin.manifest | sort | uniq -c
echo "total named by the profile: $(grep -vE '^#' /usr/share/cloudimg/ubuntu-26-04-pkg-sysadmin.manifest | grep -c .)"

Expected output:

     65 main
     19 universe
total named by the profile: 84

The header records the series and when it was generated:

head -6 /usr/share/cloudimg/ubuntu-26-04-pkg-sysadmin.manifest
# cloudimg ubuntu-26-04-pkg-sysadmin — shipped package profile
# Derived from the dpkg database at build time. Do not hand-edit.
# generated: 2026-09-22T12:12:22Z
# series: resolute (Ubuntu 26.04 LTS)
#
# [main] — Canonical five-year security commitment on 26.04 LTS

And the tail records what was left out, with the reason for each:

tail -8 /usr/share/cloudimg/ubuntu-26-04-pkg-sysadmin.manifest
# [excluded-by-contract]
# dstat      not published on resolute (removed from noble, no rdeps)
# glances    universe Python app; enabled service on 127.0.0.1:61209; heavy Recommends chain
# fio        ships fio.service (fio --server) listening on all interfaces TCP 8765
# iperf3     ships iperf3.service on all interfaces TCP 5201; belongs to the networking profile
# ipmitool   hardware-bound (no BMC on Azure); Recommends the openipmi daemon
# lm-sensors hardware-bound (no sensors on Azure); oneshot unit enabled on install
# plus every offensive or dual-use tool (nmap, hping3, masscan, aircrack-ng, hydra, ...)

The terminal shows the manifest reporting 65 main and 19 universe packages totalling 84, 743 packages installed in total, a sample of the toolbox each reporting its own version including htop, atop, sar, iotop, strace, lsof, smartctl, lvm, mdadm, nvme, ripgrep, batcat, fdfind, ncdu and jq, the three name traps mapping bat to batcat, fd-find to fdfind and sysstat to sar and iostat, and df reporting 26 GB free on the root filesystem

Three name traps worth knowing

Three packages in this profile install a binary whose name is not the package name. This catches people out, so the image documents it:

Package Command you actually run
bat batcat
fd-find fdfind
sysstat sar, iostat, mpstat, pidstat (there is no sysstat command)
command -v batcat fdfind sar iostat

Also note that linux-tools-common is installed but ships no perf binary on Ubuntu 26.04 — perf lives in the linux-perf package, which you can add with apt install linux-perf if you need it.

Step 5: Confirm nothing is listening

This is the property the image exists for. Check it yourself:

ss -tln | awk '$1=="LISTEN"{print $1, $4}' | sort -u

Expected output:

LISTEN 0.0.0.0:22
LISTEN 127.0.0.53%lo:53
LISTEN 127.0.0.54:53
LISTEN [::]:22

The two 127.0.0.5x:53 rows are systemd-resolved, the stub DNS resolver that every Ubuntu machine runs. They are bound to loopback and are not reachable from the network. Filter them out and you get the set that actually matters:

ss -H -l -t -n | awk '{print $4}' | grep -vE '^(127\.|\[?::1\]?:|\[::ffff:127\.)' | sed -E 's/.*:([0-9]+)$/\1/' | sort -un | tr '\n' ' '
echo

That prints 22 and nothing else.

The terminal shows every TCP listener with a summary confirming only port 22 is reachable from the network, the unit disposition counts of 34 profile-introduced units disabled and 17 base-stock units left as Ubuntu ships them, the listener-capable profile daemons rsync, atop, atopacct, auditd, atd, smartmontools, quota, sysfsutils and apt-listchanges all reporting disabled, the base-stock daemons chrony, cron, logrotate and fstrim all reporting enabled, the root account reporting L for locked, sshd reporting permitrootlogin no and passwordauthentication no with clientaliveinterval 120, and no swap configured

Step 6: Daemons — what is off, what is on, and why

The image ships a file recording the disposition of every systemd unit the profile touches. profile-disabled means this profile introduced it and shipped it switched off; base-stock means the standard Ubuntu image already ran it and this image left it alone.

echo "profile-introduced, shipped disabled: $(awk -F'\t' '$2=="profile-disabled"' /usr/share/cloudimg/ubuntu-26-04-pkg-sysadmin.units | grep -c .)"
echo "base-stock, left exactly as Ubuntu:   $(awk -F'\t' '$2=="base-stock"' /usr/share/cloudimg/ubuntu-26-04-pkg-sysadmin.units | grep -c .)"

Check the daemons that could have opened a port:

for u in rsync.service atop.service auditd.service atd.service smartmontools.service quota.service; do
  printf '%-26s %s\n' "$u" "$(systemctl is-enabled "$u" 2>/dev/null | head -1)"
done

Every one of them reports disabled. Now check the ones this image deliberately did not touch:

for u in chrony.service cron.service logrotate.timer fstrim.timer; do
  printf '%-26s %s\n' "$u" "$(systemctl is-enabled "$u" 2>/dev/null | head -1)"
done

Every one of them reports enabled — exactly as on a standard Ubuntu Server 26.04 machine. Time synchronisation, scheduled jobs, log rotation and periodic disk trim all keep working. A "sysadmin tools" image that silently switched those off would be a worse machine, not a safer one.

Step 7: Check the patch level and the Ubuntu Pro position

Ubuntu Pro is Canonical's optional paid subscription. Some Ubuntu packages have security fixes that are published only to Pro's ESM repositories, which means a machine without a subscription can never apply them — and, crucially, apt reports nothing at all in that situation, so "0 upgradable" is not on its own evidence of anything.

This image ships unattached, and was verified at build time to carry zero fixes that need Pro:

pro status --format=json 2>/dev/null | python3 -c "import json,sys; print('pro attached:', json.load(sys.stdin).get('attached'))"
pro security-status --format=json 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin); s=d.get('summary',{})
pa=[p for p in d.get('packages',[]) if p.get('status') in ('pending_attach','pending_becoming_available')]
print('  num_esm_infra_updates  :', s.get('num_esm_infra_updates'))
print('  num_esm_apps_updates   :', s.get('num_esm_apps_updates'))
print('  packages pending_attach:', len(pa))
print('  installed packages     :', s.get('num_installed_packages'))"

Expected output:

pro attached: False
  num_esm_infra_updates  : 0
  num_esm_apps_updates   : 0
  packages pending_attach: 0
  installed packages     : 743

Every package on this image can be patched for free, for the life of Ubuntu 26.04's standard support.

The terminal shows pro reporting the image is not attached, security-status reporting zero esm-infra and zero esm-apps updates with no packages pending attach and 743 installed packages, the manifest header naming the resolute series, the universe section, and the excluded-by-contract section naming dstat, glances, fio, iperf3, ipmitool and lm-sensors with the reason for each

Step 8: Use the toolbox

Everything is already there. A few starting points:

uptime
sar -u 1 3
iostat -x 1 2

Find what is using disk, and what is using it now:

sudo du -xh --max-depth=1 /var 2>/dev/null | sort -h | tail -5
sudo iotop -b -n 1 2>/dev/null | head -8

ncdu is on the image too, for browsing the same information interactively — run sudo ncdu -x / when you have a terminal.

Inspect a process:

SSHD_PID="$(pgrep -n sshd || echo 1)"
echo "inspecting pid ${SSHD_PID}"
sudo lsof -p "${SSHD_PID}" 2>/dev/null | head -8
echo "(lsof lists the files and sockets that process holds open)"

Search a tree quickly:

rg --stats 'PermitRootLogin' /etc/ssh 2>/dev/null | tail -6
fdfind --max-depth 1 'conf$' /etc/ssh

Look at hardware and storage:

sudo dmidecode -s system-manufacturer
lspci | head -5
sudo smartctl --scan || echo "(no SMART-capable devices on a virtual disk — expected on Azure)"

Step 9: molly-guard, and why sudo reboot asks you a question

molly-guard is part of this profile on purpose. It intercepts halt, poweroff, reboot and shutdown and, when it detects that you are in an SSH session, asks you to type the hostname before it will proceed. It exists to stop you rebooting the wrong machine — the single most common way a remote administrator ruins an afternoon.

You can see the interception for yourself:

dpkg-divert --list | grep 'by molly-guard' | sort

What this means in practice:

  • Interactive sudo reboot over SSH will prompt you for the hostname. That is the feature working.
  • sudo systemctl reboot is not intercepted and reboots immediately. systemctl is deliberately left un-diverted so you always have an un-prompted path.
  • Automation is unaffected. molly-guard only challenges when it detects an SSH session, so cloud-init, unattended-upgrades and anything driven from systemd reboot normally.

Confirm the escape hatch is real:

readlink -f /sbin/reboot
command -v systemctl

/sbin/reboot resolves to the molly-guard shim; systemctl resolves to /usr/bin/systemctl, the real thing.

If you script reboots over SSH and want the direct path, use either of these:

# Example only — these actually reboot the machine.
sudo systemctl reboot
sudo reboot --force

If you would rather not have the guard at all, remove the package and the diversions go with it:

# Example only — run this if you do not want the reboot confirmation.
sudo apt-get remove -y molly-guard

Step 10: Turn a service on when you want it

Nothing the profile adds starts by itself. When you want one, enable it deliberately:

# Example only — run these when you actually want the service.
sudo systemctl enable --now auditd.service        # kernel audit logging
sudo systemctl enable --now atd.service           # one-off scheduled jobs via 'at'
sudo systemctl enable --now smartmontools.service # SMART monitoring (bare metal)
sudo systemctl enable --now atop.service          # continuous performance history

rsync.service is the one to think twice about: enabling it starts rsync --daemon, which listens on TCP 873 on all interfaces. If you only use rsync over SSH — which is the normal case, and what rsync -e ssh does — you never need the daemon at all. If you do enable it, restrict the port in your network security group and configure /etc/rsyncd.conf before you start it.

Step 11: IPsec on this image

For the Dirty Frag class of kernel vulnerabilities (CVE-2026-43284, CVE-2026-43500) the image ships a mitigation at /etc/modprobe.d/dirtyfrag.conf that prevents the esp4, esp6, ipcomp, ipcomp6 and rxrpc modules from loading:

cat /etc/modprobe.d/dirtyfrag.conf

Expected output:

install esp4 /bin/false
install esp6 /bin/false
install ipcomp /bin/false
install ipcomp4 /bin/false
install ipcomp6 /bin/false
install rxrpc /bin/false

This profile contains no IPsec implementation, so the mitigation costs you nothing. But if you intend to terminate an IPsec tunnel on this virtual machine, you need those kernel modules and must remove the file:

# Only if you are terminating IPsec ON this VM.
sudo rm /etc/modprobe.d/dirtyfrag.conf
sudo reboot

You do not need to do this to sit behind an Azure VPN Gateway — the gateway terminates IPsec, not your VM. WireGuard and OpenVPN are also unaffected: neither uses kernel ESP.

Common Use Cases

  • Jump host / bastion. One hop into a private subnet, with the diagnostic toolbox already on it and nothing but SSH exposed.
  • Break glass diagnostic VM. Spin one up in a subnet that is misbehaving and start measuring immediately, instead of installing tools on a machine whose network you are trying to debug.
  • Operations workstation. tmux or byobu plus mc, ripgrep, fzf and bat as a persistent working environment for a team.
  • Storage and filesystem work. lvm2, mdadm, parted, gdisk, nvme-cli and smartmontools for preparing, inspecting and moving volumes.
  • Baseline for a golden image. A known, documented starting set to build your own configuration on top of, with a manifest you can diff against.

Troubleshooting

Cannot connect via SSH

Confirm the network security group allows TCP 22 from your address, that the VM has a public IP, and that you are using the private key matching the public key you supplied at deployment. Password login is disabled by design.

sudo reboot is asking me to type the hostname

That is molly-guard, and it is intentional — see Step 9. Use sudo systemctl reboot for an un-prompted reboot, or remove the molly-guard package.

A tool is installed but the command is not found

Check the three name traps in Step 4 first: bat is batcat, fd-find is fdfind, and sysstat gives you sar and iostat rather than a sysstat command. perf is not installed — add linux-perf if you need it.

A service is installed but not running

That is the design. Every daemon the profile adds ships disabled. See Step 10 to enable one.

smartctl reports no devices

Expected on Azure. The virtual disk exposes no SMART data. smartmontools is on the image for the cases where you attach something that does, and for parity with bare metal runbooks.

nmap is not installed

Correct, and deliberate — see the exclusions in Step 4. Install it yourself with sudo apt install nmap if your work requires it.

An apt upgrade is asking which services to restart

That is needrestart, left at Ubuntu's own default. Answer the prompt, or run sudo NEEDRESTART_MODE=a apt upgrade to let it restart services automatically.

Package manager issues

sudo apt-get update
apt-cache policy | grep -E '(-security|-updates)' | head -5

A service is blocked and the logs mention AppArmor

AppArmor is enforcing, the Ubuntu default. Check sudo aa-status and the journal for DENIED entries before assuming a configuration problem.

Important Notes

  • The profile is the product. The 84 packages are named in the shipped manifest with their versions and components. Nothing is hidden and nothing comes from outside the Ubuntu archive.
  • Every daemon the profile adds is off; every daemon the base image ran is untouched. The split is recorded in the shipped unit disposition file.
  • rsync.service listens on TCP 873 if you enable it. Use rsync over SSH unless you specifically need the daemon.
  • No package on this image needs Ubuntu Pro to receive its security fixes. The image ships unattached.
  • Universe packages are community maintained. While Ubuntu 26.04 is in standard support their security updates are published to resolute-updates and resolute-security like any other package.
  • unattended-upgrades is enabled, so the machine continues to apply security updates after you deploy it.
  • The image ships no swap, which is the Azure requirement. Azure manages swap on the ephemeral resource disk.

Support

cloudimg provides 24/7 support for this image. For help with deployment, configuration or the package profile itself, contact support through the Azure Marketplace listing or visit www.cloudimg.co.uk.