Deploy Ubuntu Cloud-Init with Azure VMs

Complete guide for deploying secure, production-ready Ubuntu virtual machines on Microsoft Azure using ARM templates and Cloud-Init automation

Intermediate
45 minutes
Enterprise Security
Microsoft Azure
RFS - Senior Penetration Tester

Written by RFS

Senior Penetration Tester / Security Researcher

eJPT
eCPPTv2
CRTP
ADCS CESP
Architecture Overview
Understanding the Azure VM deployment architecture with ARM templates and Cloud-Init

Components Deployed:

  • Azure Virtual Machine (Ubuntu 22.04 LTS)
  • Virtual Network with Security Groups
  • Public IP with DNS Label
  • Managed Disk with Encryption
  • Network Interface with Security Rules

Security Features:

  • SSH Key Authentication Only
  • UFW Firewall Configuration
  • Fail2Ban Intrusion Prevention
  • Automatic Security Updates
  • Azure Monitor Integration
Prerequisites
Required tools and permissions before starting the deployment

Required Tools:

  • Azure CLI (version 2.0+)
  • PowerShell (optional)
  • Text editor (VS Code recommended)
  • SSH key pair

Azure Requirements:

  • Active Azure subscription
  • Contributor role or higher
  • Resource group permissions
  • VM creation permissions
1Setup Azure CLI and Authentication

First, install Azure CLI and authenticate with your Azure account.

Install Azure CLI:

# Ubuntu/Debian
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# macOS
brew install azure-cli

# Windows
winget install Microsoft.AzureCLI

Login to Azure:

# Login to Azure
az login

# Set your subscription (if you have multiple)
az account set --subscription "your-subscription-id"

# Verify your account
az account show
2Create Resource Group

Create a resource group to contain all your Azure resources.

# Create resource group
az group create \
  --name ubuntu-vm-rg \
  --location eastus

# Verify resource group creation
az group show --name ubuntu-vm-rg
3Create ARM Template

Create the main ARM template file that defines your Azure infrastructure.

azuredeploy.json:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "vmName": {
      "type": "string",
      "defaultValue": "ubuntu-vm",
      "metadata": {
        "description": "Name of the virtual machine"
      }
    },
    "adminUsername": {
      "type": "string",
      "defaultValue": "azureuser",
      "metadata": {
        "description": "Username for the Virtual Machine"
      }
    },
    "sshPublicKey": {
      "type": "string",
      "metadata": {
        "description": "SSH public key for authentication"
      }
    },
    "vmSize": {
      "type": "string",
      "defaultValue": "Standard_B2s",
      "allowedValues": [
        "Standard_B1s",
        "Standard_B2s",
        "Standard_D2s_v3",
        "Standard_D4s_v3"
      ],
      "metadata": {
        "description": "Size of the virtual machine"
      }
    }
  },
  "variables": {
    "vnetName": "[concat(parameters('vmName'), '-vnet')]",
    "subnetName": "default",
    "networkSecurityGroupName": "[concat(parameters('vmName'), '-nsg')]",
    "publicIPAddressName": "[concat(parameters('vmName'), '-pip')]",
    "networkInterfaceName": "[concat(parameters('vmName'), '-nic')]",
    "osDiskName": "[concat(parameters('vmName'), '-osdisk')]",
    "addressPrefix": "10.0.0.0/16",
    "subnetPrefix": "10.0.0.0/24",
    "cloudInitData": "# Complete Cloud-Init configuration embedded here"
  },
  "resources": [
    // Network Security Group, Virtual Network, Public IP, Network Interface, Virtual Machine
  ],
  "outputs": {
    "publicIPAddress": {
      "type": "string",
      "value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))).ipAddress]"
    },
    "fqdn": {
      "type": "string", 
      "value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))).dnsSettings.fqdn]"
    }
  }
}
4Create Parameters File

Create a parameters file to customize your deployment values.

azuredeploy.parameters.json:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "vmName": {
      "value": "my-ubuntu-vm"
    },
    "adminUsername": {
      "value": "azureuser"
    },
    "sshPublicKey": {
      "value": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC... # Your SSH public key here"
    },
    "vmSize": {
      "value": "Standard_B2s"
    },
    "ubuntuOSVersion": {
      "value": "22_04-lts-gen2"
    }
  }
}
5Deploy the ARM Template

Deploy your ARM template to create the Azure VM with Cloud-Init configuration.

Deploy using Azure CLI:

# Deploy the ARM template
az deployment group create \
  --resource-group ubuntu-vm-rg \
  --template-file azuredeploy.json \
  --parameters azuredeploy.parameters.json

