Skip to main content

Cloud Cost Management

Cloud cost management encompasses the activities required to track, analyse, and optimise cloud expenditure. This task establishes the foundational controls and ongoing practices that prevent uncontrolled spending while ensuring resources remain available for mission delivery. Organisations that complete these procedures gain visibility into where money goes, receive alerts before budgets exhaust, and systematically reduce waste.

Prerequisites

RequirementDetail
AccessBilling administrator or cost management role in cloud platform
PermissionsCreate and modify budgets, access billing data, modify resource tags
ToolsCloud provider CLI installed and authenticated; spreadsheet for analysis
InformationChart of accounts or cost centre structure; list of projects and responsible owners
BaselineMinimum 30 days of cloud usage data for meaningful analysis
TimeInitial setup: 4-6 hours; monthly review: 2-3 hours

Verify CLI access to billing data before proceeding. The examples use AWS, Azure, and Google Cloud CLIs; adapt commands to your platform.

For AWS:

Terminal window
aws ce get-cost-and-usage \
--time-period Start=2024-11-01,End=2024-11-30 \
--granularity MONTHLY \
--metrics UnblendedCost

Expected output confirms access:

{
"ResultsByTime": [
{
"TimePeriod": {"Start": "2024-11-01", "End": "2024-12-01"},
"Total": {"UnblendedCost": {"Amount": "1234.56", "Unit": "USD"}}
}
]
}

For Azure:

Terminal window
az consumption usage list \
--start-date 2024-11-01 \
--end-date 2024-11-30 \
--query "[0:1]"

For Google Cloud:

Terminal window
gcloud billing accounts list
bq query --use_legacy_sql=false \
'SELECT SUM(cost) FROM `project.dataset.gcp_billing_export_v1_*`
WHERE DATE(_PARTITIONTIME) >= "2024-11-01"'

If commands fail with permission errors, request the following roles:

PlatformRequired role
AWSBilling IAM policy or ViewBilling permission
AzureCost Management Reader at subscription or management group level
Google CloudBilling Account Viewer and BigQuery access to billing export

Procedure

Establishing cost visibility

Cost visibility requires two components: a tagging taxonomy that categorises resources, and cost allocation rules that assign shared costs to appropriate owners.

  1. Define your tagging taxonomy based on how your organisation needs to slice costs. A minimal taxonomy requires three tags; larger organisations benefit from additional dimensions.

    Create a tagging policy document specifying required tags:

tagging-policy.yaml
required_tags:
- key: CostCentre
description: Financial cost centre code
valid_values: ["HQ-IT", "PROG-WASH", "PROG-HEALTH", "PROG-EDUC", "FIELD-KE", "FIELD-UG"]
- key: Project
description: Project or grant code
valid_values_pattern: "^[A-Z]{2,4}-[0-9]{4,6}$"
- key: Environment
description: Deployment environment
valid_values: ["production", "staging", "development", "sandbox"]
recommended_tags:
- key: Owner
description: Email of responsible person
- key: DataClassification
description: Data sensitivity level
valid_values: ["public", "internal", "confidential", "restricted"]
  1. Apply tags to existing resources. Start with the highest-cost resources to maximise visibility quickly.

    For AWS, list untagged resources consuming significant cost:

Terminal window
aws ce get-cost-and-usage \
--time-period Start=2024-11-01,End=2024-11-30 \
--granularity MONTHLY \
--metrics UnblendedCost \
--group-by Type=TAG,Key=CostCentre \
--filter '{"Not": {"Tags": {"Key": "CostCentre", "Values": []}}}' \
| jq '.ResultsByTime[0].Groups[] | select(.Keys[0] == "CostCentre$")'

Resources appearing under the empty tag key lack the CostCentre tag. Tag them using:

Terminal window
aws ec2 create-tags \
--resources i-0abc123def456 \
--tags Key=CostCentre,Value=PROG-WASH Key=Project,Value=WS-2024-001 Key=Environment,Value=production
  1. Enable cost allocation tags in your billing console. Tagging resources does not automatically make tags available in cost reports; you must activate them.

    For AWS, navigate to Billing → Cost Allocation Tags → Activate the tags defined in your taxonomy. Tags become available in cost reports within 24 hours.

    For Azure, no activation is required; tags appear automatically in Cost Management after resources are tagged.

    For Google Cloud, configure billing export to BigQuery if not already enabled:

