AWS Builder Center
AWS Tagging Best Practices: Find and Enforce Tags

AWS Tagging Best Practices: Find and Enforce Tags

Learn how to find, tag and enforce AWS tags with Tag Editor, SCPs, permissions boundaries, tag policies, AWS Config, and Terraform default tags.

DevOps Engineer
Picture this: the bill jumps overnight. Which of your 400 resources caused it, which team owns it, and is it safe to delete? If your answer is "let me check CloudTrail", you don't have a billing problem, you have a tagging problem.
I audited my organization's AWS accounts for tagging compliance. This is the plan that came out of it: find what is untagged, tag it, block new untagged resources, and catch the rest. It works for accounts inside an Organization and for standalone ones, and it includes copy-paste policies.
The four layers, and which enforcement method fits each account type


The short version

  • Find and tag what already exists in minutes with Tag Editor, which is free.
  • Enforce tags on new resources: an SCP for member accounts, a permissions boundary for standalone accounts.
  • Catch the rest with the AWS Config  required-tags  rule and an alert.
  • Result: every resource has an Environment and an Owner, so cost and cleanup questions have answers.

Why tags go missing

Tagging by convention fails because nothing stops an untagged launch and nobody audits afterwards. Soon cost reports show "untagged" and cleanup becomes guesswork.
You cannot secure, optimize or delete what you cannot attribute. In this plan every resource must carry two tags, and optional ones make reporting richer:
Tag
Required?
Example values
Purpose
 Environment 
Yes
 prod ,  staging ,  dev 
Separates risk and cost by lifecycle stage
 Owner 
Yes
 payments-team , a ticket queue
The accountable team, not whoever clicked create
 CostCenter 
Optional
 cc-1042 
Maps spend to a budget
 Application 
Optional
 checkout-api 
Groups resources into one workload
 CreatedBy 
Optional
an IAM role or user name
Who technically created it, which is not the same as who owns it
Tag keys are case-sensitive, so Environment and  environment  count as two different tags. Pick one spelling and one set of values ( prod , never sometimes  Production ), and write them down. Keep the list short: every extra required tag is one more reason for a deployment to be denied.

Check first: are your accounts already tagged by code?

First check whether each account is deployed with infrastructure as code (IaC). Hand-added tags drift or get overwritten on the next deploy, and a deny policy can break deployments that don't pass tags. Check each account separately.
Tool
How to tell it is in use
What to do if it is
CloudFormation
Stacks are listed in the CloudFormation console (check every region). Their resources carry the system tag  aws:cloudformation:stack-name , which Tag Editor can filter on.
Put  Environment  and  Owner  in the stack tags, which propagate to supported resources. Add Hooks or Guard checks to the pipeline.
CDK
A  CDKToolkit  stack means the account was bootstrapped for CDK, which deploys through CloudFormation.
Tag the whole app with  Tags.of(app).add(...) .
Terraform
No tag marks it. Look for an S3 bucket or DynamoDB table holding state, CI/CD deploy roles (a GitHub Actions role, for example), and CloudTrail events whose user agent contains "Terraform".
Set  default_tags  in the AWS provider block so every resource gets both tags.
None (console or CLI only)
No stacks, no  CDKToolkit , no deploy roles.
Follow the manual steps below as written.
Three rules follow from what you find:
  • Fix IaC-managed resources in the code, not in Tag Editor. Bulk-tagging them by hand just creates drift.
  • Test one deployment before enforcing. Run a normal pipeline deploy in a non-production account with the policy attached, and confirm the tags arrive with the create request. A deploy that tags resources after creating them will be denied.
  • Mixed accounts are normal. Tag IaC resources in code and everything else with Tag Editor. The Config rule in step 4 checks both.

The approach: four layers

The plan has four steps, and only step 3 changes with the account type:
  1. Find untagged resources with Resource Groups & Tag Editor.
  1. Tag them in bulk with placeholder values.
  1. Enforce tags on new resources at creation time.
  1. Safety net with an AWS Config rule for everything enforcement can't reach.
Step
Member account (in an Organization)
Standalone account
Find untagged
Tag Editor per account, or Config aggregator
Tag Editor
Tag them
Tag Editor bulk "Manage tags"
Tag Editor bulk "Manage tags"
Enforce on creation
SCP attached from the management account
IAM permissions boundary
Safety net
Config  required-tags , deployed with StackSets
Config  required-tags 
SCPs never restrict the management account, so keep workloads out of it. If you must run resources there, use a permissions boundary as in a standalone account.

Step 1 and 2: find and tag what already exists

