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
- Recovery Services Vault defines the protection container — see Backup Overview.
backup_policy_vmentries are declared inside a vault underbackup_policy_vm = { ... }. Each entry produces oneazurerm_backup_policy_vmresource.- VMs opt in to backup by setting
backup_policy = "<vault_key>.<policy_key>"on thewindows_vms/linux_vmsentry. The dotted composite key uniquely identifies a policy across vaults — see Composite Keys. - Disk-level exclusion is set per-disk on the VM entry with
exclude_from_backup = true. Thebackup_protected_vmmodule collects the LUNs of excluded disks and passes them toexclude_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_eachkey inside thebackup_policy_vmmodule. - Is parsed by
backup_protected_vmto 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 daily → prod-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.
Related
- Backup Overview — Recovery Services Vault, diagnostic settings, naming
- Block Storage Overview — disk patterns used by
disks.<key>entries