Terminal window
gcloud beta billing accounts describe BILLING_ACCOUNT_ID \
--format='value(name)'
# Enable detailed usage cost export
gcloud billing budgets create \
--billing-account=BILLING_ACCOUNT_ID \
--display-name="Cost Export" \
--budget-amount=1000000USD
  1. Configure cost allocation rules for shared resources. Some costs cannot be attributed through tagging because they apply across multiple projects or the entire organisation.

    Identify shared cost categories:

    Shared cost typeAllocation methodExample
    Support plansProportional to total spendAWS Enterprise Support
    Reserved capacityTo consuming workloadsReserved Instances
    Data transferBy source workloadCross-region transfer
    Shared servicesFixed percentage or per-userIdentity platform, monitoring

    For AWS, create a cost category to allocate support costs proportionally:

Terminal window
aws ce create-cost-category \
--name "SupportCostAllocation" \
--rule-version "CostCategoryExpression.v1" \
--rules '[
{
"Value": "Allocated",
"Rule": {
"Tags": {
"Key": "CostCentre",
"Values": ["HQ-IT", "PROG-WASH", "PROG-HEALTH"],
"MatchOptions": ["EQUALS"]
}
}
}
]'
  1. Verify tagging coverage by generating a compliance report. Target 90% tagging coverage within 60 days of implementing the taxonomy.
Terminal window
# AWS: Calculate tagging coverage percentage
TOTAL=$(aws ce get-cost-and-usage \
--time-period Start=2024-11-01,End=2024-11-30 \
--granularity MONTHLY \
--metrics UnblendedCost \
| jq -r '.ResultsByTime[0].Total.UnblendedCost.Amount')
TAGGED=$(aws ce get-cost-and-usage \
--time-period Start=2024-11-01,End=2024-11-30 \
--granularity MONTHLY \
--metrics UnblendedCost \
--filter '{"Tags": {"Key": "CostCentre", "Values": [], "MatchOptions": ["ABSENT"]}}' \
| jq -r '.ResultsByTime[0].Total.UnblendedCost.Amount')
COVERAGE=$(echo "scale=2; (1 - $TAGGED / $TOTAL) * 100" | bc)
echo "Tagging coverage: ${COVERAGE}%"

Expected output for a well-tagged environment:

Tagging coverage: 94.3%

Configuring budgets and alerts

Budgets establish spending thresholds that trigger alerts before costs exceed planned amounts. Configure budgets at multiple levels: organisation-wide to protect against runaway spending, and per-project to give owners visibility into their consumption.

  1. Calculate baseline monthly spend from historical data. Use the previous three months to establish a representative baseline, excluding any anomalous months.
Terminal window
aws ce get-cost-and-usage \
--time-period Start=2024-09-01,End=2024-12-01 \
--granularity MONTHLY \
--metrics UnblendedCost \
| jq '.ResultsByTime[] | {month: .TimePeriod.Start, cost: .Total.UnblendedCost.Amount}'

Sample output:

{"month": "2024-09-01", "cost": "4521.34"}
{"month": "2024-10-01", "cost": "4789.12"}
{"month": "2024-11-01", "cost": "5102.87"}

Average: $4,804.44. Set initial budget at 110% of average: $5,285.

  1. Create an organisation-wide budget with alerts at 50%, 80%, and 100% thresholds.

    For AWS:

Terminal window
aws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "Monthly-Organisation-Budget",
"BudgetLimit": {"Amount": "5285", "Unit": "USD"},
"BudgetType": "COST",
"TimeUnit": "MONTHLY",
"CostFilters": {}
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 50,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{"SubscriptionType": "EMAIL", "Address": "it-finance@example.org"}
]
},
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{"SubscriptionType": "EMAIL", "Address": "it-finance@example.org"},
{"SubscriptionType": "EMAIL", "Address": "it-manager@example.org"}
]
},
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 100,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{"SubscriptionType": "EMAIL", "Address": "it-director@example.org"},
{"SubscriptionType": "SNS", "Address": "arn:aws:sns:eu-west-1:123456789012:budget-alerts"}
]
}
]'

For Azure:

