Skip to main content
Fix Terraform Error: Elastic IP AddressLimitExceeded

Fix Terraform Error: Elastic IP AddressLimitExceeded

Key Takeaway

Fix AWS AddressLimitExceeded when creating Elastic IPs in Terraform. Release unused EIPs, request quota increase, audit per-region usage, and reduce EIP consumption with ALBs.

Table of Contents

Quick Answer

# Find unused EIPs and release them
aws ec2 describe-addresses --query 'Addresses[?AssociationId==`null`].[AllocationId,PublicIp]' --output table

# Release an unused EIP
aws ec2 release-address --allocation-id eipalloc-0abc1234

The Error

Error: creating EC2 EIP: AddressLimitExceeded: The maximum number of addresses has been reached.
  status code: 400

Error: creating NAT Gateway: AddressLimitExceeded: The maximum number of addresses has been reached.

What Causes This

AWS limits Elastic IPs to 5 per region by default. Every NAT Gateway, bastion host, and public-facing instance with a static IP consumes one.

Common EIP consumers:

  • NAT Gateways — 1 EIP each (multi-AZ = 2-3 EIPs)
  • Bastion hosts — 1 EIP each
  • Direct-attached public instances — 1 EIP each
  • VPN endpoints — 1 EIP each
  • Orphaned EIPs — released instances leave EIPs behind

Solution 1: Release Unused Elastic IPs

# List ALL EIPs in the region with association status
aws ec2 describe-addresses \
  --query 'Addresses[].{IP:PublicIp,AllocID:AllocationId,AssocID:AssociationId,Instance:InstanceId}' \
  --output table
-------------------------------------------------------------------
|                      DescribeAddresses                          |
+------------------+------------------+--------+------------------+
|    AllocID       |    AssocID       |  IP    |  Instance        |
+------------------+------------------+--------+------------------+
| eipalloc-aaa111  | eipassoc-bbb222  | 1.2.3.4| i-0abc123       |
| eipalloc-ccc333  | None             | 5.6.7.8| None            | ← Unused
| eipalloc-ddd444  | None             | 9.0.1.2| None            | ← Unused
+------------------+------------------+--------+------------------+

Release the unused ones:

# Release unused EIPs
aws ec2 release-address --allocation-id eipalloc-ccc333
aws ec2 release-address --allocation-id eipalloc-ddd444

Or release ALL unused EIPs:

aws ec2 describe-addresses \
  --query 'Addresses[?AssociationId==null].AllocationId' \
  --output text | xargs -n1 aws ec2 release-address --allocation-id

Solution 2: Request a Quota Increase

# Check current EIP quota
aws service-quotas get-service-quota \
  --service-code ec2 \
  --quota-code L-0263D0A3

# Request increase to 20
aws service-quotas request-service-quota-increase \
  --service-code ec2 \
  --quota-code L-0263D0A3 \
  --desired-value 20

Or through the AWS Console: Service Quotas → EC2 → Elastic IP addresses → Request increase

Typical approval: 1-3 business days for moderate increases.

Solution 3: Reduce EIP Usage

Use Single NAT Gateway (Non-Production)

# Instead of 3 NAT Gateways (3 EIPs):
resource "aws_nat_gateway" "single" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id
}

resource "aws_eip" "nat" {
  domain = "vpc"
}

# Route all private subnets through one NAT
resource "aws_route" "private" {
  count                  = length(aws_subnet.private)
  route_table_id         = aws_route_table.private[count.index].id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.single.id
}

One NAT Gateway instead of three saves 2 EIPs + ~$65/month.

Use ALB Instead of Per-Instance EIPs

# Instead of EIP per instance, put them behind an ALB
resource "aws_lb" "web" {
  name               = "web-alb"
  load_balancer_type = "application"
  subnets            = aws_subnet.public[*].id
}

# Instances in private subnets — no EIPs needed
resource "aws_instance" "web" {
  count     = 3
  subnet_id = aws_subnet.private[count.index % length(aws_subnet.private)].id
  # No EIP needed
}

Use SSM Instead of Bastion + EIP

# No bastion host needed — connect via SSM Session Manager
resource "aws_iam_instance_profile" "ssm" {
  role = aws_iam_role.ssm.name
}

resource "aws_instance" "web" {
  iam_instance_profile = aws_iam_instance_profile.ssm.name
  # No EIP, no public subnet needed
}
aws ssm start-session --target i-0abc123

Solution 4: Audit EIP Usage Across Regions

# Check all regions
for region in us-east-1 us-west-2 eu-west-1 ap-southeast-1; do
  count=$(aws ec2 describe-addresses --region $region --query 'length(Addresses)')
  echo "$region: $count EIPs"
done

Solution 5: Manage EIPs in Terraform

Track EIPs to prevent orphaning:

resource "aws_eip" "nat" {
  count  = var.az_count
  domain = "vpc"

  tags = {
    Name = "nat-${count.index}"
  }

  lifecycle {
    # Prevent accidental EIP release (keeps your static IP)
    prevent_destroy = true
  }
}

Hands-On Courses

Conclusion

AWS limits Elastic IPs to 5 per region. Release unused EIPs first (aws ec2 describe-addresses to find them), then reduce consumption: use a single NAT Gateway for non-production, ALB instead of per-instance EIPs, and SSM instead of bastion hosts. Request a quota increase if you genuinely need more.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.