Skip to content

VM Backup

Overview

Azure VM backup is composed of three pieces: a Recovery Services Vault (covered in Backup Overview), a VM backup policy that defines schedule and retention, and a protected VM binding that attaches a specific VM to a policy. This page covers the policy and protection wiring, including how to exclude individual data disks from a VM's backup.

Module Structure

Module Azure Resource Purpose
backup_policy_vm azurerm_backup_policy_vm Schedule + retention rules; nested under a Recovery Services Vault
backup_protected_vm azurerm_backup_protected_vm Binds a windows_vms / linux_vms entry to a policy; optionally excludes specific data disks

Both modules read directly from the recovery_services_vault and windows_vms / linux_vms root variables — there are no separate top-level variables for policies or protected VMs.

Architecture

  1. Recovery Services Vault defines the protection container — see Backup Overview.
  2. backup_policy_vm entries are declared inside a vault under backup_policy_vm = { ... }. Each entry produces one azurerm_backup_policy_vm resource.
  3. VMs opt in to backup by setting backup_policy = "<vault_key>.<policy_key>" on the windows_vms / linux_vms entry. The dotted composite key uniquely identifies a policy across vaults — see Composite Keys.
  4. Disk-level exclusion is set per-disk on the VM entry with exclude_from_backup = true. The backup_protected_vm module collects the LUNs of excluded disks and passes them to exclude_disk_luns.

Usage

1. Declare a Policy Under a Vault

Policies live nested under the owning vault in the recovery_services_vault map:

recovery_services_vault = {
  epic = {
    resource_group    = "recoveryvault"
    storage_mode_type = "GeoRedundant"

    backup_policy_vm = {
      daily = {
        backup = {
          frequency = "Daily"
          time      = "03:00"
        }
        retention_daily = {
          count = "30"
        }
      }

      weeklyMonthly = {
        retention_monthly = {
          count    = "12"
          weekdays = ["Sunday"]
          weeks    = ["First"]
        }
      }
    }
  }
}

The policy key (daily, weeklyMonthly) combined with the vault key forms the composite reference that VMs use: "epic.daily", "epic.weeklyMonthly".

Note: Hourly schedules require policy_type = "V2" (the default) and a populated backup.hour_interval / backup.hour_duration. Weekly schedules cannot have absolute-day retention_monthly — use the relative weekdays + weeks form.

2. Attach a VM to a Policy

Set backup_policy on the VM entry to the composite <vault_key>.<policy_key>:

windows_vms = {
  hsw1 = {
    names          = ["hsw1"]
    size           = "Standard_D2as_v6"
    resource_group = "hsw"
    backup_policy  = "epic.daily"
    nics = {
      primary = {
        ip_configuration = [[{ subnet = "hsw.hsw" }]]
      }
    }
    boot_diagnostics = { storage_account = "diagsaphdev" }
  }
}

Omit backup_policy (or leave it null) to skip backup protection for that VM entirely.

3. Exclude Specific Disks from Backup

Mark individual data disks with exclude_from_backup = true. The OS disk is always included by the platform and cannot be excluded.

linux_vms = {
  sql1 = {
    names          = ["sql1"]
    size           = "Standard_E4as_v6"
    resource_group = "hsw"
    backup_policy  = "epic.daily"
    disks = {
      data = {
        lun          = 0
        disk_size_gb = "256"
      }
      sqldata = {
        lun                 = 1
        disk_size_gb        = "1024"
        exclude_from_backup = true
      }
      sqllog = {
        lun                 = 2
        disk_size_gb        = "256"
        exclude_from_backup = true
      }
    }
    nics = {
      primary = {
        ip_configuration = [[{ subnet = "hsw.hsw" }]]
      }
    }
  }
}

The module emits exclude_disk_luns = [1, 2] on the resulting azurerm_backup_protected_vm. When no disks are excluded the argument is set to null, leaving the protected-VM record unchanged from its previous state.

Tip: Use this for SQL data and log disks that already have a native SQL backup policy — backing them up at the VM level is redundant and consumes vault storage.

Note: exclude_from_backup is only meaningful when backup_policy is set on the same VM entry. If backup_policy is null, the VM is not protected at all and the flag is silently ignored.

Variable Reference

backup_policy_vm (nested under recovery_services_vault.<vault_key>)

Field Type Description Default
name string Override the resource name Prefix + key + suffix
policy_type string "V1" (legacy) or "V2" (required for hourly) "V2"
timezone string IANA-style Windows timezone (e.g. "Eastern Standard Time") Root var.timezone, else "UTC"
instant_restore_retention_days number Operational restore tier retention (V2 max 30) null
backup object Schedule (see below) {}
retention_daily object { count } — required when backup.frequency = "Daily" { count = "14" }
retention_weekly object { count, weekdays } { count = "4", weekdays = ["Sunday"] }
retention_monthly object Monthly retention (see below) — omit to disable {} (no monthly retention)

backup (schedule)

Field Type Description Default
frequency string "Daily", "Weekly", or "Hourly" "Weekly"
time string "HH:MM" start time (ignored for Hourly start logic but still required) "02:00"
hour_interval string Hours between backups — Hourly only null
hour_duration string Total hours the hourly window runs null
weekdays list(string) Days for Weekly frequency ["Sunday"]

retention_monthly

Use either the absolute form (days) or the relative form (weekdays + weeks). The two are mutually exclusive.

Field Type Description Default
count string Months to retain — omit the whole block to disable monthly retention null
weekdays list(string) Relative form: weekday names null
weeks list(string) Relative form: "First", "Second", "Third", "Fourth", "Last" null
days list(string) Absolute form: day-of-month ("1""28") null
include_last_days string Absolute form: include the last day of months that don't reach the listed day "false"

Weekly schedules must use the relative form. Hourly + monthly retention also requires the relative form.

VM-side fields (on windows_vms / linux_vms entries)

Field Type Description Default
backup_policy string Composite key <vault_key>.<policy_key> — protects the VM null (not protected)

Per-disk field (on disks.<disk_key>)

Field Type Description Default
exclude_from_backup bool Exclude this disk's LUN from the VM-level backup false

Composite Keys

Policies are identified by <vault_key>.<policy_key> because the same policy name can legitimately exist in different vaults (e.g. a weekly policy in both epic and epicIM). The composite key:

  • Is what VMs reference in backup_policy (e.g. "epic.daily").
  • Is the for_each key inside the backup_policy_vm module.
  • Is parsed by backup_protected_vm to look up the owning vault: split(".", vm.backup_policy)[0].

Renaming a vault or policy key renames the composite key, which causes Terraform to destroy and recreate the policy and any associated protected-VM bindings. Treat keys as load-bearing identifiers.

Naming Convention

Policies follow the standard {prefix}{key}{suffix} pattern using the backup_policy_vm slot in the prefix/suffix maps. The vault key is not part of the name unless you embed it in the policy key.

name_prefixes = {
  backup_policy_vm = "prod-"
}

name_suffixes = {
  backup_policy_vm = "-eastus2-bpol"
}

With the example above, policy key dailyprod-daily-eastus2-bpol.

Set name on the policy entry to bypass prefix/suffix composition entirely (useful when adopting a pre-existing policy or matching a fixed naming standard).

Backup policy names allow letters, numbers, and hyphens only. Periods are rejected by the Backup API even though the error message is misleading — keep them out of both the prefix/suffix and the policy key.