# AWS Organizations and Multi-Account Setup with Terraform

> Design and create a multi-account AWS landing zone with Terraform or OpenTofu: AWS Organizations, OUs, accounts, service control policies and cross-account roles.

- Source: https://www.itwonderlab.com/terraform-aws-organizations-multi-account/
- Published: 2026-05-24
- Updated: 2026-05-24
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## Why use several AWS accounts

An AWS account is the strongest isolation boundary: permissions, quotas, billing and blast radius are per account. The usual recommendation is **one account per environment and workload** (development, production, shared services, security, logging) instead of one big account. [AWS Organizations](https://www.itwonderlab.com/aws-organizations/) groups them under one management account with consolidated billing and central policies. See [AWS best practices](https://www.itwonderlab.com/best-practices/aws-best-practices/).

### A typical structure

```
Root
├── Security OU       (audit, log archive)
├── Infrastructure OU (network, shared services)
├── Workloads OU
│   ├── dev account
│   └── pro account
└── Sandbox OU        (experiments with budget limits)
```

### Create the organization

Run this once, from the management account, and keep nothing else in it:

```hcl title="organization.tf"
resource "aws_organizations_organization" "this" {
  feature_set = "ALL"

  aws_service_access_principals = [
    "cloudtrail.amazonaws.com",
    "config.amazonaws.com",
    "sso.amazonaws.com",
    "guardduty.amazonaws.com",
  ]

  enabled_policy_types = ["SERVICE_CONTROL_POLICY", "TAG_POLICY"]
}

resource "aws_organizations_organizational_unit" "workloads" {
  name      = "Workloads"
  parent_id = aws_organizations_organization.this.roots[0].id
}

resource "aws_organizations_organizational_unit" "security" {
  name      = "Security"
  parent_id = aws_organizations_organization.this.roots[0].id
}
```

If you already have an organization, [import it](https://www.itwonderlab.com/terraform-import-moved-removed/) instead of creating it.

### Create accounts

```hcl title="accounts.tf"
resource "aws_organizations_account" "pro" {
  name      = "ditwl-pro"
  email     = "aws-pro@example.com"   # unique per account
  parent_id = aws_organizations_organizational_unit.workloads.id

  role_name                  = "OrganizationAccountAccessRole"
  iam_user_access_to_billing = "DENY"

  lifecycle {
    ignore_changes = [role_name]
    prevent_destroy = true
  }
}
```

> [!WARNING]
> Closing an account is slow and limited by quotas, and removing an `aws_organizations_account` from the configuration only removes it from the organization or tries to close it, depending on `close_on_deletion`. Use [`prevent_destroy`](https://www.itwonderlab.com/terraform-lifecycle-meta-argument/) and be careful. Many teams use **AWS Control Tower** or Account Factory for Terraform to create accounts at scale, which is a good option if you want a managed landing zone.

### Service control policies

An SCP sets the maximum permissions for every identity in an account, including its administrators. It does not grant access: it limits it.

```hcl title="scp.tf"
data "aws_iam_policy_document" "guardrails" {
  statement {
    sid       = "DenyLeavingOrganization"
    effect    = "Deny"
    actions   = ["organizations:LeaveOrganization"]
    resources = ["*"]
  }

  statement {
    sid       = "DenyDisablingCloudTrail"
    effect    = "Deny"
    actions   = ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail"]
    resources = ["*"]
  }

  statement {
    sid       = "RestrictRegions"
    effect    = "Deny"
    not_actions = ["iam:*", "organizations:*", "route53:*", "cloudfront:*", "support:*", "sts:*"]
    resources = ["*"]

    condition {
      test     = "StringNotEquals"
      variable = "aws:RequestedRegion"
      values   = ["eu-west-1", "eu-central-1"]
    }
  }
}

resource "aws_organizations_policy" "guardrails" {
  name    = "guardrails"
  type    = "SERVICE_CONTROL_POLICY"
  content = data.aws_iam_policy_document.guardrails.json
}

resource "aws_organizations_policy_attachment" "workloads" {
  policy_id = aws_organizations_policy.guardrails.id
  target_id = aws_organizations_organizational_unit.workloads.id
}
```

Test SCPs first in a sandbox OU: a wrong deny can block your own pipeline. Global services in `not_actions` must be excluded from the region restriction, otherwise things such as IAM stop working. SCPs do not apply to the management account, which is another reason to keep it empty.

### Access across accounts

- **People:** use [IAM Identity Center](https://www.itwonderlab.com/aws-iam-identity-center/) (SSO) with permission sets assigned to groups and accounts, with no IAM users.
- **Automation:** a role in each target account that the pipeline assumes from a central account or with [OIDC](https://www.itwonderlab.com/terraform-github-actions-aws-oidc/).
- **Terraform:** one provider per account with `assume_role`:

```hcl title="providers.tf"
provider "aws" {
  alias  = "pro"
  region = "eu-west-1"

  assume_role {
    role_arn = "arn:aws:iam::${aws_organizations_account.pro.id}:role/OrganizationAccountAccessRole"
  }
}
```

Providers cannot be created in a loop in Terraform (OpenTofu 1.9 and later can use `for_each` on providers), so large setups use a separate configuration per account, with [Terragrunt](https://www.itwonderlab.com/terragrunt-opentofu/) or one [directory per environment](https://www.itwonderlab.com/terraform-workspaces-vs-directories/).

### Centralized services

Typical accounts and what runs in them:

- **Log archive:** the organization-wide [CloudTrail](https://www.itwonderlab.com/aws-cloudtrail/) bucket, with write-once policies.
- **Security/audit:** [GuardDuty, Config and Security Hub](https://www.itwonderlab.com/terraform-aws-security-monitoring/) delegated administrator.
- **Network:** [Transit Gateway](https://www.itwonderlab.com/terraform-aws-vpc-peering-transit-gateway/), shared with RAM.
- **Shared services:** CI/CD runners, DNS, container registry.

### Governance

Add budgets per account, mandatory tags with tag policies ([resource tagging](https://www.itwonderlab.com/aws-resource-tagging/)) and a quarterly review of the SCPs.
