AWS Service Control Policies (SCPs) provide central control over the maximum available permissions for all accounts in your organization. SCPs help you ensure accounts stay within your organization's access control guidelines. This guide covers SCP syntax, common patterns, inheritance, and troubleshooting.
This article is part of our comprehensive Cloud Security Tips for 2026 guide covering essential practices for protecting your cloud environment.
Understanding SCPs
Key concepts about SCPs:
- Maximum permissions - SCPs limit what actions are possible, not grant them
- Deny by default - Without FullAWSAccess, all actions are denied
- Inheritance - SCPs flow down from parent OUs to child OUs and accounts
- No effect on management account - SCPs never restrict the management account
- Service-linked roles - Some operations by service-linked roles are exempt
SCP Policy Structure
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "StatementIdentifier",
"Effect": "Allow" | "Deny",
"Action": ["service:Action"],
"Resource": ["*"],
"Condition": {
"ConditionOperator": {
"ConditionKey": "ConditionValue"
}
}
}
]
}SCP Limits
| Limit | Value |
|---|---|
| Max policies per organization | 1,000 |
| Max policies attached per root/OU/account | 5 |
| Max policy document size | 5,120 characters |
| Max nesting depth for OUs | 5 levels |
Enable SCPs in Organization
# Enable SCPs (run from management account)
aws organizations enable-policy-type \
--root-id r-abc1 \
--policy-type SERVICE_CONTROL_POLICY
# Verify SCPs are enabled
aws organizations list-roots \
--query 'Roots[*].PolicyTypes'Create and Attach SCPs
Create SCP
# Create SCP from file
aws organizations create-policy \
--name "DenyRootUser" \
--description "Deny all actions by root user" \
--type SERVICE_CONTROL_POLICY \
--content file://deny-root.json
# Create SCP inline
aws organizations create-policy \
--name "RequireIMDSv2" \
--description "Require IMDSv2 for EC2 instances" \
--type SERVICE_CONTROL_POLICY \
--content '{
"Version": "2012-10-17",
"Statement": [{
"Sid": "RequireIMDSv2",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringNotEquals": {
"ec2:MetadataHttpTokens": "required"
}
}
}]
}'Attach SCP to OU or Account
# List OUs
aws organizations list-organizational-units-for-parent \
--parent-id r-abc1
# Attach to OU
aws organizations attach-policy \
--policy-id p-abcdef123456 \
--target-id ou-abc1-12345678
# Attach to specific account
aws organizations attach-policy \
--policy-id p-abcdef123456 \
--target-id 123456789012
# List policies attached to target
aws organizations list-policies-for-target \
--target-id ou-abc1-12345678 \
--filter SERVICE_CONTROL_POLICYEssential Security SCPs
1. Deny Root User Actions
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRootUser",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::*:root"
}
}
}
]
}2. Deny Leaving Organization
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLeaveOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
}
]
}3. Protect Security Services
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ProtectSecurityServices",
"Effect": "Deny",
"Action": [
"guardduty:DeleteDetector",
"guardduty:DisassociateFromMasterAccount",
"guardduty:DisassociateFromAdministratorAccount",
"guardduty:DisassociateMembers",
"guardduty:StopMonitoringMembers",
"guardduty:UpdateDetector",
"securityhub:DisableSecurityHub",
"securityhub:DeleteMembers",
"securityhub:DisassociateFromMasterAccount",
"config:DeleteConfigurationRecorder",
"config:DeleteDeliveryChannel",
"config:StopConfigurationRecorder",
"cloudtrail:DeleteTrail",
"cloudtrail:StopLogging",
"cloudtrail:UpdateTrail"
],
"Resource": "*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/SecurityAdminRole"
}
}
}
]
}4. Restrict Regions
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonApprovedRegions",
"Effect": "Deny",
"NotAction": [
"iam:*",
"organizations:*",
"support:*",
"budgets:*",
"health:*",
"route53:*",
"cloudfront:*",
"waf:*",
"wafv2:*",
"waf-regional:*",
"globalaccelerator:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"us-east-1",
"us-west-2",
"eu-west-1"
]
}
}
}
]
}5. Require IMDSv2
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RequireIMDSv2",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringNotEquals": {
"ec2:MetadataHttpTokens": "required"
}
}
},
{
"Sid": "DenyIMDSv1Modification",
"Effect": "Deny",
"Action": "ec2:ModifyInstanceMetadataOptions",
"Resource": "*",
"Condition": {
"StringEquals": {
"ec2:MetadataHttpTokens": "optional"
}
}
}
]
}6. Deny Public S3 Buckets
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyPublicS3Actions",
"Effect": "Deny",
"Action": [
"s3:PutBucketPublicAccessBlock",
"s3:DeletePublicAccessBlock"
],
"Resource": "*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/S3AdminRole"
}
}
},
{
"Sid": "DenyPublicACLs",
"Effect": "Deny",
"Action": [
"s3:PutBucketAcl",
"s3:PutObjectAcl"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": [
"public-read",
"public-read-write",
"authenticated-read"
]
}
}
}
]
}7. Require Encryption
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnencryptedS3Uploads",
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"Null": {
"s3:x-amz-server-side-encryption": "true"
}
}
},
{
"Sid": "DenyUnencryptedEBSVolumes",
"Effect": "Deny",
"Action": "ec2:CreateVolume",
"Resource": "*",
"Condition": {
"Bool": {
"ec2:Encrypted": "false"
}
}
},
{
"Sid": "DenyUnencryptedRDS",
"Effect": "Deny",
"Action": [
"rds:CreateDBInstance",
"rds:CreateDBCluster"
],
"Resource": "*",
"Condition": {
"Bool": {
"rds:StorageEncrypted": "false"
}
}
}
]
}SCP Inheritance
SCPs are inherited through the OU hierarchy:
# View effective policies for account
aws organizations list-policies-for-target \
--target-id 123456789012 \
--filter SERVICE_CONTROL_POLICY
# View all targets a policy is attached to
aws organizations list-targets-for-policy \
--policy-id p-abcdef123456Inheritance Example
Root (FullAWSAccess + DenyLeaveOrg)
├── Security OU (ProtectSecurityServices)
│ └── Security Account (inherits all above)
├── Production OU (RestrictRegions + RequireEncryption)
│ ├── App1 Account (inherits all above)
│ └── App2 Account (inherits all above)
└── Development OU (less restrictive)
└── Dev AccountTesting SCPs
Use IAM Policy Simulator
# Test if action is allowed
aws iam simulate-custom-policy \
--policy-input-list file://test-policy.json \
--action-names s3:PutObject \
--resource-arns arn:aws:s3:::my-bucket/*Test in Sandbox Account
Always test SCPs before applying to production:
- Create a sandbox OU
- Move a test account to sandbox OU
- Attach SCP to sandbox OU
- Test various operations in the sandbox account
- Review CloudTrail for denied actions
- If successful, move to production OUs
# Create sandbox OU
aws organizations create-organizational-unit \
--parent-id r-abc1 \
--name "SCP-Testing-Sandbox"
# Move test account
aws organizations move-account \
--account-id 123456789012 \
--source-parent-id ou-abc1-prod \
--destination-parent-id ou-abc1-sandboxTroubleshooting SCPs
Check CloudTrail for Denials
# Search for SCP-denied actions
aws logs filter-log-events \
--log-group-name /aws/cloudtrail/logs \
--filter-pattern '{ $.errorCode = "AccessDenied" }' \
--start-time $(date -d '1 hour ago' +%s000)Common Issues and Solutions
| Issue | Solution |
|---|---|
| Action denied unexpectedly | Check all SCPs in inheritance chain |
| SCP not taking effect | Verify attachment to correct OU/account |
| Service-linked role issues | Some SLR actions are exempt from SCPs |
| Global services blocked | Add to NotAction in region restriction SCP |
| Condition not working | Verify condition key is supported for action |
Verify Effective Permissions
# Get policy content
aws organizations describe-policy \
--policy-id p-abcdef123456 \
--query 'Policy.Content' \
--output text | jq .
# List all SCPs in organization
aws organizations list-policies \
--filter SERVICE_CONTROL_POLICY \
--query 'Policies[*].[Id,Name]' \
--output tableBest Practices
| Practice | Recommendation |
|---|---|
| Allow vs Deny | Prefer explicit Deny statements for security controls |
| Testing | Always test in sandbox before production |
| Documentation | Include clear Sid and description for each SCP |
| Exceptions | Use conditions for authorized role exceptions |
| Monitoring | Alert on SCP policy changes via CloudTrail |
| Version Control | Store SCPs in git with change history |
| Minimal Scope | Attach SCPs at lowest appropriate OU level |
Monitor SCP Changes
# Create EventBridge rule for SCP modifications
aws events put-rule \
--name "SCPChanges" \
--event-pattern '{
"source": ["aws.organizations"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["organizations.amazonaws.com"],
"eventName": [
"CreatePolicy",
"UpdatePolicy",
"DeletePolicy",
"AttachPolicy",
"DetachPolicy"
]
}
}'
# Add notification target
aws events put-targets \
--rule SCPChanges \
--targets Id=1,Arn=arn:aws:sns:us-east-1:123456789012:security-alerts