How to Set Up Cost Alerts and Budgets in Azure
Cloud costs can spiral out of control without proactive monitoring—a forgotten test environment, misconfigured autoscaling, or surprise price increases can result in budget-breaking invoices. Azure budgets and alerts provide an early warning system, notifying stakeholders before small overages become financial disasters. This guide shows you how to implement comprehensive budget controls with automated alerts, action groups, and remediation workflows.
Overview
Azure budgets and cost alerts provide multiple monitoring capabilities:
- Budget thresholds: Set spending limits for subscriptions, resource groups, or services
- Proactive alerts: Email, SMS, webhook, and Teams notifications at configurable thresholds
- Forecasting: Predict month-end spend based on current usage trends
- Action groups: Trigger automated responses when budgets are exceeded
- Anomaly detection: Identify unusual spending patterns automatically
- Multi-scope budgets: Track costs across management groups, subscriptions, or resource groups
This guide covers four implementation levels:
- Basic budgets: Email alerts for subscription-level cost thresholds
- Advanced budgets: Multi-threshold alerts with action groups
- Automated remediation: Auto-shutdown or scaling when limits are exceeded
- Cost anomaly detection: ML-powered unusual spending detection
Prerequisites
Before you begin, ensure you have:
- Azure subscription with Cost Management Contributor role
- Email address for alert notifications
- Azure CLI (2.40.0+) OR Azure PowerShell module (7.0.0+) OR Azure Portal access
- Action Groups permissions for advanced automation (optional)
- Logic Apps or Azure Automation for remediation workflows (optional)
- Understanding of your typical Azure spending patterns
Method 1: Azure Portal (Basic Budgets)
Step 1: Create Your First Budget
- Navigate to Azure Portal → Cost Management + Billing
- Select Cost Management in the left navigation
- Click Budgets under the Cost Management section
- Click + Add to create a new budget
- Configure budget basics:
- Name: e.g., "Production Subscription Monthly Budget"
- Reset period: Monthly, Quarterly, or Annually
- Creation date: Start date for budget tracking
- Expiration date: When budget tracking ends (optional)
- Set budget scope:
- Scope: Select subscription, resource group, or management group
- Filters: Optionally filter by resource group, tags, meters, or services
- Click Next
Step 2: Set Budget Amount
- Choose amount type:
- Fixed amount: Set specific dollar amount (e.g., $10,000/month)
- Auto-adjusting: Budget adjusts based on previous period actuals
- Enter Budget amount: Your monthly/quarterly spending limit
- Review Cost forecast: See if current spending will exceed budget
- Click Next
Step 3: Configure Alert Conditions
- Click + Add alert condition
- Configure first alert (Warning):
- Alert condition name: "80% Budget Warning"
- Type: Actual (spent) or Forecasted (predicted)
- % of budget: 80
- Set alert recipients:
- Email recipients: [email protected], [email protected]
- Language: Select notification language
- Add additional thresholds:
- 90% threshold: "Budget Approaching"
- 100% threshold: "Budget Exceeded"
- 110% threshold: "Critical Overspend"
- Click Create to save the budget
Step 4: Verify Budget Creation
- Return to Budgets list
- Find your newly created budget
- Click on the budget name to view:
- Current spend vs. budget
- Forecast for end of period
- Alert history: When alerts fired
- Alert rules: Configured thresholds
Method 2: Azure CLI (Automated Budget Creation)
Create Basic Budget
#!/bin/bash
# Create Azure budget with CLI
SUBSCRIPTION_ID="your-subscription-id"
BUDGET_NAME="Monthly-Subscription-Budget"
BUDGET_AMOUNT=10000
START_DATE="2025-01-01"
# Create budget
az consumption budget create \
--budget-name $BUDGET_NAME \
--amount $BUDGET_AMOUNT \
--category Cost \
--time-grain Monthly \
--start-date $START_DATE \
--subscription $SUBSCRIPTION_ID
# Add alert at 80% threshold
az consumption budget create \
--budget-name $BUDGET_NAME \
--amount $BUDGET_AMOUNT \
--category Cost \
--time-grain Monthly \
--start-date $START_DATE \
--notifications '{
"warning80": {
"enabled": true,
"operator": "GreaterThan",
"threshold": 80,
"contactEmails": ["[email protected]"],
"contactRoles": ["Owner", "Contributor"],
"thresholdType": "Actual"
},
"critical100": {
"enabled": true,
"operator": "GreaterThan",
"threshold": 100,
"contactEmails": ["[email protected]", "[email protected]"],
"contactRoles": ["Owner"],
"thresholdType": "Actual"
}
}' \
--subscription $SUBSCRIPTION_ID
Create Filtered Budget (Resource Group Specific)
# Budget for specific resource group
RESOURCE_GROUP="rg-production"
az consumption budget create \
--budget-name "RG-Production-Budget" \
--amount 5000 \
--category Cost \
--time-grain Monthly \
--start-date $START_DATE \
--resource-group $RESOURCE_GROUP \
--notifications '{
"actual90": {
"enabled": true,
"operator": "GreaterThan",
"threshold": 90,
"contactEmails": ["[email protected]"],
"thresholdType": "Actual"
}
}'
Create Service-Specific Budget
# Budget filtered by service (e.g., only Virtual Machines)
az consumption budget create \
--budget-name "VM-Service-Budget" \
--amount 3000 \
--category Cost \
--time-grain Monthly \
--start-date $START_DATE \
--subscription $SUBSCRIPTION_ID \
--filter '{
"meters": [],
"tags": {},
"resourceGroups": [],
"resources": [],
"dimensions": {
"name": "ServiceName",
"operator": "In",
"values": ["Virtual Machines", "Virtual Machines Licenses"]
}
}' \
--notifications '{
"vm80": {
"enabled": true,
"operator": "GreaterThan",
"threshold": 80,
"contactEmails": ["[email protected]"],
"thresholdType": "Actual"
}
}'
Method 3: PowerShell (Advanced Budgets with Action Groups)
Step 1: Create Action Group for Notifications
# Create action group with multiple notification types
$actionGroupName = "CostAlert-ActionGroup"
$resourceGroup = "rg-cost-management"
# Email receivers
$emailReceivers = @(
[Microsoft.Azure.Commands.Insights.OutputClasses.PSEmailReceiver]@{
Name = "FinanceTeam"
EmailAddress = "[email protected]"
UseCommonAlertSchema = $true
},
[Microsoft.Azure.Commands.Insights.OutputClasses.PSEmailReceiver]@{
Name = "CFO"
EmailAddress = "[email protected]"
UseCommonAlertSchema = $true
}
)
# SMS receivers
$smsReceivers = @(
[Microsoft.Azure.Commands.Insights.OutputClasses.PSSmsReceiver]@{
Name = "OnCall"
CountryCode = "1"
PhoneNumber = "5551234567"
}
)
# Webhook receivers (for Teams/Slack integration)
$webhookReceivers = @(
[Microsoft.Azure.Commands.Insights.OutputClasses.PSWebhookReceiver]@{
Name = "TeamsChannel"
ServiceUri = "https://outlook.office.com/webhook/..."
UseCommonAlertSchema = $true
}
)
# Create action group
Set-AzActionGroup `
-Name $actionGroupName `
-ResourceGroupName $resourceGroup `
-ShortName "CostAlert" `
-EmailReceiver $emailReceivers `
-SmsReceiver $smsReceivers `
-WebhookReceiver $webhookReceivers
Step 2: Create Budget with Action Group Integration
# Comprehensive budget creation script
param(
[Parameter(Mandatory=$true)]
[string]$SubscriptionId,
[Parameter(Mandatory=$true)]
[decimal]$BudgetAmount,
[Parameter(Mandatory=$true)]
[string]$BudgetName,
[Parameter(Mandatory=$false)]
[string]$ActionGroupId
)
# Set context
Set-AzContext -SubscriptionId $SubscriptionId
# Calculate fiscal period
$startDate = Get-Date -Day 1 -Hour 0 -Minute 0 -Second 0
$endDate = $startDate.AddYears(1)
# Define multiple alert thresholds
$notifications = @{
"Notification1" = @{
Enabled = $true
Operator = "GreaterThan"
Threshold = 50
ContactEmails = @("[email protected]")
ContactRoles = @("Owner")
ContactGroups = @()
ThresholdType = "Forecasted" # Alert before you hit limit
}
"Notification2" = @{
Enabled = $true
Operator = "GreaterThan"
Threshold = 80
ContactEmails = @("[email protected]")
ContactRoles = @("Owner", "Contributor")
ContactGroups = if ($ActionGroupId) { @($ActionGroupId) } else { @() }
ThresholdType = "Actual"
}
"Notification3" = @{
Enabled = $true
Operator = "GreaterThan"
Threshold = 100
ContactEmails = @("[email protected]", "[email protected]")
ContactRoles = @("Owner")
ContactGroups = if ($ActionGroupId) { @($ActionGroupId) } else { @() }
ThresholdType = "Actual"
}
"Notification4" = @{
Enabled = $true
Operator = "GreaterThan"
Threshold = 120
ContactEmails = @("[email protected]")
ContactRoles = @("Owner")
ContactGroups = if ($ActionGroupId) { @($ActionGroupId) } else { @() }
ThresholdType = "Actual"
}
}
# Create budget
$budget = New-AzConsumptionBudget `
-Name $BudgetName `
-Amount $BudgetAmount `
-Category Cost `
-TimeGrain Monthly `
-StartDate $startDate `
-EndDate $endDate `
-Notification $notifications
Write-Host "Budget created successfully!" -ForegroundColor Green
Write-Host "Budget ID: $($budget.Id)"
Write-Host "Amount: $$BudgetAmount/month"
Write-Host "Alert thresholds: 50% (forecast), 80%, 100%, 120%"
Run the script:
.\New-AzureBudget.ps1 `
-SubscriptionId "your-sub-id" `
-BudgetAmount 10000 `
-BudgetName "Production-Monthly-Budget" `
-ActionGroupId "/subscriptions/{sub-id}/resourceGroups/rg-cost-management/providers/microsoft.insights/actionGroups/CostAlert-ActionGroup"
Step 3: Create Tag-Based Budget
# Budget for all resources with specific tag
$filter = @{
Tags = @{
Name = "Environment"
Operator = "In"
Values = @("Production")
}
}
New-AzConsumptionBudget `
-Name "Production-Tag-Budget" `
-Amount 15000 `
-Category Cost `
-TimeGrain Monthly `
-StartDate (Get-Date).Date `
-Filter $filter `
-Notification @{
"ProdAlert" = @{
Enabled = $true
Operator = "GreaterThan"
Threshold = 90
ContactEmails = @("[email protected]")
ThresholdType = "Actual"
}
}
Method 4: Automated Remediation with Logic Apps
Create Logic App for Auto-Shutdown
When budget reaches 100%, automatically shut down non-production VMs:
Logic App Workflow:
- HTTP trigger: Receives webhook from budget alert
- Parse JSON: Extract alert details
- Condition: Check if threshold is 100%
- List VMs: Get all VMs with tag "Environment=Development"
- For each VM: Stop the VM
- Send email: Notify team of shutdown action
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"triggers": {
"manual": {
"type": "Request",
"kind": "Http",
"inputs": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"SubscriptionId": {"type": "string"},
"BudgetName": {"type": "string"},
"NotificationThresholdAmount": {"type": "string"},
"BudgetThreshold": {"type": "number"}
}
}
}
}
}
}
},
"actions": {
"Check_Threshold": {
"type": "If",
"expression": {
"and": [
{
"greaterOrEquals": [
"@triggerBody()?['data']?['BudgetThreshold']",
100
]
}
]
},
"actions": {
"List_Dev_VMs": {
"type": "Http",
"inputs": {
"method": "GET",
"uri": "https://management.azure.com/subscriptions/@{triggerBody()?['data']?['SubscriptionId']}/resources?$filter=tagName eq 'Environment' and tagValue eq 'Development' and resourceType eq 'Microsoft.Compute/virtualMachines'&api-version=2021-04-01",
"authentication": {
"type": "ManagedServiceIdentity"
}
}
},
"For_Each_VM": {
"type": "Foreach",
"foreach": "@body('List_Dev_VMs')?['value']",
"actions": {
"Stop_VM": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "https://management.azure.com@{items('For_Each_VM')?['id']}/powerOff?api-version=2021-07-01",
"authentication": {
"type": "ManagedServiceIdentity"
}
}
}
}
},
"Send_Notification": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['office365']['connectionId']"
}
},
"method": "post",
"path": "/v2/Mail",
"body": {
"To": "[email protected]",
"Subject": "Budget Alert: Development VMs Shutdown",
"Body": "<p>Budget '@{triggerBody()?['data']?['BudgetName']}' has exceeded 100%. All development VMs have been automatically shut down to prevent further costs.</p><p>Current spend: $@{triggerBody()?['data']?['NotificationThresholdAmount']}</p>"
}
}
}
}
}
}
}
}
Deploy Logic App:
# Create Logic App
az logic workflow create \
--resource-group rg-cost-management \
--name la-budget-remediation \
--definition @logic-app-definition.json \
--location eastus
# Get webhook URL
LOGIC_APP_URL=$(az logic workflow show \
--resource-group rg-cost-management \
--name la-budget-remediation \
--query "accessEndpoint" -o tsv)
echo "Add this webhook URL to your budget alert: $LOGIC_APP_URL"
Method 5: Cost Anomaly Detection
Enable ML-powered anomaly detection for unusual spending:
# Enable cost anomaly alerts
az costmanagement alert create \
--name "Unusual-Spending-Alert" \
--scope "/subscriptions/your-subscription-id" \
--definition '{
"type": "Anomaly",
"status": "Active",
"details": {
"anomalyDetectionEnabled": true,
"contactEmails": ["[email protected]"],
"contactGroups": ["/subscriptions/{sub-id}/resourceGroups/rg-cost-management/providers/microsoft.insights/actionGroups/CostAlert-ActionGroup"],
"anomalyThreshold": 10
}
}'
Anomaly detection automatically:
- Learns normal spending patterns
- Identifies deviations from baseline (±10% configurable)
- Alerts on unexpected cost spikes
- Provides root cause analysis (which service/resource caused spike)
Advanced Budget Scenarios
Scenario 1: Department Chargebacks
Create separate budgets for each department using tags:
$departments = @("Engineering", "Marketing", "Sales", "Finance")
foreach ($dept in $departments) {
$filter = @{
Tags = @{
Name = "Department"
Operator = "In"
Values = @($dept)
}
}
New-AzConsumptionBudget `
-Name "Budget-$dept" `
-Amount 5000 `
-Category Cost `
-TimeGrain Monthly `
-StartDate (Get-Date).Date `
-Filter $filter `
-Notification @{
"DeptAlert" = @{
Enabled = $true
Operator = "GreaterThan"
Threshold = 90
ContactEmails = @("$($dept.ToLower())@company.com")
ThresholdType = "Actual"
}
}
Write-Host "Created budget for $dept department"
}
Scenario 2: Project-Based Budgets
Track costs for specific projects:
$projects = @{
"ProjectAlpha" = @{Amount = 25000; Owner = "[email protected]"}
"ProjectBeta" = @{Amount = 15000; Owner = "[email protected]"}
"ProjectGamma" = @{Amount = 30000; Owner = "[email protected]"}
}
foreach ($project in $projects.Keys) {
$config = $projects[$project]
$filter = @{
Tags = @{
Name = "Project"
Operator = "In"
Values = @($project)
}
}
New-AzConsumptionBudget `
-Name "Budget-$project" `
-Amount $config.Amount `
-Category Cost `
-TimeGrain Monthly `
-StartDate (Get-Date).Date `
-Filter $filter `
-Notification @{
"ProjectAlert80" = @{
Enabled = $true
Operator = "GreaterThan"
Threshold = 80
ContactEmails = @($config.Owner)
ThresholdType = "Actual"
}
"ProjectAlert100" = @{
Enabled = $true
Operator = "GreaterThan"
Threshold = 100
ContactEmails = @($config.Owner, "[email protected]")
ThresholdType = "Actual"
}
}
Write-Host "Created budget for $project (Owner: $($config.Owner))"
}
Scenario 3: Auto-Scaling Budget
Budget that scales based on previous month:
# Get last month's actual spend
$lastMonthStart = (Get-Date).AddMonths(-1).Date
$lastMonthEnd = (Get-Date).AddDays(-1).Date
$lastMonthCost = Get-AzConsumptionUsageDetail `
-StartDate $lastMonthStart `
-EndDate $lastMonthEnd `
| Measure-Object -Property PretaxCost -Sum `
| Select-Object -ExpandProperty Sum
# Set budget to 110% of last month (allow 10% growth)
$budgetAmount = [Math]::Ceiling($lastMonthCost * 1.1)
Write-Host "Last month cost: $$lastMonthCost"
Write-Host "New budget amount: $$budgetAmount (10% increase buffer)"
New-AzConsumptionBudget `
-Name "Auto-Scaling-Monthly-Budget" `
-Amount $budgetAmount `
-Category Cost `
-TimeGrain Monthly `
-StartDate (Get-Date).Date `
-Notification @{
"AutoScaledAlert" = @{
Enabled = $true
Operator = "GreaterThan"
Threshold = 100
ContactEmails = @("[email protected]")
ThresholdType = "Forecasted"
}
}
Best Practices
1. Set Multiple Threshold Levels
Budget Structure:
50%: Early warning (forecasted) → Email: Team
80%: Action required (actual) → Email: Manager + Teams channel
90%: Critical warning (actual) → Email: Director + SMS
100%: Budget exceeded (actual) → Email: Executive + Auto-remediation
120%: Emergency (actual) → Email: CFO + Escalation
2. Use Forecasted vs. Actual Alerts
- Forecasted (50-70%): Early warning before you hit limit
- Actual (80-100%+): You've actually spent this amount
# Combine forecasted and actual alerts
$notifications = @{
"Forecast70" = @{
Enabled = $true
Threshold = 70
ThresholdType = "Forecasted" # Predict you'll hit 70%
ContactEmails = @("[email protected]")
}
"Actual85" = @{
Enabled = $true
Threshold = 85
ThresholdType = "Actual" # You've actually spent 85%
ContactEmails = @("[email protected]")
}
}
3. Budget Naming Conventions
Use clear, descriptive names:
Good:
- Prod-Subscription-Monthly-10K
- RG-DataPlatform-Q1-2025
- Project-MobileApp-Development
Bad:
- Budget1
- Test
- MyBudget
4. Document Budget Ownership
# Add tags to budgets for governance
$budgetTags = @{
"Owner" = "[email protected]"
"Department" = "Finance"
"CostCenter" = "CC-1234"
"ApprovalRequired" = "true"
"ReviewFrequency" = "Monthly"
}
# Note: Budget tags are set at creation time or via API
5. Regular Budget Review
## Monthly Budget Review Checklist
- [ ] Review all budgets vs. actual spend
- [ ] Adjust budgets for next month based on trends
- [ ] Check if any budgets need threshold changes
- [ ] Verify alert recipients are still correct
- [ ] Review action group effectiveness
- [ ] Analyze top cost drivers
- [ ] Update project/department allocations
6. Alert Fatigue Prevention
# Don't over-alert - use appropriate thresholds
# Bad: Alerts every 10% (10%, 20%, 30%...)
# Good: Strategic alerts (50% forecast, 80%, 100%, 120%)
# Use action groups to route different alerts appropriately
# - Info alerts → Email only
# - Warning alerts → Email + Teams
# - Critical alerts → Email + SMS + Webhook
Troubleshooting
Issue: Not receiving budget alerts
Symptoms: Budget is exceeded but no emails received
Resolution:
# Verify budget configuration
az consumption budget show \
--budget-name "Your-Budget-Name" \
--subscription "your-subscription-id" \
--query "notifications"
# Check if emails are in spam/junk folder
# Whitelist: [email protected]
# Verify alert history
az consumption budget show \
--budget-name "Your-Budget-Name" \
--subscription "your-subscription-id" \
--query "currentSpend"
Issue: Action group not triggering
Symptoms: Budget alert fires but Logic App/webhook doesn't run
Resolution:
# Check action group configuration
az monitor action-group show \
--name "CostAlert-ActionGroup" \
--resource-group "rg-cost-management"
# Verify webhook URL is correct
# Test action group manually
az monitor action-group test-notifications create \
--action-group "CostAlert-ActionGroup" \
--resource-group "rg-cost-management" \
--alert-type "budget"
Issue: Forecasted alerts not accurate
Symptoms: Forecast shows 150% but actual spend is only 60%
Resolution:
**Causes:**
1. New subscription with limited historical data
2. Irregular spending patterns (batch jobs, one-time purchases)
3. Recent architecture changes
**Solutions:**
- Wait 3-6 months for forecast to stabilize
- Use actual-only alerts for new subscriptions
- Manually adjust budget amount based on known spending
- Review forecast accuracy monthly and adjust thresholds
Issue: Budget not tracking all resources
Symptoms: Spend higher than budget shows
Resolution:
# Check budget scope and filters
$budget = Get-AzConsumptionBudget -Name "Your-Budget-Name"
Write-Host "Budget Scope: $($budget.Scope)"
Write-Host "Filters: $($budget.Filter | ConvertTo-Json)"
# Verify cost data includes all resources
Get-AzConsumptionUsageDetail `
-StartDate (Get-Date).Date `
-EndDate (Get-Date).Date `
| Group-Object -Property ResourceGroup `
| Select-Object Name, Count
Issue: Cannot create budget - permission denied
Symptoms: "Authorization failed" when creating budget
Resolution:
# Verify you have Cost Management Contributor role
az role assignment list \
--assignee $(az ad signed-in-user show --query id -o tsv) \
--scope "/subscriptions/your-subscription-id" \
--query "[?roleDefinitionName=='Cost Management Contributor']"
# If missing, request role assignment
# Requires Owner or User Access Administrator
az role assignment create \
--role "Cost Management Contributor" \
--assignee "your-user-principal-name" \
--scope "/subscriptions/your-subscription-id"
Integration with Other Tools
Microsoft Teams Integration
{
"type": "MessageCard",
"@context": "https://schema.org/extensions",
"summary": "Azure Budget Alert",
"themeColor": "FF0000",
"title": "Budget Alert: @{triggerBody()?['data']?['BudgetName']}",
"sections": [
{
"activityTitle": "Budget threshold exceeded",
"facts": [
{
"name": "Subscription",
"value": "@{triggerBody()?['data']?['SubscriptionId']}"
},
{
"name": "Threshold",
"value": "@{triggerBody()?['data']?['BudgetThreshold']}%"
},
{
"name": "Current Spend",
"value": "$@{triggerBody()?['data']?['NotificationThresholdAmount']}"
}
]
}
],
"potentialAction": [
{
"@type": "OpenUri",
"name": "View in Azure Portal",
"targets": [
{
"os": "default",
"uri": "https://portal.azure.com/#blade/Microsoft_Azure_CostManagement/Menu/costanalysis"
}
]
}
]
}
PagerDuty Integration
# Add PagerDuty webhook to action group
az monitor action-group create \
--name "CostAlert-PagerDuty" \
--resource-group "rg-cost-management" \
--webhook-receiver \
name="PagerDuty" \
service-uri="https://events.pagerduty.com/integration/{integration-key}/enqueue" \
use-common-alert-schema=true
ServiceNow Integration
# Webhook to ServiceNow incident creation API
az monitor action-group create \
--name "CostAlert-ServiceNow" \
--resource-group "rg-cost-management" \
--webhook-receiver \
name="ServiceNow" \
service-uri="https://yourinstance.service-now.com/api/now/table/incident" \
use-common-alert-schema=false
Next Steps
Once budgets and alerts are configured:
- Analyze spending trends: Review Cost Analysis regularly
- Optimize resources: Identify unused or oversized resources
- Implement tagging strategy: Enable cost allocation tracking
- Set up exportsto: Configure data exports per "How to Monitor Cost Export Status and Data Freshness in Azure"
- Secure your data: Apply security controls from "How to Secure Cost Management Data in Azure Storage and Synapse"
Related Resources
Frequently Asked Questions
Find answers to common questions
Need Professional Help?
Our team of experts can help you implement and configure these solutions for your organization.