# Monitor deployment status
az deployment group show \
  --resource-group ubuntu-vm-rg \
  --name azuredeploy \
  --query "properties.provisioningState"

Alternative: Deploy using PowerShell:

# Deploy using PowerShell
New-AzResourceGroupDeployment \
  -ResourceGroupName "ubuntu-vm-rg" \
  -TemplateFile "azuredeploy.json" \
  -TemplateParameterFile "azuredeploy.parameters.json"

# Get deployment outputs
(Get-AzResourceGroupDeployment -ResourceGroupName "ubuntu-vm-rg" -Name "azuredeploy").Outputs
6Verify Deployment and Connect

Verify that your VM is running and connect via SSH to confirm the Cloud-Init configuration.

Get VM Information:

# Get VM details including public IP
az vm show \
  --resource-group ubuntu-vm-rg \
  --name my-ubuntu-vm \
  --show-details \
  --query "{Name:name, PowerState:powerState, PublicIP:publicIps, FQDN:fqdns}"

# Get deployment outputs
az deployment group show \
  --resource-group ubuntu-vm-rg \
  --name azuredeploy \
  --query "properties.outputs"

Connect via SSH:

# Connect to your VM (replace with your FQDN)
ssh azureuser@your-vm-fqdn.eastus.cloudapp.azure.com

# Verify Cloud-Init completed successfully
sudo cloud-init status

# Check installed packages
dpkg -l | grep -E "(ufw|fail2ban|nginx)"

# Verify firewall status
sudo ufw status

# Check system logs
sudo journalctl -u cloud-init-local
sudo journalctl -u cloud-init
Security Verification
Verify that all security hardening measures are properly configured

Security Checks:

# Check firewall status
sudo ufw status verbose

# Verify SSH configuration
sudo sshd -T | grep -E "(passwordauth|permitroot|pubkey)"

# Check Fail2Ban status
sudo fail2ban-client status
sudo fail2ban-client status sshd

# Verify system hardening
sudo sysctl net.ipv4.conf.all.accept_redirects
sudo sysctl net.ipv4.conf.all.accept_source_route

Service Status:

# Check service status
systemctl status nginx
systemctl status fail2ban
systemctl status ufw

# Verify automatic updates
sudo systemctl status unattended-upgrades

# Check Azure Monitor agent
sudo systemctl status azuremonitoragent
Troubleshooting
Common issues and their solutions

Deployment Fails with Authentication Error

Ensure your SSH public key is correctly formatted and doesn't contain line breaks. Use cat ~/.ssh/id_rsa.pub to get the correct format.

Cloud-Init Fails to Complete

Check Cloud-Init logs: sudo cat /var/log/cloud-init-output.log and sudo journalctl -u cloud-init

Cannot Connect via SSH

Verify the Network Security Group allows SSH (port 22) and check that your SSH key matches the one specified in the parameters file.

ARM Template Validation Errors

Use az deployment group validate to check your template before deployment. Ensure all required parameters are provided.

Frequently Asked Questions

What are ARM templates in Azure?

ARM (Azure Resource Manager) templates are JSON files that define the infrastructure and configuration for your Azure resources. They enable Infrastructure as Code (IaC) practices for consistent, repeatable deployments.

How does Cloud-Init work with Azure VMs?

Cloud-Init is automatically executed when an Azure VM boots for the first time. It reads the configuration data and performs system initialization tasks like package installation, user creation, and security hardening.

What are the security benefits of this deployment method?

This method provides automated security hardening including firewall configuration, SSH hardening, intrusion detection, system monitoring, and compliance with security best practices from the moment the VM starts.

Can I customize the ARM template for my specific needs?

Yes, the ARM template is fully customizable. You can modify VM sizes, networking configurations, storage options, and Cloud-Init scripts to match your specific requirements.

Next Steps
Enhance your Azure infrastructure with additional features

Infrastructure Enhancements:

  • Set up Azure Load Balancer for high availability
  • Configure Azure Backup for VM protection
  • Implement Azure Key Vault for secrets management
  • Set up Azure Monitor and Log Analytics

Security Improvements:

  • Enable Azure Security Center
  • Configure Azure Sentinel for SIEM
  • Implement Azure Bastion for secure access
  • Set up Azure Policy for compliance

Ready to Deploy Your Azure Infrastructure?

Generate custom Cloud-Init templates with our professional tool

Download Complete Project Files

Get all ARM templates, parameters, and Cloud-Init configurations in a single download