HashiCorp and AWS released on June 18 2025 the Terraform AWS Provider 6.0. This update tackles one of the biggest pain points for practitioners: the multi-region infrastructure management.

Until now, using AWS resources across regions meant juggling multiple provider blocks and aliases. With v6.0, you can instead assign a region argument at the resource level, all within a single provider configuration.

This might sound like a small shift, but it's a fundamental change that makes Terraform configurations simpler, cleaner, and much easier to scale. In this article, I'll explain what's new, show a before vs after migration example, and share some thoughts on why this release matters.

Why multi-region matters

Multi-region deployments aren't just for the "big players". They're increasingly common in everyday infrastructure:

  • Disaster recovery: keeping replicas in a separate region
  • Latency optimization: serving customers closer to where they are
  • Compliance requirements: storing data in specific geographies

In Terraform AWS provider v5.x and earlier, doing this meant repeating provider blocks with aliases for every region. For a project using two or three regions, this was annoying. For global companies spanning 10+ regions, it was a nightmare.

Terraform AWS provider 6.0 addresses this directly as it give us a way to manage multi-region infrastructure without drowning in boilerplate.

The old way: Aliases everywhere

Let's look at how things worked before version 6.0.

Spoiler alert! It wasn't pretty.

Here's a simple RDS setup with a primary database in Ireland (eu-west-1) region and a replica in London (eu-west-2) region.

Before (Terraform AWS Provider < 6.0):

provider "aws" {
  region = "eu-west-1"
}

provider "aws" {
  alias  = "eu-london"
  region = "eu-west-2"
}

resource "aws_db_instance" "primary" {
  provider = aws
  identifier = "primary-db"
  engine     = "mysql"
  instance_class = "db.t3.micro"
  allocated_storage = 20
  engine_version = "8.0.42"
  username = "admin"
  password = "password123"
  backup_retention_period = 7
  skip_final_snapshot = true
}

resource "aws_db_instance" "replica" {
  provider = aws.eu-london
  identifier = "replica-db"
  engine     = "mysql"
  engine_version = "8.0.42"
  instance_class = "db.t3.micro"
  auto_minor_version_upgrade = false
  skip_final_snapshot = true
  replicate_source_db = aws_db_instance.primary.arn

  depends_on = [aws_db_instance.primary]
}
None
Primary DB instance as seen in AWS console

Notice the duplication here: two providers, one of them aliased, and each resource tied to its provider. If you wanted to add three or more replicas in different regions, you'd need three more providers, each with its own alias.

Now imagine scaling this to a dozen regions. The code gets harder to read, harder to maintain, and far more error prone.

After (Terraform AWS Provider ≥ 6.0):

With Terraform AWS provider 6.0, the same configuration becomes dramatically simpler. You only need one provider block, and then you can attach the region attribute directly to your resources.

provider "aws" {
  region = "eu-west-1"
}

resource "aws_db_instance" "primary" {
  identifier = "primary-db"
  engine     = "mysql"
  instance_class = "db.t3.micro"
  allocated_storage = 20
  engine_version = "8.0.42"
  username = "admin"
  password = "password123"
  backup_retention_period = 7
  skip_final_snapshot = true
}

resource "aws_db_instance" "replica" {
  region = "eu-west-2"
  identifier = "replica-db"
  engine     = "mysql"
  engine_version = "8.0.42"
  instance_class = "db.t3.micro"
  auto_minor_version_upgrade = false
  skip_final_snapshot = true
  replicate_source_db = aws_db_instance.primary.arn

  depends_on = [aws_db_instance.primary]
}
None
Replica DB instance created in AWS console

Now, the key differences here are:

  • Single provider block
  • region is now a property of the resource
  • Adding new regions is just a matter of adding another region attribute

This isn't just cleaner code, it also reduces memory usage by removing the need to load multiple provider instances.

