Skip to content

Comprehensive Guide: Secure Azure VM Backup Solution with Premium SSD RAID

This guide walks through setting up a hardened backup solution in Azure using VMs with Premium SSDs in RAID configuration, accessible only through a jumpbox, with mount scripts for both Linux and Windows servers.

1. Azure Infrastructure Design

Architecture Overview

Internet → Azure Firewall → Jumpbox VM → Backup Storage VMs with RAID
                                       ↘ Client VMs (Windows/Linux)

Network Architecture

  • Virtual Network: Primary network (10.0.0.0/16)
  • Subnets:
    • Jumpbox subnet (10.0.0.0/24)
    • Storage subnet (10.0.1.0/24)
    • Clients subnet (10.0.2.0/24)
  • NSGs: Restrict traffic between subnets
  • Azure Firewall: Control inbound/outbound traffic

2. Setting Up the Azure Environment

Create Resource Group

bash
az group create --name BackupSolutionRG --location eastus

Create Virtual Network and Subnets

bash
# Create VNet
az network vnet create \
  --resource-group BackupSolutionRG \
  --name BackupVNet \
  --address-prefix 10.0.0.0/16 \
  --subnet-name JumpboxSubnet \
  --subnet-prefix 10.0.0.0/24

# Create Storage Subnet
az network vnet subnet create \
  --resource-group BackupSolutionRG \
  --vnet-name BackupVNet \
  --name StorageSubnet \
  --address-prefix 10.0.1.0/24

# Create Clients Subnet
az network vnet subnet create \
  --resource-group BackupSolutionRG \
  --vnet-name BackupVNet \
  --name ClientsSubnet \
  --address-prefix 10.0.2.0/24

Configure Network Security Groups (NSGs)

bash
# Create NSG for Jumpbox
az network nsg create \
  --resource-group BackupSolutionRG \
  --name JumpboxNSG

# Allow SSH to jumpbox from your IP only
az network nsg rule create \
  --resource-group BackupSolutionRG \
  --nsg-name JumpboxNSG \
  --name AllowSSH \
  --priority 100 \
  --source-address-prefixes <YOUR-IP>/32 \
  --source-port-ranges '*' \
  --destination-address-prefixes '*' \
  --destination-port-ranges 22 \
  --access Allow \
  --protocol Tcp \
  --description "Allow SSH from admin IP"

# Create NSG for Storage subnet
az network nsg create \
  --resource-group BackupSolutionRG \
  --name StorageNSG

# Allow traffic only from jumpbox subnet to storage
az network nsg rule create \
  --resource-group BackupSolutionRG \
  --nsg-name StorageNSG \
  --name AllowFromJumpbox \
  --priority 100 \
  --source-address-prefixes 10.0.0.0/24 \
  --source-port-ranges '*' \
  --destination-address-prefixes '*' \
  --destination-port-ranges 22 3389 445 139 \
  --access Allow \
  --protocol '*' \
  --description "Allow from jumpbox subnet"

# Associate NSGs with subnets
az network vnet subnet update \
  --resource-group BackupSolutionRG \
  --vnet-name BackupVNet \
  --name JumpboxSubnet \
  --network-security-group JumpboxNSG

az network vnet subnet update \
  --resource-group BackupSolutionRG \
  --vnet-name BackupVNet \
  --name StorageSubnet \
  --network-security-group StorageNSG

3. Creating the Jumpbox VM

bash
# Create public IP for jumpbox
az network public-ip create \
  --resource-group BackupSolutionRG \
  --name JumpboxPublicIP \
  --allocation-method Static \
  --sku Standard

# Create jumpbox VM
az vm create \
  --resource-group BackupSolutionRG \
  --name JumpboxVM \
  --image UbuntuLTS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --vnet-name BackupVNet \
  --subnet JumpboxSubnet \
  --public-ip-address JumpboxPublicIP \
  --nsg "" \
  --size Standard_B2s

4. Creating the Storage VM with Premium SSD RAID

bash
# Create storage VM without public IP
az vm create \
  --resource-group BackupSolutionRG \
  --name StorageVM \
  --image UbuntuLTS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --vnet-name BackupVNet \
  --subnet StorageSubnet \
  --public-ip-address "" \
  --nsg "" \
  --size Standard_D4s_v3

# Add 4 Premium SSDs (P30 = 1TB each)
for i in {0..3}
do
  az vm disk attach \
    --resource-group BackupSolutionRG \
    --vm-name StorageVM \
    --name StorageDisk$i \
    --new \
    --size-gb 1024 \
    --sku Premium_LRS \
    --caching ReadWrite
done