Terminal window
az consumption budget create \
--budget-name "Monthly-Organisation-Budget" \
--amount 5285 \
--category Cost \
--time-grain Monthly \
--start-date 2024-12-01 \
--end-date 2025-12-01 \
--resource-group "" \
--notifications '{
"Actual_GreaterThan_50_Percent": {
"enabled": true,
"operator": "GreaterThan",
"threshold": 50,
"contactEmails": ["it-finance@example.org"]
}
}'
  1. Create per-project budgets filtered by the Project tag. Calculate each project’s budget from its historical spend or its approved grant budget.

    For a project with $1,200 monthly budget:

Terminal window
aws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "Project-WS-2024-001",
"BudgetLimit": {"Amount": "1200", "Unit": "USD"},
"BudgetType": "COST",
"TimeUnit": "MONTHLY",
"CostFilters": {
"TagKeyValue": ["user:Project$WS-2024-001"]
}
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{"SubscriptionType": "EMAIL", "Address": "wash-pm@example.org"}
]
}
]'
  1. Configure forecasted spend alerts to receive warnings before month-end based on current trajectory.
Terminal window
aws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "Monthly-Forecast-Alert",
"BudgetLimit": {"Amount": "5285", "Unit": "USD"},
"BudgetType": "COST",
"TimeUnit": "MONTHLY"
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "FORECASTED",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 100,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{"SubscriptionType": "EMAIL", "Address": "it-finance@example.org"}
]
}
]'
  1. Test alert delivery by temporarily lowering a budget threshold below current spend.
Terminal window
aws budgets update-budget \
--account-id 123456789012 \
--new-budget '{
"BudgetName": "Monthly-Organisation-Budget",
"BudgetLimit": {"Amount": "100", "Unit": "USD"},
"BudgetType": "COST",
"TimeUnit": "MONTHLY"
}'

Verify email receipt within 15 minutes, then restore the correct budget amount.

Analysing spend patterns

Regular analysis identifies trends, anomalies, and optimisation opportunities. Conduct monthly reviews using the following procedure.

  1. Generate a month-over-month comparison by service to identify significant changes.
Terminal window
aws ce get-cost-and-usage \
--time-period Start=2024-10-01,End=2024-12-01 \
--granularity MONTHLY \
--metrics UnblendedCost \
--group-by Type=DIMENSION,Key=SERVICE \
| jq -r '.ResultsByTime[] as $period |
$period.Groups[] |
[$period.TimePeriod.Start, .Keys[0], .Metrics.UnblendedCost.Amount] |
@csv' \
| sort -t',' -k2,2 -k1,1

Sample output identifying EC2 growth:

"2024-10-01","Amazon Elastic Compute Cloud","1823.45"
"2024-11-01","Amazon Elastic Compute Cloud","2341.67"
"2024-10-01","Amazon Simple Storage Service","456.78"
"2024-11-01","Amazon Simple Storage Service","478.23"

EC2 increased 28% ($518.22) while S3 increased 4.7% ($21.45). The EC2 increase warrants investigation.

  1. Drill into the high-growth service by resource to find the cause.
Terminal window
aws ce get-cost-and-usage \
--time-period Start=2024-11-01,End=2024-12-01 \
--granularity MONTHLY \
--metrics UnblendedCost \
--filter '{
"Dimensions": {
"Key": "SERVICE",
"Values": ["Amazon Elastic Compute Cloud"]
}
}' \
--group-by Type=DIMENSION,Key=RESOURCE_ID \
| jq -r '.ResultsByTime[0].Groups | sort_by(.Metrics.UnblendedCost.Amount | tonumber) | reverse | .[0:10][] | [.Keys[0], .Metrics.UnblendedCost.Amount] | @csv'
  1. Identify unused or underutilised resources using utilisation metrics.

    For EC2, query CloudWatch for CPU utilisation below 10% over 14 days:

Terminal window
for instance in $(aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --output text); do
avg_cpu=$(aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=$instance \
--start-time $(date -d '14 days ago' --iso-8601=seconds) \
--end-time $(date --iso-8601=seconds) \
--period 86400 \
--statistics Average \
--query 'Datapoints[].Average | [0]' \
--output text 2>/dev/null)
if [[ $(echo "$avg_cpu < 10" | bc -l 2>/dev/null) -eq 1 ]]; then
echo "Low utilisation: $instance (${avg_cpu}% avg CPU)"
fi
done
  1. Check for unattached storage volumes incurring ongoing charges.