$ terraform init -backend=true
Initializing the backend...
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.92"...
- Installing hashicorp/aws v5.100.0...
- Installed hashicorp/aws v5.100.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
$ terraform plan
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
Terraform will perform the following actions:
  # aws_db_instance.primary will be created
  + resource "aws_db_instance" "primary" {
      + address                               = (known after apply)
      + allocated_storage                     = 20
      + apply_immediately                     = false
      + arn                                   = (known after apply)
      + auto_minor_version_upgrade            = true
      + availability_zone                     = (known after apply)
      + backup_retention_period               = 7
      + backup_target                         = (known after apply)
      + backup_window                         = (known after apply)
      + ca_cert_identifier                    = (known after apply)
      + character_set_name                    = (known after apply)
      + copy_tags_to_snapshot                 = false
      + database_insights_mode                = (known after apply)
      + db_name                               = (known after apply)
      + db_subnet_group_name                  = (known after apply)
      + dedicated_log_volume                  = false
      + delete_automated_backups              = true
      + domain_fqdn                           = (known after apply)
      + endpoint                              = (known after apply)
      + engine                                = "mysql"
      + engine_lifecycle_support              = (known after apply)
      + engine_version                        = "8.0.42"
      + engine_version_actual                 = (known after apply)
      + hosted_zone_id                        = (known after apply)
      + id                                    = (known after apply)
      + identifier                            = "primary-db"
      + identifier_prefix                     = (known after apply)
      + instance_class                        = "db.t3.micro"
      + iops                                  = (known after apply)
      + kms_key_id                            = (known after apply)
      + latest_restorable_time                = (known after apply)
      + license_model                         = (known after apply)
      + listener_endpoint                     = (known after apply)
      + maintenance_window                    = (known after apply)
      + master_user_secret                    = (known after apply)
      + master_user_secret_kms_key_id         = (known after apply)
      + monitoring_interval                   = 0
      + monitoring_role_arn                   = (known after apply)
      + multi_az                              = (known after apply)
      + nchar_character_set_name              = (known after apply)
      + network_type                          = (known after apply)
      + option_group_name                     = (known after apply)
      + parameter_group_name                  = (known after apply)
      + password                              = (sensitive value)
      + password_wo                           = (write-only attribute)
      + performance_insights_enabled          = false
      + performance_insights_kms_key_id       = (known after apply)
      + performance_insights_retention_period = (known after apply)
      + port                                  = (known after apply)
      + publicly_accessible                   = false
      + replica_mode                          = (known after apply)
      + replicas                              = (known after apply)
      + resource_id                           = (known after apply)
      + skip_final_snapshot                   = true
      + snapshot_identifier                   = (known after apply)
      + status                                = (known after apply)
      + storage_throughput                    = (known after apply)
      + storage_type                          = (known after apply)
      + tags_all                              = (known after apply)
      + timezone                              = (known after apply)
      + username                              = "admin"
      + vpc_security_group_ids                = (known after apply)
    }
  # aws_db_instance.replica will be created
  + resource "aws_db_instance" "replica" {
      + address                               = (known after apply)
      + allocated_storage                     = (known after apply)
      + apply_immediately                     = false
      + arn                                   = (known after apply)
      + auto_minor_version_upgrade            = false
      + availability_zone                     = (known after apply)
      + backup_retention_period               = (known after apply)
      + backup_target                         = (known after apply)
      + backup_window                         = (known after apply)
      + ca_cert_identifier                    = (known after apply)
      + character_set_name                    = (known after apply)
      + copy_tags_to_snapshot                 = false
      + database_insights_mode                = (known after apply)
      + db_name                               = (known after apply)
      + db_subnet_group_name                  = (known after apply)
      + dedicated_log_volume                  = false
      + delete_automated_backups              = true
      + domain_fqdn                           = (known after apply)
      + endpoint                              = (known after apply)
      + engine                                = "mysql"
      + engine_lifecycle_support              = (known after apply)
      + engine_version                        = "8.0.42"
      + engine_version_actual                 = (known after apply)
      + hosted_zone_id                        = (known after apply)
      + id                                    = (known after apply)
      + identifier                            = "replica-db"
      + identifier_prefix                     = (known after apply)
      + instance_class                        = "db.t3.micro"
      + iops                                  = (known after apply)
      + kms_key_id                            = (known after apply)
      + latest_restorable_time                = (known after apply)
      + license_model                         = (known after apply)
      + listener_endpoint                     = (known after apply)
      + maintenance_window                    = (known after apply)
      + master_user_secret                    = (known after apply)
      + master_user_secret_kms_key_id         = (known after apply)
      + monitoring_interval                   = 0
      + monitoring_role_arn                   = (known after apply)
      + multi_az                              = (known after apply)
      + nchar_character_set_name              = (known after apply)
      + network_type                          = (known after apply)
      + option_group_name                     = (known after apply)
      + parameter_group_name                  = (known after apply)
      + password_wo                           = (write-only attribute)
      + performance_insights_enabled          = false
      + performance_insights_kms_key_id       = (known after apply)
      + performance_insights_retention_period = (known after apply)
      + port                                  = (known after apply)
      + publicly_accessible                   = false
      + replica_mode                          = (known after apply)
      + replicas                              = (known after apply)
      + replicate_source_db                   = (known after apply)
      + resource_id                           = (known after apply)
      + skip_final_snapshot                   = true
      + snapshot_identifier                   = (known after apply)
      + status                                = (known after apply)
      + storage_throughput                    = (known after apply)
      + storage_type                          = (known after apply)
      + tags_all                              = (known after apply)
      + timezone                              = (known after apply)
      + username                              = (known after apply)
      + vpc_security_group_ids                = (known after apply)
    }