5. Configuring RAID on the Storage VM

SSH to the Jumpbox first, then to the Storage VM:

bash
# From your local machine to jumpbox
ssh azureuser@<jumpbox-public-ip>

# From jumpbox to storage VM
ssh azureuser@<storage-vm-private-ip>

On the Storage VM, set up RAID 10:

bash
# Install mdadm utility
sudo apt update
sudo apt install -y mdadm

# Check available disks
lsblk

# Create RAID array (assuming disks are sdc, sdd, sde, sdf)
sudo mdadm --create --verbose /dev/md0 --level=10 --raid-devices=4 /dev/sdc /dev/sdd /dev/sde /dev/sdf

# Verify RAID creation
sudo mdadm --detail /dev/md0

# Create filesystem
sudo mkfs.ext4 -F /dev/md0

# Create mount point
sudo mkdir -p /mnt/raid

# Mount the RAID array
sudo mount /dev/md0 /mnt/raid

# Set permissions
sudo chmod 777 /mnt/raid

# Configure automatic mounting at boot
echo '/dev/md0 /mnt/raid ext4 defaults,nofail,discard 0 0' | sudo tee -a /etc/fstab

# Save RAID configuration
sudo mdadm --detail --scan | sudo tee -a /etc/mdadm/mdadm.conf
sudo update-initramfs -u

6. Hardening the VMs

Common Security Measures for Both VMs

bash
# Update system
sudo apt update && sudo apt upgrade -y

# Install security packages
sudo apt install -y unattended-upgrades fail2ban ufw

# Configure automatic updates
sudo dpkg-reconfigure -plow unattended-upgrades

# Configure fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

# Setup UFW (uncomplicated firewall)
# On jumpbox
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw enable

# On storage VM
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 10.0.0.0/24 to any port 22
sudo ufw allow from 10.0.2.0/24 to any port 445
sudo ufw allow from 10.0.2.0/24 to any port 139
sudo ufw enable

# Disable root SSH access
sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart sshd

# Set stronger SSH configuration
sudo bash -c 'cat > /etc/ssh/sshd_config.d/hardening.conf << EOF
Protocol 2
PermitRootLogin no
MaxAuthTries 3
MaxSessions 2
PasswordAuthentication no
PermitEmptyPasswords no
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
EOF'
sudo systemctl restart sshd

Additional Storage VM Hardening

bash
# Restrict RAID mount permissions as needed
sudo chmod 750 /mnt/raid
sudo chown azureuser:azureuser /mnt/raid

# Setup access control for shared folders
sudo apt install -y acl
sudo setfacl -m d:u::rwx,d:g::r-x,d:o:--- /mnt/raid

7. Configuring File Sharing Services

Configure Samba for Windows Clients

bash
# Install Samba
sudo apt install -y samba

# Configure Samba
sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.bak

sudo bash -c 'cat > /etc/samba/smb.conf << EOF
[global]
   workgroup = WORKGROUP
   server string = Backup Server
   security = user
   map to guest = never
   encrypt passwords = yes
   smb encrypt = required

   # Limit network interfaces
   interfaces = lo 10.0.1.0/24
   bind interfaces only = yes

   # Logging
   log file = /var/log/samba/log.%m
   max log size = 1000
   logging = file

   # Performance tuning
   socket options = TCP_NODELAY IPTOS_LOWDELAY
   read raw = yes
   write raw = yes
   strict locking = no

[BackupShare]
   path = /mnt/raid/backup
   valid users = @backup
   writable = yes
   browsable = yes
   create mask = 0660
   directory mask = 0770
EOF'

# Create backup directory and group
sudo mkdir -p /mnt/raid/backup
sudo groupadd backup
sudo useradd -m backupuser -G backup
sudo smbpasswd -a backupuser
sudo chown -R backupuser:backup /mnt/raid/backup
sudo chmod -R 770 /mnt/raid/backup

# Restart Samba
sudo systemctl restart smbd nmbd

Configure NFS for Linux Clients

bash
# Install NFS server
sudo apt install -y nfs-kernel-server

# Configure NFS exports
echo '/mnt/raid/backup 10.0.2.0/24(rw,sync,no_subtree_check,no_root_squash)' | sudo tee -a /etc/exports

# Create directories
sudo mkdir -p /mnt/raid/backup/linux
sudo chown -R nobody:nogroup /mnt/raid/backup/linux
sudo chmod -R 775 /mnt/raid/backup/linux

# Restart NFS server
sudo exportfs -a
sudo systemctl restart nfs-kernel-server

8. Client Mount Scripts

Windows Client Mount Script (PowerShell)

Create a PowerShell script (mount-backup.ps1):

