# Terraform Multi-Region and Multi-Account AWS with Provider Aliases

> How to deploy to several AWS regions and accounts from one Terraform or OpenTofu configuration using provider aliases, assume_role, modules and for_each on providers.

- Source: https://www.itwonderlab.com/terraform-multi-region-provider-alias/
- Published: 2026-07-30
- Updated: 2026-07-30
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## One configuration, several regions or accounts

A [provider](https://www.itwonderlab.com/terraform-provider/) block configures one region and one set of credentials. To work with more than one, define several configurations of the same provider and give the additional ones an `alias`. Typical reasons:

- A certificate for CloudFront must be in `us-east-1` ([static website](https://www.itwonderlab.com/terraform-s3-static-website-cloudfront/)).
- Disaster recovery in a second region (replicated buckets, backup copies).
- Resources in several accounts: shared services, [multi-account organizations](https://www.itwonderlab.com/terraform-aws-organizations-multi-account/).

### Aliases

```hcl title="providers.tf"
provider "aws" {
  region = "eu-west-1"            # default provider
}

provider "aws" {
  alias  = "virginia"
  region = "us-east-1"
}

provider "aws" {
  alias  = "dr"
  region = "eu-central-1"
}
```

Resources use the default provider unless you set `provider`:

```hcl title="main.tf"
resource "aws_acm_certificate" "cdn" {
  provider          = aws.virginia
  domain_name       = "www.example.com"
  validation_method = "DNS"
}

resource "aws_s3_bucket" "primary" {
  bucket = "ditwl-data-primary"
}

resource "aws_s3_bucket" "replica" {
  provider = aws.dr
  bucket   = "ditwl-data-replica"
}
```

Data sources accept `provider` too, for example `data "aws_region" "dr" { provider = aws.dr }`.

### Another account with assume_role

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

  assume_role {
    role_arn     = "arn:aws:iam::222222222222:role/terraform"
    session_name = "terraform"
  }
}
```

The identity that runs Terraform needs permission to assume that role, and the role's trust policy must allow it ([IAM](https://www.itwonderlab.com/aws-terraform-tutorial-aws-iam-roles-policies/)). In a pipeline the base credentials come from [OIDC](https://www.itwonderlab.com/terraform-github-actions-aws-oidc/).

### Passing providers to modules

A module uses the default provider of the caller. To give it another one, or two, use `providers`:

```hcl title="main.tf"
module "bucket_with_replica" {
  source = "./modules/replicated-bucket"

  providers = {
    aws         = aws
    aws.replica = aws.dr
  }
}
```

Inside the module, declare which configurations it expects:

```hcl title="modules/replicated-bucket/versions.tf"
terraform {
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      configuration_aliases = [aws.replica]
    }
  }
}
```

Resources in the module then use `provider = aws.replica`. Do not define `provider` blocks inside reusable modules: let the caller pass them. See [modules](https://www.itwonderlab.com/aws-terraform-tutorial-terraform-modules/).

### Many regions: the limitation and the solution

Provider configurations cannot be created in a loop with `for_each` in Terraform, so deploying to ten regions means ten provider blocks and ten module calls. Options:

- **OpenTofu 1.9 and later** supports `for_each` on provider configurations, which removes the repetition:

```hcl title="providers.tf"
locals {
  regions = toset(["eu-west-1", "eu-central-1", "us-east-1"])
}

provider "aws" {
  alias    = "by_region"
  for_each = local.regions
  region   = each.key
}

module "regional" {
  source   = "./modules/regional"
  for_each = local.regions

  providers = {
    aws = aws.by_region[each.key]
  }
}
```

- **Terraform** needs generated code, one configuration per region (with [Terragrunt](https://www.itwonderlab.com/terragrunt-opentofu/) or directories, see [project structure](https://www.itwonderlab.com/terraform-project-structure/)), or the newer approaches that HashiCorp has introduced for multi-region. Check the documentation of your version.

### State and locking

All resources of one configuration go to a single [state](https://www.itwonderlab.com/terraform-state/) and [backend](https://www.itwonderlab.com/terraform-backend/), whatever region or account they are in. Splitting by account and environment into separate states reduces the blast radius ([workspaces vs directories](https://www.itwonderlab.com/terraform-workspaces-vs-directories/)).

### Good practices

- Use the default provider for the main region and aliases for exceptions, with names that describe the purpose (`aws.virginia`, `aws.dr`).
- Keep global resources (IAM, Route 53, CloudFront) in a known region.
- Tag resources with their region and account for [cost tracking](https://www.itwonderlab.com/aws-cost-optimization-finops/).
- Test that all aliased providers authenticate before the first `apply` with `terraform plan`.
- Avoid destroying a provider's resources in the same run that removes the provider: Terraform needs the provider configuration to destroy them.