Resource Groups & Tag Editor is free, needs no setup and works per account:
  1. In the console, search for Resource Groups & Tag Editor and open Tag Editor.
  1. Under Regions choose All regions. Resources hide in regions you forgot about.
  1. Under Resource types choose All supported resource types, then Search resources.
  1. Sort by the tag column to find rows missing Environment or Owner.
  1. Select the rows, choose Manage tags, add the tags and Review and apply.
Tip: tag with placeholders first, such as  Environment = needs-review  and  Owner = unassigned , then ask owners to correct them. Guessing owners creates bad data. With five or more accounts, use a Config aggregator for one combined view.
Shortcuts: Resource Explorer's  tag:none  query lists resources with no tags at all, and the Tagging API ( aws resourcegroupstaggingapi tag-resources ) can apply the placeholders to a list of ARNs.

Step 3: enforce tags on new resources

The idea is a deny policy: a create request without the required tag fails before the resource exists. Use one statement per tag. Conditions inside one statement are ANDed, so a single combined statement would only deny when both tags are missing.
JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyCreateWithoutEnvironmentTag",
"Effect": "Deny",
"Action": [
"ec2:RunInstances", "ec2:CreateVolume", "ec2:CreateSnapshot",
"ec2:CreateSecurityGroup", "ec2:CreateVpc", "ec2:CreateNatGateway",
"rds:CreateDBInstance", "rds:CreateDBCluster",
"lambda:CreateFunction", "dynamodb:CreateTable",
"sns:CreateTopic", "sqs:CreateQueue", "cloudformation:CreateStack"
],
"Resource": "*",
"Condition": { "Null": { "aws:RequestTag/Environment": "true" } }
},
{
"Sid": "DenyCreateWithoutOwnerTag",
"Effect": "Deny",
"Action": [
"ec2:RunInstances", "ec2:CreateVolume", "ec2:CreateSnapshot",
"ec2:CreateSecurityGroup", "ec2:CreateVpc", "ec2:CreateNatGateway",
"rds:CreateDBInstance", "rds:CreateDBCluster",
"lambda:CreateFunction", "dynamodb:CreateTable",
"sns:CreateTopic", "sqs:CreateQueue", "cloudformation:CreateStack"
],
"Resource": "*",
"Condition": { "Null": { "aws:RequestTag/Owner": "true" } }
}
]
}
My full version lists about 30 actions; this one is shortened. Not every action supports  aws:RequestTag , so spot-check the rarer ones in the IAM Service Authorization Reference and drop any that don't behave.
Member accounts: use an SCP. In the management account, create the policy in AWS Organizations (Policies, then Service control policies) and attach it to the OU that holds your member accounts. Then launch an untagged EC2 instance in a member account: it should fail with an explicit deny from a service control policy. If it succeeds, check the OU attachment.
If a launch is denied, the request did not carry both tags. Pass them at creation, for example with the CLI:
Bash
aws ec2 run-instances \
--image-id ami-xxxxxxxx --instance-type t3.micro \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Environment,Value=dev},{Key=Owner,Value=my-team}]'
In CloudFormation, put the tags in the stack's tags. Adding tags after the resource exists is too late for this policy.
Standalone accounts: use a permissions boundary. With no Organization there is nothing to attach an SCP to, so scope the same deny to IAM identities. A boundary needs an allow statement too, so add this at the top of the Statement array and keep the two denies:
JSON
{ "Sid": "AllowAllWithinBoundary", "Effect": "Allow", "Action": "*", "Resource": "*" }
Create it as a customer-managed policy and set it as the Permissions boundary on every role or user that creates resources, including CI/CD deploy roles. It only restricts identities it is attached to.
Cautions: an SCP only affects future calls, so steps 1 and 2 come first. It can also break automation that tags after creating, so roll it out gradually: sandbox OU, then non-production, then the rest. To protect tags once they exist, also deny  CreateTags  and  DeleteTags  calls that touch the Environment and Owner keys (use the  aws:TagKeys  condition) for everyone except an admin role.

Step 4: the safety net with AWS Config

Creation-time policies can't cover everything: some actions don't support tag conditions, and SCPs are capped at 5,120 characters. S3 is a good example. Buckets can now be tagged at creation (it needs  s3:TagResource  as well as  s3:CreateBucket ), but the docs don't say how  aws:RequestTag  behaves for  CreateBucket . Test it in a sandbox and keep S3 under Config too.
The AWS Config managed rule  required-tags  is the detective layer. It checks up to six tag keys (and optionally their values) on 30 resource types, such as EC2, EBS, RDS, DynamoDB, load balancers and S3. It runs when a resource changes and only detects; it never blocks.
  1. Make sure AWS Config is recording in the account.
  1. Console, then AWS Config, Rules, Add rule, and pick the managed rule  required-tags .
  1. Set parameters  tag1Key = Environment  and  tag2Key = Owner .
  1. Config marks every resource missing a tag as Non-compliant.
  1. Optionally attach auto-remediation. The AWS-managed runbook AWS-SetRequiredTags does not work with this rule, so you need a custom SSM Automation document that applies the placeholder values.
