The InvalidChangeBatch error occurs when Route53 rejects your Terraform resource record set changes due to conflicts, validation issues, or mismatched values. This guide covers the most common causes and solutions.
The InvalidChangeBatch error is returned by AWS Route53 when you attempt to create, update, or delete DNS records through Terraform, but the requested changes violate Route53's validation rules. This is a client-side error returned by the AWS API, indicating that your change batch request is malformed or conflicts with existing records in your hosted zone. Route53 validates every change batch against several rules: duplicate records, conflicting record types, invalid domain names, mismatched values during deletion, and DNS naming conventions (like CNAME restrictions at zone apex). When any validation fails, Route53 rejects the entire batch and returns this error.
First, check what records actually exist in Route53. Use the AWS CLI to inspect your hosted zone:
aws route53 list-resource-record-sets --hosted-zone-id YOUR_ZONE_IDCompare this output against the records you're trying to create in your Terraform configuration. Look for:
- Records with the exact same name and type as what you're creating
- Conflicting record types (CNAME mixed with A records)
- Values that differ from what Terraform expects to delete
This will reveal if the record already exists or has been modified outside Terraform.
Ensure your zone_id references the correct Route53 hosted zone:
resource "aws_route53_record" "example" {
zone_id = aws_route53_zone.main.zone_id # Must point to your actual zone
name = "example.com"
type = "A"
ttl = 300
records = ["192.0.2.1"]
}If using a hosted zone created outside Terraform, get the correct ID:
aws route53 list-hosted-zones-by-name --dns-name example.comUsing the wrong zone ID will cause InvalidChangeBatch errors when the record name doesn't exist in that zone.
Route53 doesn't allow CNAME records to coexist with other records at the same domain name. If you see a conflict error:
For zone apex (example.com):
- Don't create CNAME records - use alias records instead
- Alias records have alias block instead of records
# Wrong - CNAME at apex not allowed
resource "aws_route53_record" "apex" {
zone_id = aws_route53_zone.main.zone_id
name = "example.com"
type = "CNAME"
ttl = 300
records = ["target.example.com"] # ❌ INVALID
}
# Correct - use alias for apex
resource "aws_route53_record" "apex" {
zone_id = aws_route53_zone.main.zone_id
name = "example.com"
type = "A"
alias {
name = aws_cloudfront_distribution.main.domain_name
zone_id = aws_cloudfront_distribution.main.hosted_zone_id
evaluate_target_health = false
}
}For subdomains (www.example.com):
- If a CNAME exists, remove it before adding A, MX, or TXT records
- Or import the CNAME and let Terraform manage it
terraform import aws_route53_record.www ZONEID_www.example.com._CNAMEIf the record already exists in Route53 but isn't in your Terraform state, import it:
# Format: terraform import aws_route53_record.NAME ZONEID_RECORDNAME_TYPE
# Example: import A record for example.com
terraform import aws_route53_record.example Z1234567890ABC_example.com_A
# Example: import CNAME record for www.example.com
terraform import aws_route53_record.www Z1234567890ABC_www.example.com_CNAMEGet your zone ID with:
aws route53 list-hosted-zones-by-name --dns-name example.com --query 'HostedZones[0].Id' --output text | cut -d'/' -f3After importing, verify Terraform detects no changes:
terraform planWhen deleting records, Terraform must match the exact values that currently exist. If the error occurs during destroy:
# View what Terraform thinks should be deleted
terraform state show aws_route53_record.exampleEnsure the values match Route53 exactly:
- TTL must match (default is usually 300)
- Record values must be identical
- For weighted/failover records, the set identifier must match
If values don't match, refresh Terraform state:
terraform refresh
terraform plan # See what it now detectsIf this doesn't resolve it, you may need to manually delete the record in AWS and re-import:
aws route53 change-resource-record-sets --hosted-zone-id ZONE_ID \
--change-batch file://delete-change.jsonThen re-import:
terraform import aws_route53_record.example ZONE_ID_name_TYPERoute53 has strict DNS naming requirements:
resource "aws_route53_record" "example" {
zone_id = aws_route53_zone.main.zone_id
# name: Each label must be 63 chars or less
# Total length including dots max 255 chars
name = "very-long-label-exceeding-63-characters-will-fail.example.com" # ❌ BAD
name = "valid-label-under-63-chars.example.com" # ✅ GOOD
type = "A"
ttl = 300
records = ["192.0.2.1"]
}Check for common issues:
- No empty labels (double dots: example..com)
- No trailing dots in the name variable (Route53 auto-adds them)
- All labels are lowercase alphanumeric, hyphens (no underscores in most cases)
If you see "DomainLabelTooLong" or "DomainLabelEmpty" in the error, fix the domain name format.
If a record exists in Route53 and you want Terraform to take over, use the allow_overwrite parameter:
resource "aws_route53_record" "example" {
zone_id = aws_route53_zone.main.zone_id
name = "example.com"
type = "A"
ttl = 300
records = ["192.0.2.1"]
allow_overwrite = true # Allow overwriting existing records
}This tells Terraform to replace the record if it differs, rather than failing with InvalidChangeBatch. Use with caution if the record is managed outside Terraform.
State Synchronization: InvalidChangeBatch errors often indicate your Terraform state is out of sync with actual Route53 records. This can happen if records are created/modified outside Terraform. Always use terraform state show and terraform refresh to verify state matches reality.
Record Set Ordering: When using weighted routing, failover routing, or geolocation routing, all records with the same name and type must have consistent set identifiers. Mismatched set identifiers in a change batch will cause InvalidChangeBatch errors.
Alias Records vs. Standard Records: AWS resources like CloudFront distributions, ALBs, and API Gateways must use alias records (which have no TTL), not standard A/AAAA records. Mixing record types for the same name will fail.
DNS Propagation: Even after resolving InvalidChangeBatch and applying successfully, Route53 changes may take a few minutes to propagate. During this window, DNS queries might still return old values.
Terraform Provider Version: Ensure you're using a recent aws provider version. Some InvalidChangeBatch scenarios have been improved in newer releases:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Use recent stable version
}
}
}Error: Error installing helm release: cannot re-use a name that is still in use
How to fix "release name in use" error in Terraform with Helm
Error: Error creating GKE Cluster: BadRequest
BadRequest error creating GKE cluster in Terraform
Error: External program failed to produce valid JSON
External program failed to produce valid JSON
Error: Unsupported argument in child module call
How to fix "Unsupported argument in child module call" in Terraform
Error: network is unreachable
How to fix "network is unreachable" in Terraform