Operating Systems Azure

Debian 13 on Azure User Guide

| Product: Debian 13 on Azure

Overview

This guide covers the deployment, verification and day one operation of Debian 13 (trixie) on Microsoft Azure, using the cloudimg virtual machine image from the Azure Marketplace.

Debian 13 is the current Debian stable release. Debian is one of the oldest and most widely deployed Linux distributions, assembled and maintained by the Debian Project, and it is the base that a large part of the Linux ecosystem is built on. Stable releases change slowly and predictably: the kernel, toolchain and library surface stay fixed for the life of the release, so software you build against it keeps working without a distribution upgrade partway through a project.

This image was captured from Debian 13.6, the point release current at build time, and every available update had been applied at the moment of capture.

What is included:

  • Debian 13 (trixie) at point release 13.6, fully patched at the moment the image was captured
  • Unattended security upgrades enabled and verified against the Debian security suite, so the instance keeps patching itself
  • Azure Linux Agent (walinuxagent) and cloud init for full Azure platform integration
  • Serial console access enabled for out of band recovery
  • AppArmor mandatory access control enabled
  • No baked password, no bundled SSH key, no shared SSH host key and no swap on the operating system disk
  • Generation 2 Hyper V virtual machine support
  • 24/7 cloudimg support

Platform: Microsoft Azure (Generation 2 Hyper V) Default user: azureuser Recommended size: Standard_B2s (2 vCPU, 4 GB RAM) or larger

Licensing

Debian is free software, but it is not covered by a single licence. Debian is a distribution: a collection of thousands of independently maintained packages, each carrying its own licence terms. You will find GPL, LGPL, MIT, BSD, Apache 2.0 and many others across the archive, and every package records its own terms in /usr/share/doc/<package>/copyright on the running system.

Everything in this image comes from Debian's main component, which is the part of the archive that meets the Debian Free Software Guidelines. The contrib, non-free and non-free-firmware components are not enabled on this image. That is a deliberate choice rather than an accident of the base: Debian's own desktop installer enables non-free-firmware so that laptops and workstations get proprietary driver blobs, but an Azure virtual machine does not need them, so this image stays entirely within the free archive. Step 4 below shows you how to confirm this for yourself, and if you do want contrib or non-free you can add the component yourself — the stock sources are left intact so that path works normally.

Running Debian requires no licence key, no paid subscription and no support contract.

Prerequisites

Before deploying this image, ensure you have:

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

Step 1: Deploy the virtual machine

Option A: Azure Portal

  1. Open the Azure Marketplace and search for "Debian 13 cloudimg"
  2. Select the image and click Create
  3. Configure the basics:
  4. Subscription: your Azure subscription
  5. Resource group: create a new one or select an existing one
  6. Virtual machine name: a name of your choosing
  7. Region: your preferred Azure region
  8. Size: Standard_B2s or larger
  9. Under Administrator account, choose SSH public key and paste your public key. The user name is azureuser
  10. Under Inbound port rules, allow SSH (port 22) from your own address range rather than from the whole internet
  11. Click Review + create, then Create

Option B: Azure CLI

az vm create \
  --resource-group myResourceGroup \
  --name my-debian-13-vm \
  --image cloudimg:debian-13:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

The image ships no password and no authorized key of its own. The only credential that will ever work is the SSH public key you supply here, which Azure injects at provisioning time.

Step 2: Connect over SSH

ssh azureuser@<vm-ip>

To find the public address of your virtual machine:

az vm show --subscription <sub-id> --resource-group myResourceGroup \
  --name my-debian-13-vm --show-details --query publicIps -o tsv

Step 3: Confirm which release you are running

Confirm the release, the point release, the kernel and that the Azure platform integration is healthy.

cat /etc/os-release
cat /etc/debian_version

Expected output:

PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
VERSION="13 (trixie)"
VERSION_CODENAME=trixie
DEBIAN_VERSION_FULL=13.6
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
13.6

/etc/debian_version is the one to watch. 13.6 is a released point release of Debian stable. A value such as trixie/sid would mean you were on testing or unstable rather than on stable Debian 13.

uname -r
uname -m
hostnamectl | grep -E "Operating System|Kernel|Virtualization|Architecture"

Expected output:

6.12.96+deb13-cloud-amd64
x86_64
  Virtualization: microsoft
Operating System: Debian GNU/Linux 13 (trixie)
          Kernel: Linux 6.12.96+deb13-cloud-amd64
    Architecture: x86-64

