Server

An AWS Tagging Strategy for Cost Allocation

By Prabath Thalangama· October 24, 2026· 3 min read
#aws#cost-optimization#tagging

Introduction

Cost allocation, automation, and access control on AWS all depend on consistent resource tags. Retrofitting tags across thousands of resources is miserable, so design the schema early and enforce it.

A minimal schema

Fewer, mandatory tags beat many optional ones. A common core:

Tag key Example Purpose
Environment prod / staging / dev filter spend and access by env
Team (or CostCenter) payments who owns and pays for it
Service (or Application) checkout-api roll up spend per service
ManagedBy terraform / console find click-ops resources
Owner payments-oncall@ who to ask

Decide case and allowed values up front (prod not Prod/production) — tag values are case-sensitive and Cost Explorer treats Prod and prod as different.

Activate cost allocation tags

Tags don't appear in cost reports until activated:

  • Billing console → Cost allocation tags → activate each user-defined tag key. Takes ~24h to start populating, and is not retroactive.
  • AWS-generated tags (aws:createdBy, aws:cloudformation:stack-name) can be activated too.

Enforce

  • Tag Policies (Organizations) — define allowed keys/values and report non-compliance. Note: tag policies report drift; they don't block by default.
  • SCPs — actually deny creation without required tags:
{
  "Effect": "Deny",
  "Action": ["ec2:RunInstances", "rds:CreateDBInstance"],
  "Resource": "*",
  "Condition": {
    "Null": { "aws:RequestTag/Team": "true" }
  }
}
  • IaC — set default tags at the provider level so every resource inherits them:
provider "aws" {
  default_tags {
    tags = {
      Environment = "prod"
      Team        = "payments"
      ManagedBy   = "terraform"
    }
  }
}
  • AWS Config rule required-tags — flags non-compliant existing resources; pair with an auto-remediation or a nagging report.

Costs tags can't reach

  • Untaggable resources — data transfer, some support charges, a few older service dimensions. These land in "untagged".
  • Shared costs — a NAT Gateway, an ALB, a shared EKS cluster serving many teams. Options: a Shared team tag and split by an agreed formula in your FinOps tooling, or split by usage (per-namespace with Kubecost/OpenCost for EKS).
  • Reserved Instances / Savings Plans discounts — apply at the billing level; amortised cost views in Cost Explorer attribute them back.

Reporting

  • Cost Explorer — group by tag, filter by tag, set up per-team reports. Good for exploration and monthly reviews.
  • Cost and Usage Report (CUR) — the raw hourly line-item data to S3, queried with Athena or loaded into QuickSight / a BI tool. The source of truth for chargeback.
  • Budgets — per-tag budget alerts (Team=payments monthly budget with alerts at 80/100/120%).

Verification and troubleshooting

# Find untagged resources
aws resourcegroupstaggingapi get-resources \
  --tag-filters Key=Team --query 'ResourceTagMappingList[?length(Tags)==`0`]'

# Bulk-tag from a list
aws resourcegroupstaggingapi tag-resources \
  --resource-arn-list arn:... --tags Team=payments,Environment=prod
  • Tag added but not in Cost Explorer — not activated as a cost allocation tag, or it's been < 24h, or the spend predates activation (not retroactive).
  • Big "untagged" or "No Team" bucket — resources created before enforcement, untaggable line items, or an automation creating resources without default tags. Backfill with Tag Editor / resourcegroupstaggingapi; add provider default_tags.
  • Same logical value split across rows — case/spelling drift (prod vs Production). Normalise with a tag policy and a one-time bulk re-tag.
  • SCP blocking legitimate creation — a service that tags resources after creation (some do) fails the RequestTag condition. Use aws:ResourceTag in a Config rule instead of an SCP for those, or exempt the service role.
  • CUR and Cost Explorer disagree — different rounding, amortisation settings, or time granularity. CUR line items are authoritative; match the Cost Explorer settings (amortised vs unblended).
  • Auto Scaling / spot instances untagged — set PropagateAtLaunch on ASG tags, or tags in the launch template.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.