Terminal window
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[].{ID:VolumeId,Size:Size,Created:CreateTime}' \
--output table

Volumes in available state are not attached to any instance but continue to incur storage charges.

  1. Analyse data transfer costs, which often grow unexpectedly.
Terminal window
aws ce get-cost-and-usage \
--time-period Start=2024-11-01,End=2024-12-01 \
--granularity MONTHLY \
--metrics UnblendedCost \
--filter '{
"Dimensions": {
"Key": "USAGE_TYPE_GROUP",
"Values": ["EC2: Data Transfer - Internet (Out)", "EC2: Data Transfer - Region to Region (Out)"]
}
}' \
--group-by Type=DIMENSION,Key=USAGE_TYPE
  1. Document findings in a monthly cost review report. Include:
    • Total spend versus budget
    • Month-over-month change by cost centre
    • Top 5 cost increases with explanations
    • Identified optimisation opportunities
    • Actions taken since last review

Implementing optimisations

Act on analysis findings to reduce waste and improve cost efficiency. The following optimisations are listed in order of typical impact and implementation complexity.

  1. Terminate or stop unused resources identified in analysis. For development and staging environments, implement schedules to stop instances outside working hours.

    Create a Lambda function to stop tagged instances at 19:00 and start them at 07:00:

stop_instances.py
import boto3
def lambda_handler(event, context):
ec2 = boto3.client('ec2')
# Find instances tagged for scheduling
response = ec2.describe_instances(
Filters=[
{'Name': 'tag:Schedule', 'Values': ['office-hours']},
{'Name': 'instance-state-name', 'Values': ['running']}
]
)
instance_ids = []
for reservation in response['Reservations']:
for instance in reservation['Instances']:
instance_ids.append(instance['InstanceId'])
if instance_ids:
ec2.stop_instances(InstanceIds=instance_ids)
return f"Stopped {len(instance_ids)} instances"
return "No instances to stop"

Schedule with EventBridge:

Terminal window
aws events put-rule \
--name "StopDevInstances" \
--schedule-expression "cron(0 19 ? * MON-FRI *)" \
--state ENABLED
aws events put-rule \
--name "StartDevInstances" \
--schedule-expression "cron(0 7 ? * MON-FRI *)" \
--state ENABLED

Potential savings: A t3.large instance running 24/7 costs approximately $60/month; running only during office hours (12 hours × 22 days) reduces this to $22/month, a 63% reduction.

  1. Right-size over-provisioned instances based on utilisation data.

    Use AWS Compute Optimizer or equivalent to get recommendations:

Terminal window
aws compute-optimizer get-ec2-instance-recommendations \
--instance-arns arn:aws:ec2:eu-west-1:123456789012:instance/i-0abc123def456 \
--query 'instanceRecommendations[].{
Current: currentInstanceType,
Recommended: recommendationOptions[0].instanceType,
MonthlySavings: recommendationOptions[0].projectedUtilizationMetrics
}'

Sample recommendation:

{
"Current": "m5.xlarge",
"Recommended": "m5.large",
"MonthlySavings": "Estimated $70/month (50% reduction)"
}

Before resizing, verify the application can function with reduced resources in staging.

  1. Delete unattached volumes and obsolete snapshots.
Terminal window
# List unattached volumes older than 30 days
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[?CreateTime<=`2024-11-01`].{ID:VolumeId,Size:Size,Cost:"$0.10/GB/month"}' \
--output table
# Delete after confirmation (no undo)
aws ec2 delete-volume --volume-id vol-0abc123def456

For snapshots, identify those not associated with any AMI:

Terminal window
aws ec2 describe-snapshots \
--owner-ids self \
--query 'Snapshots[?StartTime<=`2024-06-01`].{ID:SnapshotId,Size:VolumeSize,Created:StartTime}'
  1. Implement S3 lifecycle policies to transition infrequently accessed data to cheaper storage tiers.
Terminal window
aws s3api put-bucket-lifecycle-configuration \
--bucket project-data-bucket \
--lifecycle-configuration '{
"Rules": [
{
"ID": "ArchiveOldData",
"Status": "Enabled",
"Filter": {"Prefix": "reports/"},
"Transitions": [
{"Days": 90, "StorageClass": "STANDARD_IA"},
{"Days": 180, "StorageClass": "GLACIER"}
],
"Expiration": {"Days": 2555}
}
]
}'