The -cloud-amd64 kernel flavour is Debian's cloud tuned kernel, built with the drivers a virtual machine actually needs and without the ones it does not. Confirm the Azure Linux Agent and cloud init are present:

systemctl is-active walinuxagent.service
systemctl is-enabled walinuxagent.service
cloud-init --version

Expected output:

active
enabled
/usr/bin/cloud-init 25.1.4

The Azure Linux Agent handles provisioning, extensions and the serial console. cloud init applies your host name and injects the SSH key you supplied at deployment, and it is also what generates a fresh, unique set of SSH host keys the first time your instance boots.

Terminal showing cat /etc/os-release reporting Debian GNU/Linux 13 trixie at point release 13.6, the 6.12.96+deb13-cloud-amd64 kernel, x86_64 architecture and the Azure Linux Agent reporting active and enabled

Step 4: Confirm the image is fully patched

The image is captured fully patched. Confirm nothing was held back, and confirm which parts of the archive are enabled.

sudo apt-get update
apt-mark showhold
sudo apt-get -s dist-upgrade | grep -c '^Inst ' || true

Expected output: apt-mark showhold prints nothing at all, and the simulated upgrade counts zero packages:

0

An empty apt-mark showhold matters as much as the zero. A held package is pinned at its current version forever and silently stops receiving security updates, so a "fully patched" image with a held package is not fully patched at all.

Confirm the security suite is enabled and see exactly which components you have:

apt-cache policy | grep -E 'l=Debian-Security|v=13'
grep -E 'Suites|Components' /etc/apt/sources.list.d/debian.sources

Expected output:

     release v=13,o=Debian,a=stable-security,n=trixie-security,l=Debian-Security,c=main,b=amd64
     release v=13.6,o=Debian,a=stable,n=trixie,l=Debian,c=main,b=amd64
Suites: trixie trixie-updates trixie-backports
Components: main
Suites: trixie-security
Components: main

l=Debian-Security confirms the security suite is wired in, so security updates reach this instance. Components: main confirms the licensing position described in the overview: only Debian's free archive is enabled, with no contrib, non-free or non-free-firmware.

Terminal showing a simulated dist upgrade reporting no held packages and zero packages upgradable, the Debian-Security suite enabled, the stable release at v=13.6, and the periodic unattended upgrade settings armed against the Debian-Security origin

Step 5: Review the secure by default posture

Nothing that could be guessed ships in this image. There is no default password, no bundled SSH key and no SSH host key carried over from the build.

passwd -S root
awk -F: '($2 ~ /^\$/){c++} END{print c+0}' /etc/shadow

Expected output:

root L 2026-07-26 0 99999 7 -1
0

L means the root account is locked, so there is no root password to guess. The second command counts every account carrying a password hash, and 0 means none do.

sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication|kbdinteractiveauthentication)'

Expected output:

permitrootlogin without-password
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no

This reads the effective configuration straight from the SSH daemon rather than from a file, so a directive that was commented out or overridden elsewhere cannot make the posture look better than it is. Password authentication is off, keyboard interactive authentication is off, and root cannot log in with a password.

Your SSH host keys are unique to your instance. They are removed from the image before capture and regenerated on first boot, so no two instances launched from this image share a host key:

sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

The fingerprint you see is yours alone. Check AppArmor, Debian's mandatory access control framework:

sudo aa-status --enabled && echo "apparmor enabled"
sudo aa-status --profiled

Expected output:

apparmor enabled
107

Terminal showing the root account locked, zero accounts carrying a password hash, password authentication disabled, public key authentication enabled and AppArmor enabled with 107 profiles loaded

Step 6: Check resources and install your software

df -h /
free -h
swapon --show
systemctl is-system-running
systemctl list-units --state=failed --no-legend | wc -l

Expected output on a Standard_B2s instance:

Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1        30G  858M   27G   4% /

               total        used        free      shared  buff/cache   available
Mem:           3.8Gi       491Mi       3.2Gi       488Ki       319Mi       3.3Gi

running
0

running with zero failed units means the system reached its target state cleanly. swapon --show printing nothing is deliberate: Azure manages swap on the ephemeral resource disk through the Azure Linux Agent rather than on the operating system disk, so no swap is baked into the image.

The complete Debian archive is available, so any package you need is one command away:

apt-cache policy nginx

Expected output:

nginx:
  Installed: (none)
  Candidate: 1.26.3-3+deb13u7

