AWS Tag Policy vs SCP: Which One Actually Enforces Your Tagging Rules?
Key Takeaways
- Neither control enforces tagging by itself. A tag policy validates the case and allowed values of a tag you've already defined, but it never requires that the tag exists on a resource in the first place. A service control policy (SCP) can block a resource from being created at all, but it has no concept of an approved value list or case normalization.
- Presence and correctness are two different jobs. Requiring that a CostCenter tag exists on every EC2 instance is an SCP problem. Making sure that tag reads Prod instead of prod or PROD is a tag policy problem.
- Tag policy enforcement is opt-in per resource type, not automatic. Until you explicitly turn on "prevent noncompliant operations" for a specific resource type, a tag policy only reports violations after the fact. It doesn't block anything.
- SCPs never touch the management account. If your tagging gaps are showing up there, an SCP can't close them no matter how you write the policy. That's the one blind spot no SCP configuration fixes.
The Verdict
If the question is "can I stop someone from creating a resource with no tag at all," the answer is an SCP rather than a tag policy. If the question is "can I stop someone from misspelling or miscasing a tag that's already required," the answer is a tag policy rather than an SCP. Most teams asking "which one enforces my tagging rules" collapse two different failure modes into one control, and neither service was built to cover both on its own.
A tag policy reports by default and doesn't block. You define a tag key, the case treatment you want, and the values that count as compliant. Then AWS Organizations evaluates existing tags against that definition. Violations surface in the AWS Resource Groups Tagging compliance dashboard. Nothing in that process stops a noncompliant tag from being written, and untagged resources or tags outside the policy's definition aren't evaluated at all. AWS does let you flip specific resource types into an enforced mode that prevents a noncompliant tagging request from completing, but you have to turn it on deliberately, resource type by resource type. It isn't the default, and it doesn't retroactively require a tag to exist.
An SCP works the opposite way. It doesn't evaluate anything after the fact. It's a permission guardrail evaluated at the moment of the API call: if a deny statement's conditions match the request, the call fails and the resource is never created. That makes it the only one of the two that can actually require a tag's presence, because you write the condition to check whether the tag is missing and deny the action when it is.
AWS Tag Policy vs. SCP: Side by Side
| Characteristic | AWS Tag Policy | Service Control Policy (SCP) |
|---|---|---|
| What it governs | The case and allowed values of a tag once it's applied | Whether an API action is permitted to complete at all |
| Managed in | AWS Organizations, under the Tag Policies policy type | AWS Organizations, under the Service Control Policies policy type |
| Default behavior | Reporting only; noncompliant tags surface in Resource Groups compliance data | Deny unless explicitly allowed; effect is immediate at request time |
| Can it require a tag exist? | No. Untagged resources and tags outside the policy's definition aren't evaluated | Yes, using a Null condition against aws:RequestTag/{key} to deny actions missing that tag |
| Can it require a tag be well-formed? | Yes, this is its primary purpose (tag key case, tag value list) | Only crudely, by enumerating exact allowed values with StringEquals; there's no case-normalization or managed value list comparable to a tag policy |
| Enforcement scope when turned on | Per resource type, opt-in via enforced_for; must be explicitly enabled for that tag key and resource type | Applies to any action you write a deny statement for, across any taggable service, the moment the SCP is attached |
| Where violations surface | AWS Resource Groups Tagging compliance dashboard, evaluated after the fact for non-enforced tags | Immediately, as an AccessDenied error at the moment of the request |
| Affects the management account? | Compliance is evaluated per account, including the management account, for visibility | No. SCPs never apply to the organization's management account, regardless of how they're attached |
| How multiple policies combine across an OU hierarchy | Effective policy is the most restrictive combination of every tag policy attached above the account; a child OU can narrow the parent's allowed values, never expand them | Effective permissions are the intersection of every SCP attached above the account; a denial anywhere in the chain holds regardless of what's attached directly to the account |
Deciding Which One You Need
- Do you need to guarantee a tag key is present on every new resource, no exceptions? Reach for an SCP with a deny statement conditioned on that tag being absent from the request.
- Do you need the tag's value to match an approved list or case convention once it's applied? Reach for a tag policy with tag_value defined for that key.
- Do you need both, a tag that must exist and must be well-formed? Layer them. An SCP blocks creation when the key is missing; a tag policy with enforcement turned on for that resource type rejects a present-but-malformed value.
- Are the accounts in question the management account? Tag policies (reporting only) are your only lever there. SCPs have no effect on management-account principals, so there's no way to force tagging at the API layer for that account.
Worked Scenario: Requiring and Normalizing a CostCenter Tag
Say the goal is: every new EC2 instance across the organization must carry a CostCenter tag, and that tag must be one of three approved values.
Step 1: Block instance creation when the tag is missing, with an SCP.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRunInstancesWithoutCostCenterTag",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"Null": {
"aws:RequestTag/CostCenter": "true"
}
}
}
]
}The Null condition operator evaluates to true when the specified key is absent from the request. Attach this to the OU holding the accounts you want covered. It has no effect if attached at a level that includes the management account, since SCPs don't apply there.
Step 2: Normalize and validate the tag's value, with a tag policy.
{
"tags": {
"CostCenter": {
"tag_key": {
"@@assign": "CostCenter"
},
"tag_value": {
"@@assign": [
"1001-Eng",
"1002-Sales",
"1003-Ops"
]
},
"enforced_for": {
"ec2:instance": {
"@@assign": "enabled"
}
}
}
}
}AWS has been expanding the list of resource types that support enforced tag policies since the feature launched, and the exact enforced_for key syntax varies by resource type. Treat the ec2:instance key above as an example of the pattern rather than a guaranteed value: before attaching a policy like this in production, check AWS Organizations > Policies > Tag Policies in the console (or the current tag policy syntax reference) for the resource type you're targeting. The console only lets you select enforcement for services that currently support it.. That makes it the fastest way to confirm what's live at the moment you're building the policy, rather than relying on any list frozen in an article.
With both in place: the SCP stops an untagged instance from being created at all. The tag policy stops a tagged-but-wrong instance (CostCenter=Eng instead of 1001-Eng) from completing once enforcement is turned on for EC2 instances. It continues reporting on any that slip through in accounts where enforcement isn't yet active.
The Same Pattern for Other Resource Types
The SCP side of this generalizes directly. Swap the action and resource ARN for whichever service you're targeting: for S3, that's s3:CreateBucket against arn:aws:s3:::*. For RDS, it's rds:CreateDBInstance against arn:aws:rds:*:*:db:*. The condition block, a Null check against aws:RequestTag/{key}, doesn't change.
{
"Sid": "DenyCreateBucketWithoutCostCenterTag",
"Effect": "Deny",
"Action": "s3:CreateBucket",
"Resource": "arn:aws:s3:::*",
"Condition": {
"Null": {
"aws:RequestTag/CostCenter": "true"
}
}
}The tag policy side is where it gets uneven. Whether a given resource type supports enforced mode, and what its enforced_for key looks like, depends entirely on whether AWS has added support for that service. Confirm it in the console for the specific resource type you're targeting rather than assuming support carries over from one service to the next.
Where a Single Deny Statement Falls Short
The policy in Step 1 only covers ec2:RunInstances. That's not enough to actually hold the line, for two reasons. First, it doesn't stop anyone from removing the tag after a compliant launch: nothing denies ec2:DeleteTags or the cross-service tag:UntagResources, so a correctly tagged instance can be stripped of its tag five minutes later with no resistance from this policy. Second, if a tag gets applied through a separate ec2:CreateTags call rather than inline through TagSpecifications on the launch itself (whether because of internal tooling, a script, or an older console flow), the condition on RunInstances never evaluates that tag at all.
This is a well-known failure mode rather than an edge case. AWS maintains a dedicated knowledge-center article on fixing tag-based SCP restrictions that don't work as expected, specifically because teams write a single-action policy like the one above and then find gaps in it. Closing them means covering every action that can create, modify, or remove the tag, not just the one that creates the resource:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyMissingOrRemovedCostCenterTag",
"Effect": "Deny",
"Action": [
"ec2:RunInstances",
"ec2:CreateTags",
"ec2:DeleteTags"
],
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"Null": {
"aws:RequestTag/CostCenter": "true"
}
}
}
]
}Even this isn't automatically complete for every resource type. Verify which actions apply to your target resource (an S3 bucket's tagging and untagging actions aren't the same API calls as an EC2 instance's) before treating a single-action deny statement as coverage.
How Enforcement Behaves as Your Org Structure Changes
Both controls inherit down an OU hierarchy, and both follow the same principle: a child level can only narrow what a parent allows, never expand it. For SCPs, an account's effective permissions are the intersection of every SCP attached above it, from the root down to the account. If a permission is blocked anywhere in that chain, it's blocked, regardless of what's attached directly to the account. For tag policies, the effective policy at any level is the most restrictive combination of every tag policy attached above it: values allowed at a child OU can only be a subset of what a parent OU allows, never a superset.
That inheritance model has a practical consequence that security architects run into constantly: moving an account between OUs changes what's enforced on it immediately, without anyone touching the account directly. An account moved out from under an OU that had the CostCenter SCP attached loses that enforcement the moment the move completes, and nothing in the account's own configuration shows that it happened. The same is true in reverse: an account that inherits a stricter tag policy after a reorg can start rejecting tag values that were compliant an hour earlier.
This is the same drift pattern that shows up anywhere enforcement is tied to org structure instead of to the resource itself. A compensating control or temporary exception (an account detached from an OU "just for testing," a policy left off during a migration) needs an explicit owner and expiration. Otherwise it quietly becomes the org's new baseline, and the next audit finds a gap nobody remembers creating.
Native keeps controls like these mapped to the frameworks they're meant to satisfy, and flags the moment enforcement drifts from what's actually attached across accounts. See how Native handles compliance enforcement across an AWS Organization.
FAQ
Can an SCP stop someone from creating an untagged S3 bucket?
Yes, if the SCP explicitly denies the relevant creation action (for example, s3:CreateBucket) conditioned on a Null check against the required aws:RequestTag/{key}. It doesn't happen by default. An SCP with no tagging-related deny statement has no opinion about tags at all.
Do AWS tag policies block noncompliant tags automatically?
No. Tag policies default to reporting only. Noncompliant tags on resources they cover show up in the AWS Resource Groups Tagging compliance dashboard, but the tagging request itself completes. Blocking requires explicitly turning on enforcement for the specific resource type.
Can I use an SCP and a tag policy together?
Yes, and AWS's own tag policy guidance points in this direction: once you have a tag policy standardizing values, the recommended next step is an SCP that requires the tag on new resource creation. They're complementary rather than redundant.
Do SCPs apply to the AWS Organizations management account?
No. SCPs never affect the management account, including its root user. If tagging gaps live there, a tag policy's compliance reporting is the only visibility you'll get from these two controls.
What happens if I define a tag policy but never turn on enforcement?
Nothing is blocked. The policy still evaluates compliance and reports violations, but noncompliant tagging requests complete normally until enforcement is explicitly enabled for that resource type.
Does moving an account between OUs change which tagging rules apply to it?
Yes, immediately. Both SCPs and tag policies evaluate based on the account's current position in the hierarchy rather than where it was when a resource was created. A move can silently add or remove enforcement with no change to the account itself.
Can a tag policy at a child OU override a stricter tag policy at the parent?
No. Effective tag policies merge as the most restrictive combination across the hierarchy. A child OU's tag policy can narrow what the parent allows, but it can't expand it.
What's the difference between aws:RequestTag and aws:TagKeys in an SCP?
aws:RequestTag/{key} checks the value of a specific tag key in the request. aws:TagKeys checks which tag keys are present, independent of their values, and you typically pair it with ForAllValues or ForAnyValue to restrict the whole set of keys a request can include, rather than the value of any one of them.
Does either of these retroactively fix tags on resources that already exist?
No. Both controls evaluate at the moment of a request; neither one goes back and tags or corrects resources that predate the policy. A tag policy will surface existing noncompliant tags in the Resource Groups compliance dashboard, but fixing them is a separate remediation effort that neither policy performs automatically.