IntermediateCloud & Security 7 min read

How to Configure a Terraform Remote Backend (S3 + DynamoDB)

Secure your Terraform state with S3 and DynamoDB — essential for team collaboration and exam prep.

Terraform remote backends are critical for team-based infrastructure management. They store state files remotely, enable state locking to prevent concurrent modifications, and provide versioning for rollback. This guide walks through configuring an S3 bucket as the state store and a DynamoDB table for locking. You'll use real AWS CLI commands to create the resources, then configure Terraform to use them. By the end, you'll have a production-ready backend setup that aligns with HashiCorp and AWS DevOps best practices.

1

Create the S3 Bucket for State Storage

First, create an S3 bucket with versioning enabled. Use a globally unique name and block public access. The bucket will store your Terraform state files securely.

AWS CLI
aws s3api create-bucket --bucket my-terraform-state-2025 --region us-east-1
aws s3api put-bucket-versioning --bucket my-terraform-state-2025 --versioning-configuration Status=Enabled
aws s3api put-public-access-block --bucket my-terraform-state-2025 --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

Use a consistent naming convention like <project>-terraform-state-<env> to avoid bucket name collisions.

Bucket names must be globally unique across all AWS accounts. Test with a unique suffix.

2

Create the DynamoDB Table for State Locking

Create a DynamoDB table with a primary key named 'LockID' (type String). This table prevents concurrent Terraform runs from corrupting the state file.

AWS CLI
aws dynamodb create-table --table-name terraform-locks --attribute-definitions AttributeName=LockID,AttributeType=S --key-schema AttributeName=LockID,KeyType=HASH --billing-mode PAY_PER_REQUEST

Use on-demand billing to avoid provisioning unnecessary capacity for low-traffic lock operations.

3

Configure IAM Permissions for Terraform

Attach an IAM policy to the Terraform user or role granting minimal permissions to access S3 and DynamoDB. This follows the principle of least privilege.

IAM Policy JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::my-terraform-state-2025",
        "arn:aws:s3:::my-terraform-state-2025/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/terraform-locks"
    }
  ]
}

Never use wildcard resources in production. Scope permissions to the specific bucket and table ARNs.

4

Write the Terraform Backend Configuration

Add a backend block to your Terraform configuration. This tells Terraform to store state in S3 and use DynamoDB for locking. Replace placeholders with your actual resource names.

HCL (Terraform)
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-2025"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

Use a key structure like <environment>/<project>/terraform.tfstate to organize multiple state files in one bucket.

5

Initialize the Backend and Migrate State

Run terraform init to initialize the backend. If you have an existing local state, Terraform will prompt you to migrate it to the remote backend. Confirm to proceed.

Terraform CLI Output
terraform init

Initializing the backend...
Do you want to copy existing state to the new backend?
  Enter a value: yes

Successfully configured the backend "s3"! Terraform will automatically
use this backend for all subsequent operations.

Always run terraform init after changing the backend configuration to ensure the new backend is properly configured.

6

Verify State Locking with a Concurrent Run

Test the locking mechanism by running terraform apply in one terminal and terraform plan in another. The second command should fail with a locking error.

Terraform CLI Output
# Terminal 1:
terraform apply -auto-approve

# Terminal 2 (simultaneously):
terraform plan

Error: Error acquiring the state lock

Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
  ID:        abc123
  Path:      my-terraform-state-2025/prod/terraform.tfstate
  Operation: OperationTypeApply
  Who:       user@example.com
  Version:   1.6.0
  Created:   2025-03-15 10:30:00.123456789 +0000 UTC

If a lock is stuck, use 'terraform force-unlock <LOCK_ID>' to release it. Use this sparingly and only after verifying no other process is running.

7

Enable State File Encryption and Lifecycle Rules

Add server-side encryption to the S3 bucket and configure lifecycle rules to expire old state versions. This enhances security and reduces storage costs.

AWS CLI
aws s3api put-bucket-encryption --bucket my-terraform-state-2025 --server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

aws s3api put-bucket-lifecycle-configuration --bucket my-terraform-state-2025 --lifecycle-configuration '{"Rules":[{"ID":"Expire-old-versions","Status":"Enabled","NoncurrentVersionExpiration":{"NoncurrentDays":90}}]}'

Combine SSE-S3 with DynamoDB encryption at rest for a defense-in-depth approach to state security.

Key tips

  • Always use a dedicated AWS account or separate IAM role for Terraform operations to limit blast radius in case of credential leaks.

  • Enable S3 bucket logging and DynamoDB CloudTrail data events to audit all state access and lock operations.

  • Use Terraform workspaces with the S3 backend to manage multiple environments (dev, staging, prod) without duplicating backend config.

  • Store the backend configuration in a separate file (e.g., backend.hcl) and use partial configuration with -backend-config flags for CI/CD pipelines.

  • Test your backend setup in a non-production environment first to avoid accidental state corruption in critical infrastructure.

  • Consider using Terraform Cloud or Terraform Enterprise for built-in state management, VCS integration, and team collaboration features.

Frequently asked questions

What happens if I lose the DynamoDB lock table?

If the DynamoDB table is deleted, Terraform will fail to acquire a lock and operations will halt. You must recreate the table with the same name and primary key 'LockID'. Existing state files in S3 remain intact, but you lose locking until the table is restored.

Can I use the same S3 bucket for multiple Terraform projects?

Yes, by using unique 'key' paths for each project (e.g., project-a/terraform.tfstate, project-b/terraform.tfstate). Ensure each project uses a separate DynamoDB lock table or the same table — the LockID includes the state file path, so collisions are avoided.

How do I migrate from a local backend to S3 without downtime?

Run 'terraform init' with the new backend configuration. Terraform will detect the existing local state and prompt you to migrate it to S3. Confirm 'yes' to copy the state. No downtime occurs as long as no other Terraform operations are running during migration.

Is it safe to store secrets in Terraform state files?

No. Terraform state can contain sensitive data like passwords and API keys. Always enable S3 encryption at rest, restrict IAM access, and consider using a secrets manager (e.g., AWS Secrets Manager) to inject sensitive values dynamically.

What is the difference between 'terraform init' and 'terraform plan' with a remote backend?

'terraform init' configures the backend and downloads provider plugins. 'terraform plan' reads the current state from the remote backend and compares it with your configuration to generate an execution plan. Both require backend access, but only 'init' sets up the connection.

Practice with real exam questions

Apply what you just learned with exam-style practice questions.

Related guides