Storage class cost comparison for 1 TB:

Storage classMonthly costAccess cost
S3 Standard$23.00$0.0004/request
S3 Standard-IA$12.50$0.01/request
S3 Glacier$4.00$0.03/request + retrieval time
S3 Glacier Deep Archive$1.00$0.02/request + 12hr retrieval
  1. Purchase reserved capacity for stable workloads. Only commit to reservations for resources that have run continuously for 6 or more months and will continue for at least another 12 months.

    Calculate potential savings:

Terminal window
aws ce get-reservation-purchase-recommendation \
--service "Amazon Elastic Compute Cloud - Compute" \
--term-in-years ONE_YEAR \
--payment-option NO_UPFRONT \
--query 'Recommendations[].RecommendationDetails[].{
Instance: InstanceDetails.EC2InstanceDetails.InstanceType,
MonthlySavings: EstimatedMonthlySavingsAmount,
BreakEven: EstimatedBreakEvenInMonths
}'

Sample recommendation:

{
"Instance": "t3.large",
"MonthlySavings": "24.50",
"BreakEven": "7 months"
}

A 1-year no-upfront reserved instance for t3.large saves approximately 30% compared to on-demand pricing.

  1. Enable S3 Intelligent-Tiering for buckets with unpredictable access patterns rather than manually managing lifecycle transitions.
Terminal window
aws s3api put-bucket-intelligent-tiering-configuration \
--bucket project-data-bucket \
--id "AutoTiering" \
--intelligent-tiering-configuration '{
"Id": "AutoTiering",
"Status": "Enabled",
"Tierings": [
{"Days": 90, "AccessTier": "ARCHIVE_ACCESS"},
{"Days": 180, "AccessTier": "DEEP_ARCHIVE_ACCESS"}
]
}'

Establishing ongoing governance

Sustaining cost efficiency requires regular review cadence and clear accountability.

  1. Schedule monthly cost review meetings with budget owners. The agenda should cover:

    • Actual versus budgeted spend by cost centre
    • Significant variances (over 10%) requiring explanation
    • Forecast for remainder of budget period
    • Optimisation opportunities and actions
    • New projects requiring budget allocation
  2. Create a cost dashboard for self-service visibility. Use cloud-native tools or export to BI platforms.

    For AWS Cost Explorer saved reports:

Terminal window
aws ce create-cost-category \
--name "Programme-View" \
--rule-version "CostCategoryExpression.v1" \
--rules '[
{"Value": "WASH", "Rule": {"Tags": {"Key": "CostCentre", "Values": ["PROG-WASH"]}}},
{"Value": "Health", "Rule": {"Tags": {"Key": "CostCentre", "Values": ["PROG-HEALTH"]}}},
{"Value": "Education", "Rule": {"Tags": {"Key": "CostCentre", "Values": ["PROG-EDUC"]}}},
{"Value": "Shared", "Rule": {"Tags": {"Key": "CostCentre", "Values": ["HQ-IT"]}}}
]'
  1. Implement anomaly detection to alert on unexpected spend patterns.
Terminal window
aws ce create-anomaly-monitor \
--anomaly-monitor '{
"MonitorName": "ServiceAnomaly",
"MonitorType": "DIMENSIONAL",
"MonitorDimension": "SERVICE"
}'
aws ce create-anomaly-subscription \
--anomaly-subscription '{
"SubscriptionName": "CostAnomalyAlert",
"MonitorArnList": ["arn:aws:ce::123456789012:anomalymonitor/abc123"],
"Subscribers": [
{"Type": "EMAIL", "Address": "it-finance@example.org"}
],
"Threshold": 100,
"Frequency": "DAILY"
}'

This alerts when any service cost deviates by more than $100 from predicted spend.

  1. Document your cost allocation model for grant compliance. Many funders require demonstration that cloud costs are allocated fairly to funded activities.

    Create a cost allocation report template:

Cost Allocation Report
Period: [Month Year]
Grant: [Grant Code]
Direct Costs (tagged to grant):
- Compute: $X,XXX.XX
- Storage: $XXX.XX
- Data transfer: $XX.XX
Allocated Shared Costs:
- Platform support (X% of $Y,YYY): $ZZZ.ZZ
- Security services (X% of $Y,YYY): $ZZZ.ZZ
Total Cloud Costs for Grant: $X,XXX.XX
Allocation Methodology: [Description of how percentages derived]