powershell
# Save this as mount-backup.ps1
$BackupServer = "10.0.1.4" # Storage VM's private IP
$Username = "backupuser"
$Password = ConvertTo-SecureString "YourStrongPasswordHere" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($Username, $Password)

# Check if already connected
$Connected = Get-SmbConnection -ServerName $BackupServer -ErrorAction SilentlyContinue

if (-not $Connected) {
    # Establish SMB connection
    New-SmbMapping -LocalPath "Z:" -RemotePath "\\$BackupServer\BackupShare" -Credential $Credential -Persistent $true
    Write-Host "Backup share mounted successfully as Z: drive"
} else {
    Write-Host "Backup share is already connected"
}

# Schedule backup (example using robocopy)
$SourceDir = "C:\ImportantData"
$BackupDir = "Z:\$env:COMPUTERNAME"

# Create computer-specific folder if it doesn't exist
if (!(Test-Path $BackupDir)) {
    New-Item -Path $BackupDir -ItemType Directory
}

# Run robocopy with logging
robocopy $SourceDir $BackupDir /MIR /Z /W:5 /R:2 /MT:8 /LOG:"C:\Logs\backup_$(Get-Date -Format 'yyyyMMdd').log"

Linux Client Mount Script (Bash)

Create a bash script (mount-backup.sh):

bash
#!/bin/bash
# Save this as mount-backup.sh

BACKUP_SERVER="10.0.1.4"  # Storage VM's private IP
MOUNT_POINT="/mnt/backup"
BACKUP_DIR="/mnt/backup/$HOSTNAME"
SOURCE_DIR="/data"

# Ensure mount point exists
sudo mkdir -p $MOUNT_POINT

# Check if already mounted
if ! mount | grep -q "$MOUNT_POINT"; then
    echo "Mounting backup share..."
    sudo mount -t nfs $BACKUP_SERVER:/mnt/raid/backup/linux $MOUNT_POINT
else
    echo "Backup share is already mounted"
fi

# Create host-specific directory
mkdir -p $BACKUP_DIR

# Run backup with rsync
rsync -avz --delete --stats --log-file=/var/log/backup-$(date +%Y%m%d).log $SOURCE_DIR/ $BACKUP_DIR/

echo "Backup completed at $(date)"

Make the script executable:

bash
chmod +x mount-backup.sh

9. Setting Up Scheduled Backups

Windows Task Scheduler

  1. Open Task Scheduler on the Windows client
  2. Create a new task:
    • Name: "Backup to Azure Storage"
    • Trigger: Daily at your preferred time
    • Action: Start a program
    • Program/script: powershell.exe
    • Arguments: -ExecutionPolicy Bypass -File "C:\Scripts\mount-backup.ps1"

Linux Cron Job

bash
# Edit crontab
crontab -e

# Add this line to run daily at 1 AM
0 1 * * * /path/to/mount-backup.sh >> /var/log/backup-cron.log 2>&1

10. Monitoring and Maintenance

Setup Disk Health Monitoring

bash
# Install smartmontools
sudo apt install -y smartmontools

# Check disk health
sudo smartctl -a /dev/sdc
sudo smartctl -a /dev/sdd
sudo smartctl -a /dev/sde
sudo smartctl -a /dev/sdf

# Setup monitoring service
sudo bash -c 'cat > /etc/smartd.conf << EOF
/dev/sdc -a -o on -S on -s (S/../.././02|L/../../6/03) -m root
/dev/sdd -a -o on -S on -s (S/../.././02|L/../../6/03) -m root
/dev/sde -a -o on -S on -s (S/../.././02|L/../../6/03) -m root
/dev/sdf -a -o on -S on -s (S/../.././02|L/../../6/03) -m root
EOF'

sudo systemctl enable smartd
sudo systemctl start smartd

RAID Monitoring Script

Create /usr/local/bin/raid-check.sh:

bash
#!/bin/bash
# RAID monitoring script

ADMIN_EMAIL="admin@example.com"
RAID_DEVICE="/dev/md0"

# Check RAID status
raid_status=$(mdadm --detail $RAID_DEVICE | grep "State :" | awk '{print $3}')

if [[ "$raid_status" != "clean" ]] && [[ "$raid_status" != "active" ]]; then
    echo "RAID status is $raid_status on $(hostname) at $(date)" | mail -s "RAID ALERT: $HOSTNAME" $ADMIN_EMAIL
fi

