AWS Service Control Policies (SCPs) Guide

Complete guide to implementing guardrails with AWS Service Control Policies in Organizations including syntax, deny patterns, and troubleshooting.

12 min readUpdated 2026-01-14

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

LimitValue
Max policies per organization1,000
Max policies attached per root/OU/account5
Max policy document size5,120 characters
Max nesting depth for OUs5 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_POLICY

Essential 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-abcdef123456

Inheritance 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 Account

Testing 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:

  1. Create a sandbox OU
  2. Move a test account to sandbox OU
  3. Attach SCP to sandbox OU
  4. Test various operations in the sandbox account
  5. Review CloudTrail for denied actions
  6. 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-sandbox

Troubleshooting 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

IssueSolution
Action denied unexpectedlyCheck all SCPs in inheritance chain
SCP not taking effectVerify attachment to correct OU/account
Service-linked role issuesSome SLR actions are exempt from SCPs
Global services blockedAdd to NotAction in region restriction SCP
Condition not workingVerify 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 table

Best Practices

PracticeRecommendation
Allow vs DenyPrefer explicit Deny statements for security controls
TestingAlways test in sandbox before production
DocumentationInclude clear Sid and description for each SCP
ExceptionsUse conditions for authorized role exceptions
MonitoringAlert on SCP policy changes via CloudTrail
Version ControlStore SCPs in git with change history
Minimal ScopeAttach 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

Frequently Asked Questions

Find answers to common questions

SCPs define the maximum permissions available to an account but don't grant permissions directly. IAM policies grant actual permissions to users and roles. SCPs act as guardrails that restrict what IAM policies can do. Even if an IAM policy allows an action, an SCP can block it. SCPs apply to all principals in an account except the management account. Use SCPs for organization-wide security boundaries and IAM policies for granting specific access.

Need Professional Help?

Our team of experts can help you implement and configure these solutions for your organization.