Verification

Confirm cost management controls function correctly:

Tagging coverage: Run the coverage calculation from step 5 of “Establishing cost visibility.” Coverage should exceed 90%.

Terminal window
# Expected output
Tagging coverage: 94.3%

Budget alerts: Verify budgets exist and have notifications configured.

Terminal window
aws budgets describe-budgets --account-id 123456789012 \
--query 'Budgets[].{Name:BudgetName,Limit:BudgetLimit.Amount,Alerts:length(notifications)}' \
--output table

Expected output shows budgets with alert counts:

-------------------------------------------
| DescribeBudgets |
+---------+---------------------------+---+
| Alerts | Name |Limit|
+---------+---------------------------+---+
| 3 | Monthly-Organisation-Budget| 5285|
| 1 | Project-WS-2024-001 | 1200|
| 1 | Monthly-Forecast-Alert | 5285|
+---------+---------------------------+---+

Anomaly detection: Confirm monitor and subscription are active.

Terminal window
aws ce get-anomaly-monitors \
--query 'AnomalyMonitors[].{Name:MonitorName,Status:MonitorSpecification}'

Scheduled actions: Verify instance scheduling Lambda functions execute.

Terminal window
aws lambda list-functions \
--query 'Functions[?contains(FunctionName, `Instance`)].{Name:FunctionName,Last:LastModified}'

Check CloudWatch Logs for recent executions:

Terminal window
aws logs filter-log-events \
--log-group-name /aws/lambda/StopDevInstances \
--start-time $(date -d '24 hours ago' +%s000) \
--query 'events[].message'

Troubleshooting

SymptomCauseResolution
Tags not appearing in Cost ExplorerCost allocation tags not activatedNavigate to Billing → Cost Allocation Tags → Activate required tags; wait 24 hours
Budget alerts not receivedSNS topic missing permissions or email not confirmedCheck SNS topic policy allows budgets.amazonaws.com; verify email subscription confirmed
Anomaly detection triggers excessive alertsThreshold set too low for variable workloadsIncrease threshold; use percentage rather than absolute amount for variable services
Cost allocation shows large “untagged” categoryResources created without tags; inherited resources from before taxonomyRun compliance report to identify untagged resources; implement preventive controls
Reserved instance savings not materialisingReservations purchased for wrong instance family or regionVerify reservation attributes match running instance attributes exactly
Scheduled Lambda not stopping instancesInstances missing Schedule tag; Lambda lacking ec2:StopInstances permissionVerify tag key matches filter exactly; check IAM role attached to Lambda
Cost data missing for recent daysCost and Usage Report has 24-48 hour delayWait for data propagation; use real-time billing dashboard for current day
Cannot create budget with tag filterTag key format incorrect; tag not activatedUse format user:TagKey$TagValue for AWS; verify tag activated in Cost Allocation Tags
Compute Optimizer shows no recommendationsInsufficient data (requires 14 days) or instance types not supportedWait for data collection; Compute Optimizer covers EC2, EBS, Lambda, and ECS on Fargate
S3 lifecycle policy not transitioning objectsObjects smaller than 128KB; policy filter excluding objectsIntelligent-Tiering has 128KB minimum; verify prefix filter matches object paths
Cost anomaly detected but cause unclearAnomaly detection identifies deviation but not root causeUse Cost Explorer to drill into the flagged service by resource, usage type, and tag
Chargeback report totals do not match invoiceShared costs not allocated; credits/refunds not includedVerify all cost categories sum to invoice total; include credits in allocation methodology

Common untagged resource sources:

Resources frequently created without tags include: Auto Scaling launched instances (configure launch template with tags), CloudFormation stacks missing tag propagation (set PropagateAtLaunch: true), and resources created via console without tag entry (implement Service Control Policies requiring tags).

Prevent future untagged resources with an SCP:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RequireCostCentreTag",
"Effect": "Deny",
"Action": [
"ec2:RunInstances",
"ec2:CreateVolume"
],
"Resource": "*",
"Condition": {
"Null": {
"aws:RequestTag/CostCentre": "true"
}
}
}
]
}

See also