Plan: 2 to add, 0 to change, 0 to destroy.

Migrating safely

If you're upgrading from v5.x to v6.x, here's the process I recommend:

  1. Update your provider version in versions.tf file to require AWS provider ≥ 6.0
  2. Run the following Terraform commands:
terraform plan -refresh-only
terraform apply -refresh-only
$ terraform plan -refresh-only
aws_db_instance.primary: Refreshing state... [id=db-WXYUIWJ46RE23CZEHCNYWCD6FY]
aws_db_instance.replica: Refreshing state... [id=db-WXYUIWJ46RE23CZEHCNYWCD6FY]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the
last "terraform apply" which may have affected this plan:

  # aws_db_instance.primary has changed
  ~ resource "aws_db_instance" "primary" {
        id                                    = "db-WXYUIWJ46RE23CZEHCNYWCD6FY"
      ~ latest_restorable_time                = "2025-08-21T19:35:00Z" -> "2025-08-21T20:05:00Z"
        tags                                  = {}
        # (72 unchanged attributes hidden)
    }

  # aws_db_instance.replica has changed
  ~ resource "aws_db_instance" "replica" {
        id                                    = "db-WXYUIWJ46RE23CZEHCNYWCD6FY"
      ~ latest_restorable_time                = "2025-08-21T20:00:01Z" -> "2025-08-21T20:05:00Z"
        tags                                  = {}
        # (70 unchanged attributes hidden)
    }
This is a refresh-only plan, so Terraform will not take any actions to undo
these. If you were expecting these changes then you can apply this plan to
record the updated values in the Terraform state without changing any remote
objects.
$ terraform apply -refresh-only
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

This ensures the Terraform's state is up to date.

3. Replace provider aliases with region arguments in each resource.

4. Run a terraform plan to confirm the migration didn't introduce unexpected changes.

$ terraform plan
aws_db_instance.primary: Refreshing state... [id=db-WXYUIWJ46RE23CZEHCNYWCD6FY]
aws_db_instance.replica: Refreshing state... [id=db-IVNMUQOHWDADWCPZVIDDXEMDMQ]
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
None
Replication tab showing the primary and replica relationship across regions

Final Thoughts

The Terraform AWS provider 6.0 release may feel incremental, but the region-per-resource model is a game changer for teams running workloads across multiple AWS regions.

It reduces duplication and makes configurations easier to read, maintain, and lowers the barrier to adopting best practices like regional fail over or compliance-driven data residency.

If you've been stuck managing dozens of provider aliases, now's the time to simplify your workflows and migrate to v6.0. Your future self and your teammates will thank you.

If you found this guide helpful, feel free to give it a clap and share it. For more articles consider making a follow on my account.