Install what you need in the usual way, for example sudo apt-get install -y nginx.

Terminal showing 30 GB of root disk with 27 GB available, 3.8 GB of memory, no swap devices, systemd running with zero failed units, and the nginx package available from the Debian archive

Step 7: Automatic security updates

The image ships with Debian's unattended upgrades mechanism enabled, so security updates are applied without you having to log in. Confirm the configuration:

cat /etc/apt/apt.conf.d/20auto-upgrades

Expected output:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

Confirm the service unit itself is enabled:

systemctl show -p UnitFileState --value unattended-upgrades.service

Expected output:

enabled

Most importantly, confirm which origins it will actually act on. A configured updater that matches no origin would apply nothing at all:

sudo unattended-upgrade --dry-run --debug 2>&1 | grep 'Allowed origins'

Expected output:

Allowed origins are: origin=Debian,codename=trixie,label=Debian, origin=Debian,codename=trixie,label=Debian-Security, origin=Debian,codename=trixie-security,label=Debian-Security

label=Debian-Security appearing in that list is the proof that Debian security updates are in scope for the automatic updater.

To see what it has been doing:

sudo tail -n 20 /var/log/unattended-upgrades/unattended-upgrades.log 2>/dev/null || echo "no upgrade run recorded yet on this instance"

If you prefer to control patching yourself, disable it with sudo systemctl disable --now unattended-upgrades.service. Bear in mind that you then own the patching schedule.

Step 8: Restrict SSH access

Your Azure Network Security Group is the first line of defence and should already limit port 22 to your own address range. The image ships with no host firewall active so that it never interferes with your own network design.

Debian does not install ufw by default. If you want a host firewall as well, install it and configure it in this order so that you never lock yourself out. Allow SSH first, then enable the firewall.

sudo apt-get install -y ufw
sudo ufw allow from <your-mgmt-cidr> to any port 22 proto tcp
sudo ufw --force enable
sudo ufw status verbose

Replace the placeholder with the address range you administer the instance from. Debian's own default firewall framework is nftables, which is already installed if you would rather write rules directly.

Step 9: Set the time zone and host name

The instance runs in UTC with time synchronisation active, which is the right default for servers.

timedatectl | head -6

Expected output:

               Local time: Sun 2026-07-26 23:12:04 UTC
           Universal time: Sun 2026-07-26 23:12:04 UTC
                 RTC time: Sun 2026-07-26 23:12:03
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active

To change the time zone or the host name:

sudo timedatectl set-timezone Europe/London
sudo hostnamectl set-hostname web01.<your-domain>

Step 10: Serial console recovery

If a change ever leaves the instance unreachable over SSH, the Azure serial console gives you an out of band route in. It is enabled on this image, which you can confirm from the kernel command line:

grep -o 'console=ttyS0[^ ]*' /proc/cmdline

Expected output:

console=ttyS0,115200

Open it from the Azure Portal under Help then Serial console on your virtual machine. Because the root account is locked, sign in there with your azureuser account.

Troubleshooting

SSH refuses the connection. Password authentication is disabled by design. Connect with the private key that matches the public key you supplied at deployment, and confirm the Network Security Group allows port 22 from your address.

SSH warns that the host key has changed. That is expected the first time you connect to a newly created instance that reuses an address you have connected to before. Every instance generates its own unique host keys on first boot, so remove the stale entry with ssh-keygen -R <vm-ip> and reconnect.

A package fails to install. Refresh the package lists first with sudo apt-get update. The image ships with the archive metadata cleared so that every instance fetches a current index.

A package you expect is not in the archive. Check whether it lives in contrib or non-free, which are not enabled on this image by design. Add the component you need to /etc/apt/sources.list.d/debian.sources and run sudo apt-get update.

Updates appear after deployment. That is expected and healthy. The image was fully patched at capture, and Debian has published updates since. Unattended upgrades will apply security updates automatically, or you can apply everything immediately with sudo apt-get update && sudo apt-get dist-upgrade.

Support

This image is supported 24/7 by cloudimg. Contact support@cloudimg.co.uk with your subscription identifier and the virtual machine name.

Debian is a registered trademark of Software in the Public Interest, Inc. cloudimg is not affiliated with, endorsed by or sponsored by the Debian Project or Software in the Public Interest, Inc. Debian is free software; running it requires no licence key and no paid subscription. Debian is a distribution of many separately licensed packages rather than a single licensed product, and each package's terms are recorded in /usr/share/doc/<package>/copyright on the running instance.