AWS Control Tower — Set Up a Multi-Account Landing Zone
Your company started with one AWS account. Then someone needed a dev environment, so you made a second. Then staging. Then a sandbox for the data team. Now you have twelve accounts, each with different IAM policies, no consistent logging, and a security audit that makes everyone nervous. A landing zone is the fix — it's the foundational structure that makes multi-account AWS actually manageable instead of chaotic.
What Is a Landing Zone?
A landing zone is a pre-configured, secure, multi-account AWS environment based on best practices. Think of it as the foundation of a building — you set it up once, and everything built on top inherits the right structure.
A well-built landing zone provides:
- Centralized identity — One place to manage users and access across all accounts
- Centralized logging — CloudTrail, Config, and GuardDuty logs from every account flow to one place
- Guardrails — Preventive and detective controls that enforce policies automatically
- Account vending — Self-service creation of new accounts with consistent configurations
- Network connectivity — Shared VPCs, Transit Gateway, or VPC peering patterns
You can build this from scratch with AWS Organizations, SCPs, and a lot of CloudFormation. Or you can use Control Tower, which does 80% of the work for you.
AWS Control Tower — The Managed Landing Zone
Control Tower sets up a landing zone in about an hour. Here's what it creates automatically:
AWS Organization (Root)
├── Security OU
│ ├── Audit Account (security tools, cross-account access)
│ └── Log Archive Account (centralized CloudTrail, Config logs)
├── Sandbox OU
│ └── Developer accounts (experimentation, limited blast radius)
├── Workloads OU
│ ├── Production accounts
│ └── Staging accounts
└── Management Account (billing, Organization management)
Setting it up:
# Control Tower is set up via the AWS Console
# Navigate to: AWS Control Tower → Set up landing zone
# After setup, verify the organizational structure
aws organizations list-organizational-units-for-parent \
--parent-id r-abc1 \
--query 'OrganizationalUnits[].{Name:Name, Id:Id}'
# List enrolled accounts
aws controltower list-enrolled-accounts \
--query 'enrolledAccounts[].{Name:accountName, Id:accountId, OU:organizationalUnitName}'
Organizational Units — Best Practices
The OU structure is the backbone of your landing zone. Here's a battle-tested layout:
| OU | Purpose | Example Accounts |
|---|---|---|
| Security | Security tooling and audit | Audit, Log Archive |
| Infrastructure | Shared services | Networking, Shared Services, DNS |
| Workloads/Prod | Production applications | App-Prod, Data-Prod, API-Prod |
| Workloads/NonProd | Non-production environments | App-Dev, App-Staging, QA |
| Sandbox | Experimentation | Developer sandboxes |
| Suspended | Decommissioned accounts | Accounts pending deletion |
| Policy Staging | Test new SCPs | Policy test accounts |
Key principles:
- Never run workloads in the management account — It's for billing and Organization management only.
- Separate production from non-production at the OU level — SCPs differ between them.
- One workload per account — Blast radius isolation. If one app gets compromised, others are unaffected.
- The Suspended OU has a deny-all SCP — Accounts there can't do anything.
// deny-all-scp.json — Attach to the Suspended OU
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllAccess",
"Effect": "Deny",
"Action": "*",
"Resource": "*"
}
]
}
Guardrails — Preventive vs Detective
Guardrails are the rules that keep your accounts in line. Control Tower provides two types:
Preventive guardrails use Service Control Policies (SCPs) to block actions before they happen:
// Preventive: Deny root user access
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRootUser",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::*:root"
}
}
}
]
}
// Preventive: Deny leaving the organization
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLeaveOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
}
]
}
Detective guardrails use AWS Config rules to detect and report violations:
# Detective guardrails create Config rules like:
# - S3 buckets must have encryption enabled
# - EBS volumes must be encrypted
# - CloudTrail must be enabled
# - RDS instances must not be publicly accessible
# Check compliance status
aws configservice get-compliance-summary-by-config-rule \
--query 'ComplianceSummary'
| Guardrail Category | Type | Example |
|---|---|---|
| Mandatory | Preventive | Disallow changes to CloudTrail, log archive |
| Strongly Recommended | Preventive + Detective | Disallow public S3 buckets, require encryption |
| Elective | Detective | Detect unused EC2 instances, unattached EBS |
Mandatory guardrails are always on. Strongly recommended ones should be enabled for production OUs. Elective ones are opt-in based on your organization's needs.
Account Factory — Vending New Accounts
Account Factory is Control Tower's mechanism for creating new AWS accounts with consistent configurations:
# Create a new account via Account Factory (CLI)
aws controltower create-managed-account \
--account-name "app-team-prod" \
--account-email "app-team-prod@company.com" \
--organizational-unit-name "Workloads-Prod" \
--sso-user-email "admin@company.com" \
--sso-user-first-name "Admin" \
--sso-user-last-name "User"
Every account created through Account Factory automatically gets:
- Enrolled in AWS Organizations
- Placed in the specified OU (inheriting SCPs)
- CloudTrail enabled, logs sent to Log Archive
- AWS Config enabled, reporting to Audit account
- IAM Identity Center (SSO) access configured
- VPC with default configuration (customizable)
Account Factory for Terraform (AFT)
If your team uses Terraform, AFT lets you define account configurations as code:
# aft-account-request/main.tf
module "app_team_production" {
source = "./modules/aft-account-request"
control_tower_parameters = {
AccountEmail = "app-prod@company.com"
AccountName = "App-Team-Production"
ManagedOrganizationalUnit = "Workloads-Prod"
SSOUserEmail = "admin@company.com"
SSOUserFirstName = "Admin"
SSOUserLastName = "User"
}
account_tags = {
Environment = "production"
Team = "app-team"
CostCenter = "CC-1234"
}
account_customizations_name = "production-baseline"
}
AFT uses a pipeline architecture:
Account Request (Terraform)
→ CodePipeline
→ Control Tower Account Factory
→ Account Customizations (another pipeline)
→ Global Customizations (applied to ALL accounts)
The customization layer is where you define what happens after the account is created — installing security agents, setting up VPC peering, deploying baseline IAM roles:
# aft-account-customizations/production-baseline/terraform/main.tf
resource "aws_iam_role" "deployment" {
name = "DeploymentRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${var.cicd_account_id}:root"
}
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_guardduty_detector" "main" {
enable = true
}
resource "aws_ebs_encryption_by_default" "enabled" {
enabled = true
}
Customizations for Control Tower (CfCT)
For teams that prefer CloudFormation, Customizations for Control Tower (CfCT) is an AWS solution that deploys additional resources into accounts using StackSets:
# manifest.yaml — CfCT configuration
region: us-east-1
version: 2021-03-15
resources:
- name: SecurityBaseline
description: Deploy security baseline to all accounts
resource_file: templates/security-baseline.yaml
deployment_targets:
organizational_units:
- Workloads-Prod
- Workloads-NonProd
deploy_method: stack_set
regions:
- us-east-1
- us-west-2
- name: VPCBaseline
description: Standard VPC configuration
resource_file: templates/vpc-baseline.yaml
parameters:
- parameter_key: VPCCidr
parameter_value: $[alfred_ssm_/org/vpc/cidr]
deployment_targets:
organizational_units:
- Workloads-Prod
Landing Zone Architecture — Putting It Together
A production-ready landing zone looks like this:
Management Account
├── AWS Organizations (root)
├── AWS Control Tower
├── Billing & Cost Management
└── IAM Identity Center (SSO)
Security OU
├── Log Archive Account
│ ├── S3: CloudTrail logs (all accounts)
│ ├── S3: Config snapshots (all accounts)
│ └── S3: VPC Flow Logs (all accounts)
└── Audit Account
├── GuardDuty (delegated admin)
├── Security Hub (delegated admin)
├── AWS Config (aggregator)
└── Cross-account IAM roles
Infrastructure OU
├── Network Account
│ ├── Transit Gateway
│ ├── Shared VPCs (RAM)
│ ├── Route 53 hosted zones
│ └── Direct Connect / VPN
└── Shared Services Account
├── ECR repositories
├── AMI management
└── CI/CD pipelines
Workloads OU
├── App-Prod (isolated VPC, peered via TGW)
├── App-Staging
└── App-Dev
# Verify your landing zone health
aws controltower get-landing-zone \
--landing-zone-identifier "arn:aws:controltower:us-east-1:123456789012:landingzone/ABC123"
# List all guardrail violations
aws controltower list-enabled-controls \
--target-identifier "arn:aws:organizations::123456789012:ou/o-abc123/ou-xyz789"
Service Catalog Integration
Service Catalog lets you create a portfolio of approved products (CloudFormation templates) that teams can self-service deploy:
# Create a portfolio for approved infrastructure patterns
aws servicecatalog create-portfolio \
--display-name "Approved Infrastructure" \
--provider-name "Platform Team" \
--description "Pre-approved, compliant infrastructure patterns"
# Teams can then launch products like:
# - "Standard Web Application" (ALB + ECS + RDS)
# - "Data Pipeline" (S3 + Glue + Redshift)
# - "Secure S3 Bucket" (encryption, versioning, lifecycle)
A landing zone is not a one-time project — it's a living system that evolves with your organization. Start with Control Tower's defaults, enable strongly recommended guardrails, and use Account Factory (or AFT) to vend new accounts. The investment pays for itself the first time a guardrail blocks someone from making an S3 bucket public in production, or the first time you can spin up a new team's infrastructure in hours instead of weeks. Build the foundation right, and everything on top becomes easier.