# Check disk space
disk_usage=$(df -h $RAID_DEVICE | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$disk_usage" -gt 90 ]; then
    echo "Disk usage is at ${disk_usage}% on $(hostname) at $(date)" | mail -s "DISK SPACE ALERT: $HOSTNAME" $ADMIN_EMAIL
fi

Make it executable and add to crontab:

bash
sudo chmod +x /usr/local/bin/raid-check.sh

# Add to root's crontab
sudo crontab -e

# Add this line to run every hour
0 * * * * /usr/local/bin/raid-check.sh

11. Setting Up Backup Rotation Policy

Create /usr/local/bin/backup-rotate.sh:

bash
#!/bin/bash
# Backup rotation script

BACKUP_DIR="/mnt/raid/backup"
DAILY_KEEP=7
WEEKLY_KEEP=4
MONTHLY_KEEP=6

# Current date variables
DAY=$(date +%d)
WEEKDAY=$(date +%u)
MONTH=$(date +%m)

# Create timestamp directory
TIMESTAMP=$(date +%Y%m%d-%H%M)
mkdir -p "$BACKUP_DIR/snapshots/$TIMESTAMP"

# Use hardlinks for efficiency
cp -al "$BACKUP_DIR/latest/" "$BACKUP_DIR/snapshots/$TIMESTAMP/"

# Update 'latest' symlink
rm -f "$BACKUP_DIR/latest"
ln -s "snapshots/$TIMESTAMP" "$BACKUP_DIR/latest"

# Create daily, weekly, monthly snapshots
if [ "$WEEKDAY" = "7" ]; then  # Sunday
    # Create weekly snapshot
    cp -al "$BACKUP_DIR/snapshots/$TIMESTAMP/" "$BACKUP_DIR/weekly/week-$(date +%U)-$YEAR/"
fi

if [ "$DAY" = "01" ]; then
    # Create monthly snapshot
    cp -al "$BACKUP_DIR/snapshots/$TIMESTAMP/" "$BACKUP_DIR/monthly/month-$MONTH-$YEAR/"
fi

# Cleanup old snapshots
# ... (implement rotation logic based on DAILY_KEEP, WEEKLY_KEEP, MONTHLY_KEEP)
# This would search for and remove old directories based on your retention policy

Make it executable and add to crontab:

bash
sudo chmod +x /usr/local/bin/backup-rotate.sh
sudo crontab -e

# Add this line to run daily at 3 AM
0 3 * * * /usr/local/bin/backup-rotate.sh

12. Disaster Recovery Procedures

Document Recovery Process

Create /mnt/raid/README.md:

markdown
# Backup System Disaster Recovery Guide

## RAID Failure Scenarios

### If a single disk fails:

1. Identify the failed disk:

mdadm --detail /dev/md0


2. Mark the disk as failed:

mdadm /dev/md0 --fail /dev/sdX


3. Remove the failed disk:

mdadm /dev/md0 --remove /dev/sdX


4. Add a new disk:

mdadm /dev/md0 --add /dev/sdY


5. Monitor rebuild progress:

cat /proc/mdstat


### Complete RAID Failure:

1. Create new RAID array
2. Restore from offline backup (Azure Backup/Azure Storage)

## Network Access Failure:

1. Check NSG rules in Azure Portal
2. Verify jumpbox connectivity
3. Test internal network connectivity

13. Additional Azure Enhancements

Enable Azure Backup for VM-level Protection

bash
# Create Recovery Services Vault
az backup vault create \
  --resource-group BackupSolutionRG \
  --name BackupVault \
  --location eastus

# Enable VM backup
az backup protection enable-for-vm \
  --resource-group BackupSolutionRG \
  --vault-name BackupVault \
  --vm StorageVM \
  --policy-name DefaultPolicy

Enable Azure Disk Encryption

bash
# Create Key Vault
az keyvault create \
  --name BackupKeyVault \
  --resource-group BackupSolutionRG \
  --location eastus \
  --enabled-for-disk-encryption true

# Enable disk encryption on StorageVM
az vm encryption enable \
  --resource-group BackupSolutionRG \
  --name StorageVM \
  --disk-encryption-keyvault BackupKeyVault

# Monitor encryption progress
az vm encryption show --resource-group BackupSolutionRG --name StorageVM

Conclusion

This guide has walked through setting up a secure Azure VM-based backup solution with:

  • Proper network segmentation and a jumpbox for secure access
  • Premium SSD RAID configuration for performance and redundancy
  • Hardened VMs with tight security controls
  • File sharing services for both Windows and Linux clients
  • Mount scripts and scheduled backup jobs
  • Monitoring, maintenance, and disaster recovery procedures

This solution provides a robust backup system with control over your data while leveraging Azure's infrastructure reliability. Regular testing and updates are essential to maintain security and reliability over time.