In an Organization, deploy the rule to every current and future member account with CloudFormation StackSets from the management account.
Tag policies keep the values clean. An Organizations tag policy defines the allowed keys and values, such as Environment must be  prod ,  staging  or  dev . It does not flag resources that have no tag at all, so use it together with the Config rule, not instead of it.
Config is regional, so enable the recorder and rule in every region you use, or view them through an aggregator. Then send the "Config Rules Compliance Change" event to EventBridge and route NON_COMPLIANT results to SNS or team chat so someone actually sees them.
Optional: auto-tag the creator. An EventBridge rule on CloudTrail create events (such as  RunInstances ) can trigger a small Lambda that writes the caller to a  CreatedBy  tag. Keep Owner as the accountable team and don't copy one into the other.

Limits and gaps I found

This approach is a strong baseline, not a complete answer. These are the gaps I would plan for:
Gap
Why it happens
What to do
Management account is not covered by SCPs
SCPs never restrict the management account
Use the permissions boundary there, and keep workloads out of that account
Auto Scaling and other service roles skip the SCP
SCPs don't affect service-linked roles, so instances launched by an Auto Scaling group are not denied
Set tags on the launch template or group, and let Config catch the rest
A boundary only protects identities that carry it
A new role without the boundary, or removing the boundary, bypasses the deny
Require the boundary when creating roles and users, and deny changes to it
Tags are checked only at creation
Someone can delete or edit a tag afterwards
Deny tag removal for required keys, and let Config flag the change
Only the tag key is checked, not its value
The  Null  condition tests presence
Add allowed values to the Config rule, or use Organizations tag policies
Config covers 30 resource types
Lambda, ECS, EKS, SNS, SQS, KMS, Secrets Manager and EFS are not on the managed rule's list
Audit those with Tag Editor, and add custom rules if they matter
Config only detects
The managed rule can't block, and  AWS-SetRequiredTags  doesn't work with it
Write a custom SSM runbook, or fix the tags in your templates
Cost reports stay empty
Tags must be activated as cost allocation tags in Billing, and they can take up to 24 hours to appear
Activate  Environment  and  Owner  on day one
A deny can break pipelines
The effective SCP can't be viewed from the member account, and automation may not send tags
Test in a sandbox OU first, never attach to the root untested, and keep a break-glass exemption

Roll it out in phases and measure it

Governance is a migration, not a switch. A sequence that keeps people unblocked:
  1. Audit: run Tag Editor or  tag:none  and record the percentage of resources with both required tags. That number is your baseline.
  1. Backfill: bulk-tag with placeholders, and fix IaC-managed resources in code.
  1. Detect: turn on the Config rule and the alert, and watch for a week without blocking anyone.
  1. Enforce: attach the deny policy to a sandbox OU, then widen it.
  1. Maintain: review the compliance percentage monthly and keep a documented exception path for the few resources that truly cannot be tagged.
Track three numbers: percent of resources compliant, number of denied create calls per week (a spike means a pipeline needs fixing), and share of spend visible by Environment or Owner in Cost Explorer.

What it costs

Honest note on cost: Tag Editor and SCPs are free. AWS Config is pay-per-use, with two parts: configuration items recorded, and rule evaluations. I modelled 100 resources that each change about 3 times a month, using the prices I found during my R&D (check the current AWS Config pricing page before you budget):
Recording mode
Config items per month
Item cost
Rule evaluations
Evaluation cost
Total per month
Continuous
300
$0.90 (at $0.003 each)
300
$0.30 (at $0.001 each)
$1.20
Periodic (daily)
3,000
$36.00 (at $0.012 each)
3,000
$3.00
$39.00
Continuous recording is cheaper unless your resources change dozens of times a month, because periodic items are priced about 10 times higher. At this scale you stay inside the first evaluation tier of 100,000 per month.

Final checklist

  • Required tags and allowed values are written down (Environment, Owner).
  • Each account is checked for IaC, and tags are fixed in code where it is used.
  • All regions are audited, and existing resources carry placeholder tags.
  • A deny policy (SCP or permissions boundary) is tested in a sandbox before wider rollout.
  • The Config  required-tags  rule is on in every region you use, with alerts to a person.
  • Cost allocation tags are activated in Billing.
  • The compliance percentage is measured monthly, with an exception process for the rest.

Over to you

If you manage tagging today, which tag do you enforce first, and what do you do with legacy resources nobody claims? I would like to hear whether you use SCPs, boundaries, Config rules, or tag policies for this, and where you hit limits.

More from me

Have feedback or a topic request? Leave a comment, I read every one.

Sources

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article