AWS Public IP vs Elastic IP: Common Mistakes and Best Practices 2025
The difference between an AWS public IP and an Elastic IP lies in persistence and control. A public IP is auto-assigned and changes when you stop/start an instance, while an Elastic IP is a static, user-managed public IP that you can reassign across instances.
Public IP = temporary, auto-assigned ➜ Elastic IP = persistent, user-controlled.
Table of Contents
Introduction: Why IP Stability Matters in Cloud Infrastructure
When you launch an EC2 instance in AWS, you’re faced with a critical networking decision: should you use the automatically assigned public IP address, or allocate an Elastic IP? This seemingly simple choice has profound implications for your application’s availability, DNS configuration, cost structure, and operational complexity.
Many AWS engineers and DevOps professionals struggle with this distinction, leading to:
- Broken DNS records when instances are stopped and restarted
- Unexpected monthly charges from forgotten Elastic IPs
- Overcomplicated architectures using static IPs when dynamic solutions would suffice
- Missed opportunities for high-availability failover mechanisms
This comprehensive guide clarifies the AWS public IP vs Elastic IP debate with practical examples, real-world scenarios, and copy-paste-ready code snippets. Whether you’re preparing for AWS certification, architecting production systems, or optimizing your cloud infrastructure costs, you’ll find actionable insights here.
What you’ll learn:
- The fundamental differences between AWS public IP and Elastic IP addresses
- Exact AWS CLI, Terraform, and CloudFormation configurations
- Cost implications and quota management strategies
- Best practices for production workloads and high-availability setups
- When to avoid static IPs entirely in favor of modern alternatives
Background: AWS Networking & IP Address Fundamentals
Before diving into the AWS public IP vs Elastic IP comparison, let’s establish the networking foundation.
Private IP, VPC, and Subnets
Every EC2 instance receives a private IP address from your VPC’s CIDR block. This IP enables communication within your VPC but is not routable from the internet. For example, an instance in a VPC with CIDR 10.0.0.0/16 might receive private IP 10.0.1.45.
Here is the detailed Guide on Private IP and Public IP
Internet Gateway and Public Connectivity
To enable internet access, your VPC needs an Internet Gateway (IGW) attached. Instances in public subnets (subnets with routes to the IGW) can receive public IP addresses for inbound and outbound internet communication.
┌─────────────────────────────────────────────────┐
│ Internet │
└──────────────────┬──────────────────────────────┘
│
┌───────▼────────┐
│ Internet Gateway│
└───────┬────────┘
│
┌──────────────────▼──────────────────────────────┐
│ VPC (10.0.0.0/16) │
│ ┌──────────────────────────────────────────┐ │
│ │ Public Subnet (10.0.1.0/24) │ │
│ │ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ EC2 Instance │ │ │
│ │ │ Private IP: 10.0.1.45 │ │ │
│ │ │ Public IP: 54.x.x.x │ │ │
│ │ └─────────────────────────┘ │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
How NAT and Public IP Mapping Works
AWS uses 1:1 NAT (Network Address Translation) to map public IPs to private IPs. When traffic arrives at a public IP, the IGW translates it to the instance’s private IP. The instance itself is unaware of its public IP—it only sees its private IP on its network interface.
AWS Public IP (Auto-Assigned) Explained
What is an AWS Public IP Address?
An AWS public IP address is automatically assigned by AWS when you launch an EC2 instance in a public subnet with the “Auto-assign Public IP” setting enabled. This IP provides internet connectivity but comes with significant limitations.
Lifecycle and Behavior
Key characteristics of AWS public IP addresses:
- Assigned at launch: The public IP is allocated from AWS’s IP pool when the instance starts
- Released on stop: When you stop (not reboot) an instance, the public IP is released back to AWS
- New IP on start: When you start the stopped instance, AWS assigns a different public IP
- Released on termination: The IP is permanently released when the instance is terminated
- Non-transferable: You cannot move the public IP to another instance
When AWS Assigns Public IPs
Public IP assignment happens automatically if:
- The instance is launched in a public subnet (subnet with IGW route)
- The subnet has “Auto-assign public IPv4 address” enabled, OR
- You explicitly enable it at launch time
Limitations and Trade-offs
Critical limitations:
- No persistence: Stopping and starting the instance changes the IP
- No DNS stability: You cannot reliably point DNS records to a changing IP
- No failover capability: You cannot quickly reassign the IP to a replacement instance
- No control: AWS manages the IP lifecycle entirely
When public IP works well:
- Development and testing environments
- Ephemeral workloads (spot instances, batch processing)
- Instances behind load balancers (where the LB has a stable endpoint)
- Learning and experimentation

AWS CLI Example: Viewing Public IP
# Describe instances and view public IP
aws ec2 describe-instances \
--instance-ids i-1234567890abcdef0 \
--query 'Reservations[0].Instances[0].{PrivateIP:PrivateIpAddress,PublicIP:PublicIpAddress,State:State.Name}' \
--output table
# Output example:
# -----------------------------------------
# | DescribeInstances |
# +-------------+--------------+----------+
# | PrivateIP | PublicIP | State |
# +-------------+--------------+----------+
# | 10.0.1.45 | 54.123.45.67| running |
# +-------------+--------------+----------+
# Check if auto-assign public IP is enabled for a subnet
aws ec2 describe-subnets \
--subnet-ids subnet-12345678 \
--query 'Subnets[0].MapPublicIpOnLaunch'
Real-World Public IP Scenario
Problem: A developer launches an EC2 instance with auto-assigned public IP 54.123.45.67, tests their application, and updates DNS to point app.example.com to this IP. The next day, they stop the instance to save costs. When they restart it, the instance receives 52.98.76.54, and the DNS record is now broken.
Lesson: Never rely on AWS public IP addresses for DNS records or any configuration requiring IP stability.
Elastic IP (EIP) Explained: Your Static Public IPv4 Address
What is an AWS Elastic IP?
An Elastic IP (EIP) is a static, public IPv4 address that you allocate from AWS’s address pool and manage independently of EC2 instances. Unlike auto-assigned public IPs, Elastic IPs remain associated with your AWS account until you explicitly release them.
How Elastic IP Works
Core concepts:
- Allocation: Request an Elastic IP from AWS (from Amazon’s pool or your own BYOIP range)
- Association: Attach the EIP to an EC2 instance or network interface
- Reassociation: Move the EIP between instances instantly
- Disassociation: Detach the EIP while keeping it allocated
- Release: Return the EIP to AWS’s pool
Allocating an Elastic IP with AWS CLI
# Allocate an Elastic IP in VPC
aws ec2 allocate-address --domain vpc
# Output:
# {
# "PublicIp": "203.0.113.25",
# "AllocationId": "eipalloc-abc123def456",
# "Domain": "vpc",
# "NetworkBorderGroup": "us-east-1"
# }
# Allocate with tags for better management
aws ec2 allocate-address \
--domain vpc \
--tag-specifications 'ResourceType=elastic-ip,Tags=[{Key=Name,Value=prod-web-server},{Key=Environment,Value=production}]'
Associating an Elastic IP with an Instance
# Associate EIP with an EC2 instance
aws ec2 associate-address \
--instance-id i-1234567890abcdef0 \
--allocation-id eipalloc-abc123def456
# Output:
# {
# "AssociationId": "eipassoc-xyz789"
# }
# Associate EIP with a specific network interface
aws ec2 associate-address \
--network-interface-id eni-11223344 \
--allocation-id eipalloc-abc123def456 \
--private-ip-address 10.0.1.45
Viewing and Managing Elastic IPs
# List all Elastic IPs in your account
aws ec2 describe-addresses \
--query 'Addresses[*].{IP:PublicIp,AllocationId:AllocationId,AssociatedInstance:InstanceId,State:Association.PublicIp}' \
--output table
# Find unattached (orphaned) Elastic IPs
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null].{IP:PublicIp,AllocationId:AllocationId}' \
--output table
# Disassociate an Elastic IP
aws ec2 disassociate-address \
--association-id eipassoc-xyz789
# Release an Elastic IP (returns it to AWS)
aws ec2 release-address \
--allocation-id eipalloc-abc123def456
Elastic IP Reassociation for Failover
One of the most powerful features of Elastic IPs is instant reassociation. If an instance fails, you can immediately move the EIP to a standby instance:
# Move EIP from failed instance to backup instance
aws ec2 associate-address \
--instance-id i-backup-instance-id \
--allocation-id eipalloc-abc123def456 \
--allow-reassociation
This operation typically completes in seconds, minimizing downtime. DNS records pointing to the EIP continue to work without updates.
Terraform Configuration for Elastic IP
# Allocate an Elastic IP
resource "aws_eip" "web_server" {
domain = "vpc"
tags = {
Name = "prod-web-server-eip"
Environment = "production"
ManagedBy = "terraform"
}
}
# Associate EIP with an EC2 instance
resource "aws_eip_association" "web_server" {
instance_id = aws_instance.web_server.id
allocation_id = aws_eip.web_server.id
}
# Example EC2 instance
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
subnet_id = aws_subnet.public.id
tags = {
Name = "prod-web-server"
}
}
# Output the Elastic IP address
output "elastic_ip" {
value = aws_eip.web_server.public_ip
description = "The Elastic IP assigned to the web server"
}
CloudFormation Template for Elastic IP
AWSTemplateFormatVersion: '2010-09-09'
Description: 'EC2 instance with Elastic IP'
Resources:
WebServerEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: prod-web-server-eip
- Key: Environment
Value: production
WebServerEIPAssociation:
Type: AWS::EC2::EIPAssociation
Properties:
InstanceId: !Ref WebServerInstance
EIP: !Ref WebServerEIP
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0c55b159cbfafe1f0
InstanceType: t3.medium
SubnetId: !Ref PublicSubnet
Tags:
- Key: Name
Value: prod-web-server
Outputs:
ElasticIP:
Description: The Elastic IP address
Value: !Ref WebServerEIP
Export:
Name: !Sub '${AWS::StackName}-EIP'
Elastic IP Costs and Quotas
Cost structure (as of 2025):
- Free when associated: No charge for an EIP attached to a running instance
- $0.005 per hour when idle: Charged when allocated but not associated (~$3.60/month)
- $0.005 per hour for additional IPs: Charged for EIPs beyond the first one per running instance
- No data transfer charges: EIP itself doesn’t incur data transfer costs
Default quotas per region:
- 5 Elastic IPs per region (soft limit)
- Can request increase through AWS Service Quotas
- Some accounts may have higher limits based on usage history
Cost optimization tip: Always release unused Elastic IPs. A forgotten, unattached EIP costs ~$43.20 per year.
# Find and calculate cost of orphaned EIPs
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null]' \
--output json | jq 'length as $count | "Orphaned EIPs: \($count), Monthly cost: $\($count * 3.60)"'
AWS Public IP vs Elastic IP: Detailed Comparison and Decision Guide
Side-by-Side Comparison Table
| Feature | AWS Public IP | Elastic IP |
|---|---|---|
| Persistence | Changes on stop/start | Static until released |
| Control | AWS-managed | User-managed |
| Cost | Included in instance cost | Free when attached, $0.005/hr when idle |
| Transferability | Cannot move between instances | Can reassociate instantly |
| DNS Suitability | Not recommended | Excellent for DNS records |
| Quota | Unlimited (per subnet setting) | 5 per region (default) |
| Failover Support | No | Yes (instant reassociation) |
| IPv6 Support | Auto-assigned IPv6 available | Separate IPv6 CIDR handling |
| Best for | Dev/test, ephemeral workloads | Production, DNS, HA setups |
| Release on Terminate | Automatic | Manual (unless using CloudFormation with DeletionPolicy) |
Use Case Scenarios and Recommendations
Scenario 1: Development and Testing Environments
Recommendation: Use AWS auto-assigned public IP
Development environments frequently start and stop. The IP change isn’t problematic because:
- Developers connect via AWS Session Manager or SSH using the dynamic IP
- No DNS records point to dev instances
- Cost savings from stopping instances outweigh EIP benefits
# Launch dev instance with auto-assigned public IP
aws ec2 run-instances \
--image-id ami-0c55b159cbfafe1f0 \
--instance-type t3.micro \
--subnet-id subnet-12345678 \
--associate-public-ip-address \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=dev-test-server},{Key=Environment,Value=development}]'
Scenario 2: Production Web Servers
Recommendation: Use Elastic IP
Production servers require stable DNS records. An EIP enables:
- Predictable DNS configuration
- Quick disaster recovery (reassign to backup instance)
- Whitelisting in third-party firewalls
# Production setup with Elastic IP
ALLOCATION=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
INSTANCE=$(aws ec2 run-instances --image-id ami-0c55b159cbfafe1f0 --instance-type t3.medium --subnet-id subnet-12345678 --query 'Instances[0].InstanceId' --output text)
aws ec2 associate-address --instance-id $INSTANCE --allocation-id $ALLOCATION
Scenario 3: High Availability and Failover
Recommendation: Use Elastic IP with automation
For critical applications requiring minimal downtime:
#!/bin/bash
# Failover script: Move EIP from failed primary to standby
PRIMARY_INSTANCE="i-primary123"
STANDBY_INSTANCE="i-standby456"
EIP_ALLOCATION="eipalloc-abc123"
# Check primary instance health
INSTANCE_STATE=$(aws ec2 describe-instances --instance-ids $PRIMARY_INSTANCE --query 'Reservations[0].Instances[0].State.Name' --output text)
if [ "$INSTANCE_STATE" != "running" ]; then
echo "Primary instance failed. Failing over to standby..."
aws ec2 associate-address \
--instance-id $STANDBY_INSTANCE \
--allocation-id $EIP_ALLOCATION \
--allow-reassociation
echo "Failover complete. EIP now points to standby instance."
fi
Scenario 4: Applications Behind Load Balancers
Recommendation: Use public IP (or no public IP with NAT Gateway)
When instances sit behind an Application Load Balancer or Network Load Balancer:
- The load balancer provides the stable endpoint
- Individual instances don’t need Elastic IPs
- Consider private subnets with NAT Gateway for outbound traffic
# Terraform: Instances behind ALB don't need EIPs
resource "aws_instance" "app_server" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
subnet_id = element(aws_subnet.private.*.id, count.index)
# No public IP needed - traffic comes through ALB
associate_public_ip_address = false
tags = {
Name = "app-server-${count.index + 1}"
}
}
resource "aws_lb" "app" {
name = "app-alb"
internal = false
load_balancer_type = "application"
subnets = aws_subnet.public.*.id
}
Scenario 5: NAT Instances (Outdated Pattern)
Recommendation: Use AWS NAT Gateway instead
While you could use an EIP with a NAT instance, AWS NAT Gateway is the modern, managed alternative:
- NAT Gateway automatically uses an Elastic IP
- Highly available within an AZ
- No instance management overhead
# Create NAT Gateway with Elastic IP
ALLOCATION=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
aws ec2 create-nat-gateway \
--subnet-id subnet-12345678 \
--allocation-id $ALLOCATION \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=prod-nat-gateway}]'
Decision Framework: Public IP or Elastic IP?
Use this decision tree:
Do you need a public IP at all?
├─ No (private subnet with NAT Gateway) → Best option
└─ Yes
└─ Is the instance behind a load balancer?
├─ Yes → Use public IP or no public IP
└─ No
└─ Do you need DNS to point to this IP?
├─ Yes → Use Elastic IP
└─ No
└─ Is this production?
├─ Yes → Use Elastic IP (for failover flexibility)
└─ No → Use public IP
Default recommendation: Use AWS auto-assigned public IP unless you specifically need persistence, transferability, or DNS stability. When in doubt for production workloads, allocate an Elastic IP.
Common Pitfalls and AWS IP Best Practices
Pitfall 1: Forgotten Elastic IPs Driving Up Costs
Problem: Unattached Elastic IPs accumulate charges. A single forgotten EIP costs $3.60/month or $43.20/year.
Solution: Implement regular audits and automated cleanup.
# Script to find and alert on orphaned EIPs
#!/bin/bash
ORPHANED=$(aws ec2 describe-addresses --query 'Addresses[?AssociationId==null].AllocationId' --output text)
if [ -n "$ORPHANED" ]; then
echo "WARNING: Orphaned Elastic IPs found:"
echo "$ORPHANED"
COST=$(echo "$ORPHANED" | wc -w | awk '{print $1 * 3.60}')
echo "Monthly cost: \$$COST"
# Option: Automatically release after confirmation
# for alloc in $ORPHANED; do
# aws ec2 release-address --allocation-id $alloc
# done
fi
Best practice: Use AWS Config rules or AWS Cost Anomaly Detection to alert on unattached EIPs.
Pitfall 2: Using Public IPs in DNS Records
Problem: DNS records pointing to auto-assigned public IPs break when instances restart.
Solution: Always use Elastic IPs for DNS, or better yet, use Route 53 with load balancers.
# Bad: DNS pointing to auto-assigned public IP
# www.example.com → 54.123.45.67 (changes on restart)
# Better: DNS pointing to Elastic IP
# www.example.com → 203.0.113.25 (stable)
# Best: Route 53 alias to Application Load Balancer
# www.example.com → ALIAS myalb-123456.us-east-1.elb.amazonaws.com
Pitfall 3: Hardcoding IPs in Application Configuration
Problem: Applications with hardcoded IP addresses become brittle and difficult to maintain.
Solution: Use DNS names, AWS Systems Manager Parameter Store, or service discovery.
# Bad: Hardcoded IP
DATABASE_HOST = "203.0.113.25"
# Better: Environment variable
import os
DATABASE_HOST = os.environ.get('DATABASE_HOST')
# Best: Service discovery or DNS
import boto3
ssm = boto3.client('ssm')
param = ssm.get_parameter(Name='/prod/database/endpoint')
DATABASE_HOST = param['Parameter']['Value']
Pitfall 4: Not Tagging Elastic IPs
Problem: In large AWS accounts, untagged EIPs become “mystery resources” that no one wants to touch.
Solution: Enforce tagging policies at allocation time.
# Always tag Elastic IPs
aws ec2 allocate-address \
--domain vpc \
--tag-specifications 'ResourceType=elastic-ip,Tags=[
{Key=Name,Value=prod-api-server},
{Key=Environment,Value=production},
{Key=CostCenter,Value=engineering},
{Key=Owner,Value=devops-team},
{Key=CreatedBy,Value=terraform}
]'
Best Practice 1: Use Elastic Load Balancers Instead of Static IPs
For most production workloads, avoid exposing individual instance IPs entirely:
Benefits of load balancer approach:
- Automatic health checks and failover
- SSL/TLS termination
- Multi-AZ high availability
- No need to manage Elastic IPs
- Better scalability (add/remove instances transparently)
# Modern architecture: ALB instead of Elastic IP
resource "aws_lb" "app" {
name = "prod-app-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public.*.id
}
# Route 53 points to ALB, not individual instances
resource "aws_route53_record" "app" {
zone_id = aws_route53_zone.main.zone_id
name = "app.example.com"
type = "A"
alias {
name = aws_lb.app.dns_name
zone_id = aws_lb.app.zone_id
evaluate_target_health = true
}
}
Best Practice 2: Monitor and Alert on EIP Quotas
AWS limits you to 5 Elastic IPs per region by default. Monitor usage to avoid hitting limits:
# Check current EIP usage across all regions
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
count=$(aws ec2 describe-addresses --region $region --query 'length(Addresses)' --output text 2>/dev/null || echo 0)
echo "$region: $count/5 Elastic IPs used"
done
Best Practice 3: Document and Maintain an IP Inventory
Create a central inventory of your Elastic IPs:
# Generate EIP inventory report
aws ec2 describe-addresses \
--query 'Addresses[*].{
IP:PublicIp,
AllocationId:AllocationId,
Instance:InstanceId,
NetworkInterface:NetworkInterfaceId,
Name:Tags[?Key==`Name`].Value|[0],
Environment:Tags[?Key==`Environment`].Value|[0]
}' \
--output table > eip-inventory.txt
Best Practice 4: Automate EIP Management with Infrastructure as Code
Never manually allocate EIPs in production. Use Terraform or CloudFormation:
Benefits:
- Reproducible infrastructure
- Version control for IP assignments
- Automated cleanup on stack deletion
- Clear ownership and documentation
# Terraform module for standardized EIP allocation
module "web_server_eip" {
source = "./modules/eip"
name = "prod-web-server"
environment = "production"
instance_id = aws_instance.web.id
}
Best Practice 5: Consider Alternatives for Modern Architectures
AWS Global Accelerator: For global applications, use Global Accelerator instead of multiple regional EIPs:
- Static anycast IPs that route to optimal endpoints
- Built-in DDoS protection
- Health-based routing
IPv6: For future-proof architectures, consider IPv6:
- No Elastic IP needed (IPv6 addresses are globally unique and persistent)
- Free and unlimited
- Growing support across services
# Enable IPv6 for VPC and subnet
aws ec2 associate-vpc-cidr-block \
--vpc-id vpc-12345678 \
--amazon-provided-ipv6-cidr-block
aws ec2 associate-subnet-cidr-block \
--subnet-id subnet-12345678 \
--ipv6-cidr-block 2600:1234:5678:9abc::/64
Real-World Examples and Case Studies
Case Study 1: Zero-Downtime Instance Migration
Scenario: A fintech company needs to upgrade their payment processing server from t3.large to c5.xlarge without DNS changes or customer impact.
Solution using Elastic IP:
# Step 1: Current state
# Old instance i-old123 has EIP 203.0.113.25
# DNS: api.fintech.com → 203.0.113.25
# Step 2: Launch new instance
NEW_INSTANCE=$(aws ec2 run-instances \
--image-id ami-optimized123 \
--instance-type c5.xlarge \
--subnet-id subnet-12345678 \
--query 'Instances[0].InstanceId' \
--output text)
# Step 3: Configure and test new instance
# (Application setup, data sync, testing)
# Step 4: Move EIP to new instance (during maintenance window)
aws ec2 associate-address \
--instance-id $NEW_INSTANCE \
--allocation-id eipalloc-abc123 \
--allow-reassociation
# Step 5: Verify and terminate old instance
aws ec2 terminate-instances --instance-ids i-old123
Result: Migration completed in 30 seconds of actual downtime (EIP reassociation time). No DNS propagation wait. No customer-facing changes.
Case Study 2: Disaster Recovery with Cross-Region Failover
Scenario: An e-commerce platform maintains warm standby instances in a secondary region for disaster recovery.
Problem with public IPs: DR failover would require updating DNS and waiting for propagation (15 minutes to 48 hours).
Solution using Elastic IP + Route 53 health checks:
# Primary region (us-east-1)
resource "aws_eip" "primary" {
provider = aws.us-east-1
domain = "vpc"
tags = {
Name = "ecommerce-primary"
}
}
resource "aws_eip_association" "primary" {
provider = aws.us-east-1
instance_id = aws_instance.primary.id
allocation_id = aws_eip.primary.id
}
# Route 53 health check
resource "aws_route53_health_check" "primary" {
ip_address = aws_eip.primary.public_ip
port = 443
type = "HTTPS"
resource_path = "/health"
failure_threshold = 3
request_interval = 30
}
# Route 53 failover record
resource "aws_route53_record" "primary" {
zone_id = aws_route53_zone.main.zone_id
name = "shop.ecommerce.com"
type = "A"
ttl = 60
failover_routing_policy {
type = "PRIMARY"
}
set_identifier = "primary"
health_check_id = aws_route53_health_check.primary.id
records = [aws_eip.primary.public_ip]
}
# Secondary region configuration (similar)
Result: Automatic failover within 90 seconds when primary region becomes unhealthy.
Case Study 3: Third-Party Firewall Whitelisting
Scenario: A healthcare SaaS integrates with hospital systems that whitelist IPs in strict firewall rules.
Why Elastic IP is essential:
- Hospital IT departments require 30-day notice for IP changes
- Auto-assigned public IPs change on every restart
- Compliance audits verify stable IP addresses
# Allocate dedicated EIPs for each hospital integration
aws ec2 allocate-address --domain vpc --tag-specifications \
'ResourceType=elastic-ip,Tags=[
{Key=Name,Value=hospital-xyz-integration},
{Key=Customer,Value=XYZ-Hospital},
{Key=ComplianceLevel,Value=HIPAA}
]'
# Document in compliance spreadsheet
echo "Hospital XYZ whitelist IP: 203.0.113.25" >> compliance-docs/ip-inventory.txt
Case Study 4: Cost Optimization by Removing Unnecessary EIPs
Scenario: A startup’s AWS bill showed $180/month in Elastic IP charges for 50 orphaned addresses.
Analysis:
# Audit revealed:
# - 25 EIPs from terminated test instances
# - 15 EIPs from moved to ALB architecture
# - 10 EIPs from former employees' experiments
# Cleanup script
for alloc in $(aws ec2 describe-addresses --query 'Addresses[?AssociationId==null].AllocationId' --output text); do
echo "Releasing unused EIP: $alloc"
aws ec2 release-address --allocation-id $alloc
done
Result: Reduced EIP costs from $180/month to $14.40/month (4 legitimate production EIPs), saving $1,987.20 annually.
Lesson: Regular EIP audits are essential cost hygiene. Implement automated alerts for unattached EIPs older than 7 days.
Advanced Topics and Edge Cases
BYOIP: Bring Your Own IP to AWS
For enterprises with existing IP reputation or regulatory requirements, AWS supports BYOIP (Bring Your Own IP).
Use cases:
- Maintaining IP reputation for email servers
- Meeting compliance requirements for specific IP ranges
- Preserving customer-facing IPs during cloud migration
Requirements:
- You must own a /24 or larger IPv4 block (or /48 IPv6)
- ROA (Route Origin Authorization) must be configured
- IP block must be registered with an RIR (ARIN, RIPE, etc.)
# Provision your own IP range in AWS
aws ec2 provision-byoip-cidr \
--cidr 198.51.100.0/24 \
--cidr-authorization-context \
Message="1|aws|198.51.100.0/24|20250101|SHA256|RSAPSS" \
Signature="signature-from-roa"
# Advertise the range
aws ec2 advertise-byoip-cidr --cidr 198.51.100.0/24
# Allocate Elastic IP from your range
aws ec2 allocate-address \
--domain vpc \
--public-ipv4-pool ipv4pool-ec2-1234567890abcdef0
Cost: BYOIP doesn’t reduce EIP costs. Standard Elastic IP charges apply.
IPv6 in AWS: Public IPv6 vs Elastic IPv6
AWS offers IPv6 support with different characteristics:
IPv6 addresses in AWS:
- Automatically assigned from AWS-provided /56 prefix
- Globally unique and routable (GUA)
- Persistent across stop/start (unlike IPv4 public IPs!)
- No Elastic IP equivalent needed
- Completely free
# Enable IPv6 for instance
aws ec2 associate-subnet-cidr-block \
--subnet-id subnet-12345678 \
--ipv6-cidr-block 2600:1f18:1234:5678::/64
aws ec2 run-instances \
--image-id ami-0c55b159cbfafe1f0 \
--instance-type t3.medium \
--subnet-id subnet-12345678 \
--ipv6-address-count 1
# View IPv6 address
aws ec2 describe-instances \
--instance-ids i-1234567890abcdef0 \
--query 'Reservations[0].Instances[0].NetworkInterfaces[0].Ipv6Addresses[0].Ipv6Address'
Key difference: IPv6 addresses persist across instance stop/start, making them more like Elastic IPs for free. However, they’re not transferable between instances.
Elastic IP Quotas and Scaling Considerations
Default limits:
- 5 Elastic IPs per region per account
- Soft limit (can request increase)
Requesting quota increases:
# Check current quota
aws service-quotas get-service-quota \
--service-code ec2 \
--quota-code L-0263D0A3
# Request increase (via AWS Console > Service Quotas)
# Or via support ticket
Architecture patterns to avoid hitting limits:
- Use Load Balancers: ALB/NLB provides stable endpoints without per-instance EIPs
- NAT Gateway: Use NAT Gateway instead of NAT instances with EIPs
- Private subnets: Keep most instances in private subnets with NAT for outbound
- AWS Global Accelerator: Use 2 static anycast IPs for global traffic distribution
Alternatives to Static IP Dependencies
Modern cloud architectures should minimize static IP dependencies:
1. AWS Global Accelerator
Provides 2 static anycast IPs that route to optimal AWS endpoints globally.
resource "aws_globalaccelerator_accelerator" "example" {
name = "global-app-accelerator"
ip_address_type = "IPV4"
enabled = true
}
resource "aws_globalaccelerator_listener" "example" {
accelerator_arn = aws_globalaccelerator_accelerator.example.id
protocol = "TCP"
port_range {
from_port = 80
to_port = 80
}
}
# Output: Static IPs like 75.2.60.5 and 99.83.190.51
Cost: $0.025/hour + data transfer. More expensive than EIP but provides DDoS protection and performance optimization.
2. Route 53 Alias Records
Point DNS to AWS resources directly without exposing IPs:
resource "aws_route53_record" "app" {
zone_id = aws_route53_zone.main.zone_id
name = "app.example.com"
type = "A"
alias {
name = aws_lb.app.dns_name
zone_id = aws_lb.app.zone_id
evaluate_target_health = true
}
}
Benefits: No IP address exposure, automatic failover, no DNS propagation delays.
3. AWS PrivateLink
For service-to-service communication, eliminate public IPs entirely:
resource "aws_vpc_endpoint_service" "example" {
acceptance_required = false
network_load_balancer_arns = [aws_lb.internal_nlb.arn]
}
# Consumers connect via VPC endpoint (no public IP needed)
resource "aws_vpc_endpoint" "example" {
vpc_id = aws_vpc.consumer.id
service_name = aws_vpc_endpoint_service.example.service_name
vpc_endpoint_type = "Interface"
}
Instance Metadata and Public IP Discovery
Instances can discover their own public IP (but not EIP) through metadata:
# From within the instance
curl http://169.254.169.254/latest/meta-data/public-ipv4
# Returns: 54.123.45.67 (auto-assigned) or EIP if associated
curl http://169.254.169.254/latest/meta-data/public-hostname
# Returns: ec2-54-123-45-67.compute-1.amazonaws.com
# Using IMDSv2 (more secure)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-ipv4
Note: The instance cannot distinguish between auto-assigned public IP and Elastic IP through metadata—both appear the same.
How to Work with Elastic IP in AWS: Step-by-Step Guide
Follow this ordered process for proper Elastic IP management:
Step 1: Allocate an Elastic IP
# Allocate EIP and capture the allocation ID
aws ec2 allocate-address --domain vpc --output json > eip-allocation.json
ALLOCATION_ID=$(jq -r '.AllocationId' eip-allocation.json)
PUBLIC_IP=$(jq -r '.PublicIp' eip-allocation.json)
echo "Allocated EIP: $PUBLIC_IP (Allocation ID: $ALLOCATION_ID)"
Step 2: Associate the Elastic IP with an Instance
# Associate with a running EC2 instance
aws ec2 associate-address \
--instance-id i-1234567890abcdef0 \
--allocation-id $ALLOCATION_ID
# Verify association
aws ec2 describe-addresses \
--allocation-ids $ALLOCATION_ID \
--query 'Addresses[0].{IP:PublicIp,Instance:InstanceId,AssociationId:AssociationId}'
Step 3: Verify the Association and Test Connectivity
# Check instance shows EIP in description
aws ec2 describe-instances \
--instance-ids i-1234567890abcdef0 \
--query 'Reservations[0].Instances[0].{PublicIP:PublicIpAddress,State:State.Name}'
# Test connectivity (replace with your EIP)
ping -c 3 $PUBLIC_IP
Step 4: Reassociate to Another Instance (If Needed)
# Move EIP to a different instance
aws ec2 associate-address \
--instance-id i-newinstance456 \
--allocation-id $ALLOCATION_ID \
--allow-reassociation
# The --allow-reassociation flag is critical for moving an already-associated EIP
Step 5: Release the Elastic IP When No Longer Needed
# First, disassociate if currently attached
ASSOCIATION_ID=$(aws ec2 describe-addresses \
--allocation-ids $ALLOCATION_ID \
--query 'Addresses[0].AssociationId' \
--output text)
if [ "$ASSOCIATION_ID" != "None" ]; then
aws ec2 disassociate-address --association-id $ASSOCIATION_ID
fi
# Then release the EIP
aws ec2 release-address --allocation-id $ALLOCATION_ID
echo "EIP $PUBLIC_IP has been released"
Critical reminder: Always release unused Elastic IPs to avoid hourly charges of $0.005 (~$3.60/month per EIP).
Complete Comparison Tables
AWS Public IP vs Elastic IP: Detailed Feature Matrix
| Aspect | AWS Public IP | Elastic IP |
|---|---|---|
| Allocation | Automatic at launch | Manual allocation required |
| Cost (attached) | Included with instance | Included (free when attached to running instance) |
| Cost (unattached) | N/A (doesn’t exist when unattached) | $0.005/hour (~$3.60/month) |
| Persistence | Lost on stop, changes on start | Persists until explicitly released |
| Instance stop/start | New IP assigned on start | Same IP retained |
| Instance terminate | Released automatically | Remains allocated (must manually release) |
| Transferability | Cannot move between instances | Instant reassociation between instances |
| DNS suitability | Poor (changes frequently) | Excellent (stable address) |
| Quota limits | None (subnet-level setting) | 5 per region (soft limit, can increase) |
| Failover support | Not possible | Yes (reassign to standby instance) |
| Best practices | Dev/test, ephemeral workloads | Production, DNS records, HA setups |
| Management | Fully AWS-managed | User-managed (requires release) |
| Visibility in metadata | Yes (via instance metadata service) | Yes (appears as public IP) |
| VPC requirement | Must be in public subnet | Can associate with private subnet instances (via ENI) |
| Multiple IPs per instance | No (one auto-assigned max) | Yes (multiple EIPs per instance via multiple ENIs) |
Cost Comparison by Scenario (Monthly)
| Scenario | Public IP Cost | Elastic IP Cost | Winner |
|---|---|---|---|
| Running instance 24/7 | $0 (included) | $0 (included) | Tie |
| Instance stopped 50% of month | $0 (no charge when stopped) | ~$1.80 (charged while unattached) | Public IP |
| 5 instances running 24/7 | $0 | $0 | Tie |
| Forgot to release after termination | $0 (auto-released) | $3.60/month until noticed | Public IP |
| Need IP for 2 hours of testing | $0 | $0.01 (if released after) | Tie |
| Production with planned failover | Not possible | $0 (worth the flexibility) | Elastic IP |
Use Case Decision Matrix
| Requirement | Recommended Solution | Why |
|---|---|---|
| Dev/test environment | Public IP | Cost-effective, IP changes acceptable |
| Production web server | Elastic IP | DNS stability required |
| Instances behind ALB | No public IP needed | ALB provides stable endpoint |
| NAT for private instances | NAT Gateway (uses EIP) | Managed, highly available |
| Bastion/Jump host | Elastic IP | Stable SSH access point |
| Auto Scaling Group | No public/Elastic IP | Use ALB or NLB for external access |
| Microservices (internal) | No public IP | Use private networking |
| Legacy app with IP whitelisting | Elastic IP | Third-party firewall requirements |
| Multi-region HA | Route 53 + EIPs per region | Failover with health checks |
| Global application | AWS Global Accelerator | Static anycast IPs, better performance |
Conclusion and Key Takeaways
Decision Summary: AWS Public IP vs Elastic IP
The AWS public IP vs Elastic IP choice fundamentally comes down to persistence and control:
- Use auto-assigned public IP for development, testing, and instances behind load balancers where IP stability doesn’t matter
- Use Elastic IP for production workloads requiring DNS records, third-party integrations, or high-availability failover scenarios
- Use neither (private subnets + NAT Gateway) for the most secure, scalable architecture
Core Principles for IP Management
- Default to no public IP: Most instances should run in private subnets with NAT Gateway or ALB for internet access
- Use load balancers over static IPs: ALB/NLB provides better availability than individual instance IPs
- Elastic IPs are for exceptions: Allocate only when absolutely necessary for DNS, failover, or compliance
- Always tag and audit: Untagged, orphaned EIPs drain budgets silently
- Automate with IaC: Never manually manage production IPs—use Terraform or CloudFormation
Production-Ready Checklist
Use this checklist before deploying IP configurations:
Before Launch:
- [ ] Determine if public IP is actually needed
- [ ] Consider load balancer + private instances instead
- [ ] Document IP requirements (DNS, failover, whitelisting)
- [ ] Plan for IPv6 alongside IPv4
- [ ] Verify EIP quota availability in target region
For Elastic IPs:
- [ ] Allocate with descriptive tags (Name, Environment, Owner, CostCenter)
- [ ] Document allocation in infrastructure registry
- [ ] Configure Route 53 health checks if used for HA
- [ ] Set up billing alerts for unattached EIPs
- [ ] Create automated cleanup procedures
- [ ] Test reassociation/failover procedures
Ongoing Operations:
- [ ] Monthly audit of all Elastic IPs
- [ ] Release unused EIPs within 24 hours
- [ ] Monitor EIP quota usage across regions
- [ ] Review for replacement opportunities (ALB, Global Accelerator)
- [ ] Update documentation when IPs change ownership
When to Reevaluate Your Architecture
If you’re using more than 10 Elastic IPs in a single region, consider whether:
- Load balancers could consolidate multiple instance IPs
- AWS Global Accelerator could provide better performance with fewer IPs
- PrivateLink could eliminate public exposure entirely
- IPv6 could reduce IPv4 dependency
Cost Optimization Action Items
- Audit now: Run the orphaned EIP detection script immediately
- Set alerts: Configure AWS Budgets for $5/month threshold per EIP
- Automate cleanup: Schedule weekly Lambda function to identify unattached EIPs > 7 days old
- Review architecture: Challenge every Elastic IP—is there a better way?
Appendix: CLI, Terraform & CloudFormation Cheatsheet
AWS CLI Commands Reference
# ALLOCATE ELASTIC IP
aws ec2 allocate-address --domain vpc
# ASSOCIATE TO INSTANCE
aws ec2 associate-address \
--instance-id i-1234567890abcdef0 \
--allocation-id eipalloc-abc123def456
# ASSOCIATE TO NETWORK INTERFACE
aws ec2 associate-address \
--network-interface-id eni-11223344 \
--allocation-id eipalloc-abc123def456
# DESCRIBE ALL ELASTIC IPs
aws ec2 describe-addresses
# DESCRIBE SPECIFIC EIP
aws ec2 describe-addresses --allocation-ids eipalloc-abc123def456
# FIND UNATTACHED (ORPHANED) EIPs
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null]'
# DISASSOCIATE EIP
aws ec2 disassociate-address --association-id eipassoc-xyz789
# RELEASE EIP
aws ec2 release-address --allocation-id eipalloc-abc123def456
# TAG AN ELASTIC IP
aws ec2 create-tags \
--resources eipalloc-abc123def456 \
--tags Key=Name,Value=prod-web-server
# CHECK INSTANCE PUBLIC IP
aws ec2 describe-instances \
--instance-ids i-1234567890abcdef0 \
--query 'Reservations[0].Instances[0].PublicIpAddress'
Terraform Complete Example
# Provider configuration
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
# VPC and Networking
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "production-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = false # We'll use EIP instead
tags = {
Name = "public-subnet-1a"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# Security Group
resource "aws_security_group" "web" {
name = "web-server-sg"
description = "Security group for web server"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "web-server-sg"
}
}
# EC2 Instance
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0" # Update with current AMI
instance_type = "t3.medium"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
# Don't enable auto-assign public IP - we're using EIP
associate_public_ip_address = false
tags = {
Name = "prod-web-server"
Environment = "production"
}
}
# Elastic IP
resource "aws_eip" "web" {
domain = "vpc"
tags = {
Name = "prod-web-server-eip"
Environment = "production"
ManagedBy = "terraform"
}
# Ensure EIP isn't created until IGW exists
depends_on = [aws_internet_gateway.main]
}
# Associate EIP with Instance
resource "aws_eip_association" "web" {
instance_id = aws_instance.web.id
allocation_id = aws_eip.web.id
}
# Outputs
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.web.id
}
output "elastic_ip" {
description = "Elastic IP address of the web server"
value = aws_eip.web.public_ip
}
output "elastic_ip_allocation_id" {
description = "Allocation ID of the Elastic IP"
value = aws_eip.web.id
}
CloudFormation Complete Template
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Complete EC2 instance with Elastic IP setup'
Parameters:
LatestAmiId:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
InstanceType:
Type: String
Default: t3.medium
AllowedValues:
- t3.micro
- t3.small
- t3.medium
- t3.large
Description: EC2 instance type
Environment:
Type: String
Default: production
AllowedValues:
- development
- staging
- production
Description: Environment name
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub '${Environment}-vpc'
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${Environment}-igw'
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub '${Environment}-public-subnet'
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${Environment}-public-rt'
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for web server
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub '${Environment}-web-sg'
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: !Ref InstanceType
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref WebServerSecurityGroup
Tags:
- Key: Name
Value: !Sub '${Environment}-web-server'
- Key: Environment
Value: !Ref Environment
ElasticIP:
Type: AWS::EC2::EIP
DependsOn: AttachGateway
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub '${Environment}-web-eip'
- Key: Environment
Value: !Ref Environment
EIPAssociation:
Type: AWS::EC2::EIPAssociation
Properties:
InstanceId: !Ref WebServerInstance
EIP: !Ref ElasticIP
Outputs:
InstanceId:
Description: Instance ID of the web server
Value: !Ref WebServerInstance
Export:
Name: !Sub '${AWS::StackName}-InstanceId'
ElasticIP:
Description: Elastic IP address
Value: !Ref ElasticIP
Export:
Name: !Sub '${AWS::StackName}-ElasticIP'
AllocationId:
Description: Allocation ID of the Elastic IP
Value: !GetAtt ElasticIP.AllocationId
Export:
Name: !Sub '${AWS::StackName}-AllocationId'
PublicDNS:
Description: Public DNS name
Value: !GetAtt WebServerInstance.PublicDnsName
Summary Comparison Table
| Feature | Auto-assigned Public IP | Elastic IP |
|---|---|---|
| Persistence | ❌ Changes on stop/start | ✅ Persistent until released |
| Cost (running) | ✅ Free | ✅ Free |
| Cost (stopped) | ✅ Free (released) | ❌ $0.005/hour |
| Transferable | ❌ No | ✅ Yes (instant) |
| DNS suitable | ❌ No | ✅ Yes |
| Failover support | ❌ No | ✅ Yes |
| Best for | Dev/test | Production |
Frequently Asked Questions (People Also Ask)
1. What is the difference between AWS public IP and Elastic IP?
An AWS public IP is a temporary IPv4 address automatically assigned to EC2 instances in public subnets. It changes every time you stop and restart the instance. An Elastic IP is a static IPv4 address that you manually allocate and control. It remains assigned to your account until you explicitly release it, and you can reassociate it between instances instantly, making it ideal for DNS records and production workloads.
2. Does AWS charge for Elastic IPs?
AWS charges $0.005 per hour (approximately $3.60 per month) for Elastic IPs that are allocated but not associated with a running instance. When an Elastic IP is attached to a running EC2 instance, there is no charge. However, you are charged for additional Elastic IPs beyond the first one per running instance. Always release unused Elastic IPs to avoid unnecessary costs.
3. When does an AWS public IP change?
An AWS auto-assigned public IP changes in these scenarios: when you stop and then restart an EC2 instance (not during reboot), when you terminate and relaunch an instance, or when you move an instance to a different subnet. The public IP is released back to AWS’s pool when the instance stops, and a new one is assigned when it starts again. Elastic IPs, by contrast, remain constant across these operations.
4. How do I allocate and associate an Elastic IP?
First, allocate an Elastic IP using aws ec2 allocate-address --domain vpc, which returns an allocation ID. Then associate it with your instance using aws ec2 associate-address --instance-id YOUR_INSTANCE_ID --allocation-id YOUR_ALLOCATION_ID. You can verify the association with aws ec2 describe-addresses. To move the EIP to another instance, use the same associate command with the new instance ID and add the --allow-reassociation flag.
5. What are the limits for Elastic IP addresses in AWS?
AWS provides a default quota of 5 Elastic IP addresses per region per account. This is a soft limit that you can increase by submitting a request through the AWS Service Quotas console or creating a support ticket. The limit exists to encourage efficient IP usage and to promote architectural patterns using load balancers instead of direct instance IP exposure. Most production architectures should not require more than 5 EIPs per region if following AWS best practices.
6. Should I use Elastic IP or Route 53 for DNS?
For most production applications, use Route 53 alias records pointing to Application Load Balancers or Network Load Balancers rather than pointing DNS directly to Elastic IPs. This provides better availability, automatic failover, health checking, and eliminates single-instance dependencies. Use Elastic IPs with Route 53 only when you have specific requirements like third-party firewall whitelisting, legacy application constraints, or single-instance architectures where load balancers add unnecessary complexity and cost.
Related Resources and Internal Links
For deeper dives into related AWS networking and DevOps topics, explore these guides on thedevopstooling.com:
- AWS VPC Routing Tables Explained – Master VPC routing for proper public and private subnet configuration
- Terraform AWS Infrastructure Basics – Learn Infrastructure as Code for managing AWS resources including Elastic IPs
- Ansible for AWS Automation – Automate AWS tasks including EIP management with Ansible playbooks
- Kubernetes on AWS (EKS) Networking – Understand how EKS handles IP addressing and load balancing
- AWS Cost Optimization for DevOps Engineers – Comprehensive guide to reducing AWS costs, including IP management strategies
Downloadable Resources
Elastic IP Best Practices Checklist (PDF)
Pre-Deployment Checklist:
- [ ] Determine actual need for public IP (consider private subnet + ALB)
- [ ] Evaluate load balancer alternative
- [ ] Confirm EIP quota availability in target region
- [ ] Plan DNS strategy (EIP direct vs Route 53 alias)
- [ ] Document IP in infrastructure inventory
- [ ] Prepare tagging strategy (Name, Environment, Owner, CostCenter)
Allocation Best Practices:
- [ ] Use Infrastructure as Code (Terraform/CloudFormation)
- [ ] Apply comprehensive tags at allocation
- [ ] Document association in runbooks
- [ ] Configure monitoring and alerting
- [ ] Set up health checks for HA scenarios
- [ ] Plan backup/failover instances
Operational Best Practices:
- [ ] Monthly EIP audit and cleanup
- [ ] Monitor for unattached EIPs (> 24 hours)
- [ ] Review bills for unexpected EIP charges
- [ ] Test failover procedures quarterly
- [ ] Keep IP inventory up-to-date
- [ ] Review for modernization opportunities
Security Best Practices:
- [ ] Restrict EIP allocation IAM permissions
- [ ] Enable CloudTrail logging for EIP operations
- [ ] Configure security groups appropriately
- [ ] Use AWS Config rules for compliance
- [ ] Implement least-privilege access
- [ ] Regular security group audits
Cost Optimization:
- [ ] Release EIPs within 1 hour of disassociation
- [ ] Set billing alerts for orphaned EIPs
- [ ] Evaluate ALB/NLB alternatives monthly
- [ ] Consider IPv6 for new workloads
- [ ] Use AWS Cost Explorer for EIP analysis
- [ ] Schedule Lambda for automated cleanup
Visual Diagram: AWS Instance Lifecycle with Public vs Elastic IP
┌─────────────────────────────────────────────────────────────────────┐
│ PUBLIC IP LIFECYCLE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Launch Instance Stop Instance Start Instance │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Instance │ auto-assign │ Instance │ release │ Instance │ │
│ │ running │──────────────> │ stopped │────────> │ running │ │
│ │ │ 52.10.20.30 │ │ IP gone │ │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │
│ │ │ │
│ └────────────────────────────────────────────────────┘ │
│ NEW IP: 54.30.40.50 │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ ELASTIC IP LIFECYCLE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Allocate EIP Associate Stop Instance Start & Auto │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ EIP │────> │ Instance │────> │ Instance │──>│ Instance │ │
│ │203.0.1.1 │ link │ running │ keep │ stopped │ │ running │ │
│ └──────────┘ └──────────┘ EIP └──────────┘ └──────────┘ │
│ │ │ │ │ │
│ │ │ │ │ │
│ └──────────────────┴───────────────┴─────────────────┘ │
│ SAME IP: 203.0.113.1 (persistent) │
│ │
│ Reassociate to New Instance: │
│ ┌──────────┐ ┌──────────┐ │
│ │ EIP │────> │ Instance │ (instant failover) │
│ │203.0.1.1 │ move │ NEW │ │
│ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Advanced Automation Scripts
Automated EIP Health Check and Failover Script
#!/bin/bash
# eip-healthcheck-failover.sh
# Monitors primary instance and fails over EIP to standby if unhealthy
set -e
# Configuration
PRIMARY_INSTANCE="i-primary123456"
STANDBY_INSTANCE="i-standby789012"
EIP_ALLOCATION="eipalloc-abc123def456"
HEALTH_CHECK_URL="https://203.0.113.1/health"
HEALTH_CHECK_RETRIES=3
SNS_TOPIC_ARN="arn:aws:sns:us-east-1:123456789012:eip-failover-alerts"
# Function to check instance health
check_health() {
local url=$1
local retries=$2
for i in $(seq 1 $retries); do
if curl -sf --max-time 5 "$url" > /dev/null 2>&1; then
echo "Health check passed (attempt $i)"
return 0
fi
echo "Health check failed (attempt $i)"
sleep 10
done
return 1
}
# Function to get current EIP association
get_current_association() {
aws ec2 describe-addresses \
--allocation-ids "$EIP_ALLOCATION" \
--query 'Addresses[0].InstanceId' \
--output text
}
# Function to perform failover
perform_failover() {
local target_instance=$1
echo "$(date): Performing failover to $target_instance"
aws ec2 associate-address \
--instance-id "$target_instance" \
--allocation-id "$EIP_ALLOCATION" \
--allow-reassociation
# Send SNS notification
aws sns publish \
--topic-arn "$SNS_TOPIC_ARN" \
--subject "EIP Failover Alert" \
--message "EIP $EIP_ALLOCATION failed over to instance $target_instance at $(date)"
echo "$(date): Failover complete"
}
# Main logic
echo "$(date): Starting health check for $PRIMARY_INSTANCE"
if ! check_health "$HEALTH_CHECK_URL" "$HEALTH_CHECK_RETRIES"; then
echo "$(date): Primary instance unhealthy, initiating failover"
current_instance=$(get_current_association)
if [ "$current_instance" == "$PRIMARY_INSTANCE" ]; then
perform_failover "$STANDBY_INSTANCE"
else
echo "$(date): EIP already on standby instance, checking standby health"
if ! check_health "$HEALTH_CHECK_URL" "$HEALTH_CHECK_RETRIES"; then
echo "$(date): CRITICAL: Both instances unhealthy!"
aws sns publish \
--topic-arn "$SNS_TOPIC_ARN" \
--subject "CRITICAL: Both EIP Targets Unhealthy" \
--message "Both primary and standby instances are unhealthy at $(date)"
exit 1
fi
fi
else
echo "$(date): Primary instance healthy"
# Optionally fail back to primary if currently on standby
current_instance=$(get_current_association)
if [ "$current_instance" == "$STANDBY_INSTANCE" ]; then
echo "$(date): Currently on standby, considering failback to primary"
# Add your failback logic here if desired
fi
fi
echo "$(date): Health check complete"
Lambda Function for Orphaned EIP Cleanup
# lambda_eip_cleanup.py
# AWS Lambda function to identify and optionally release orphaned EIPs
import boto3
import json
from datetime import datetime, timedelta
from typing import List, Dict
ec2_client = boto3.client('ec2')
sns_client = boto3.client('sns')
# Configuration
ORPHAN_THRESHOLD_HOURS = 24
SNS_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:eip-cleanup-alerts'
DRY_RUN = True # Set to False to actually release EIPs
PROTECTED_TAGS = ['production', 'critical'] # Don't auto-release these
def get_orphaned_eips() -> List[Dict]:
"""Find all unattached Elastic IPs"""
response = ec2_client.describe_addresses()
orphaned = []
for address in response['Addresses']:
if 'AssociationId' not in address:
# EIP is not associated with any instance
tags = {tag['Key']: tag['Value'] for tag in address.get('Tags', [])}
orphaned.append({
'AllocationId': address['AllocationId'],
'PublicIp': address['PublicIp'],
'Tags': tags
})
return orphaned
def is_protected(eip: Dict) -> bool:
"""Check if EIP has protected tags"""
tags = eip.get('Tags', {})
for key, value in tags.items():
if key.lower() == 'environment' and value.lower() in PROTECTED_TAGS:
return True
if key.lower() == 'protected' and value.lower() == 'true':
return True
return False
def release_eip(allocation_id: str, public_ip: str) -> bool:
"""Release an Elastic IP"""
try:
if not DRY_RUN:
ec2_client.release_address(AllocationId=allocation_id)
print(f"Released EIP: {public_ip} ({allocation_id})")
return True
else:
print(f"DRY RUN: Would release EIP: {public_ip} ({allocation_id})")
return False
except Exception as e:
print(f"Error releasing EIP {public_ip}: {str(e)}")
return False
def send_notification(orphaned_eips: List[Dict], released_eips: List[Dict]):
"""Send SNS notification about orphaned/released EIPs"""
message_lines = [
f"EIP Cleanup Report - {datetime.utcnow().isoformat()}",
f"",
f"Total orphaned EIPs found: {len(orphaned_eips)}",
f"EIPs released: {len(released_eips)}",
f"Monthly cost of orphaned EIPs: ${len(orphaned_eips) * 3.60:.2f}",
f"",
f"Orphaned EIPs:"
]
for eip in orphaned_eips:
protected = " [PROTECTED]" if is_protected(eip) else ""
name = eip['Tags'].get('Name', 'Unnamed')
message_lines.append(
f" - {eip['PublicIp']} ({eip['AllocationId']}) - {name}{protected}"
)
if released_eips:
message_lines.append(f"")
message_lines.append(f"Released EIPs:")
for eip in released_eips:
name = eip['Tags'].get('Name', 'Unnamed')
message_lines.append(
f" - {eip['PublicIp']} ({eip['AllocationId']}) - {name}"
)
message = "\n".join(message_lines)
try:
sns_client.publish(
TopicArn=SNS_TOPIC_ARN,
Subject=f"EIP Cleanup Report - {len(orphaned_eips)} Orphaned EIPs",
Message=message
)
print("Notification sent successfully")
except Exception as e:
print(f"Error sending notification: {str(e)}")
def lambda_handler(event, context):
"""Main Lambda handler"""
print(f"Starting EIP cleanup scan at {datetime.utcnow().isoformat()}")
print(f"DRY RUN mode: {DRY_RUN}")
# Get all orphaned EIPs
orphaned_eips = get_orphaned_eips()
print(f"Found {len(orphaned_eips)} orphaned EIPs")
if not orphaned_eips:
print("No orphaned EIPs found")
return {
'statusCode': 200,
'body': json.dumps('No orphaned EIPs found')
}
# Filter out protected EIPs and release eligible ones
released_eips = []
for eip in orphaned_eips:
if is_protected(eip):
print(f"Skipping protected EIP: {eip['PublicIp']}")
continue
if release_eip(eip['AllocationId'], eip['PublicIp']):
released_eips.append(eip)
# Send notification
send_notification(orphaned_eips, released_eips)
return {
'statusCode': 200,
'body': json.dumps({
'orphaned_count': len(orphaned_eips),
'released_count': len(released_eips),
'monthly_cost_savings': len(released_eips) * 3.60
})
}
Terraform Module for Automated EIP Management
# modules/managed-eip/main.tf
# Reusable Terraform module for Elastic IP with monitoring and tagging
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
variable "name" {
description = "Name of the EIP resource"
type = string
}
variable "environment" {
description = "Environment (dev, staging, production)"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}
variable "instance_id" {
description = "EC2 instance ID to associate with"
type = string
default = null
}
variable "cost_center" {
description = "Cost center for billing"
type = string
default = "engineering"
}
variable "enable_cloudwatch_alarm" {
description = "Enable CloudWatch alarm for unattached EIP"
type = bool
default = true
}
variable "alarm_sns_topic_arn" {
description = "SNS topic ARN for EIP alarms"
type = string
default = ""
}
locals {
common_tags = {
Name = var.name
Environment = var.environment
CostCenter = var.cost_center
ManagedBy = "terraform"
CreatedAt = timestamp()
}
}
# Allocate Elastic IP
resource "aws_eip" "this" {
domain = "vpc"
tags = local.common_tags
}
# Associate with instance if provided
resource "aws_eip_association" "this" {
count = var.instance_id != null ? 1 : 0
instance_id = var.instance_id
allocation_id = aws_eip.this.id
}
# CloudWatch metric for monitoring
resource "aws_cloudwatch_log_metric_filter" "eip_unattached" {
count = var.enable_cloudwatch_alarm ? 1 : 0
name = "${var.name}-eip-unattached"
log_group_name = "/aws/lambda/eip-monitor"
pattern = "[time, request_id, event_type = DISASSOCIATE, allocation_id = ${aws_eip.this.id}*]"
metric_transformation {
name = "EIPUnattached"
namespace = "CustomMetrics/EIP"
value = "1"
dimensions = {
AllocationId = aws_eip.this.id
}
}
}
# CloudWatch alarm for unattached EIP (cost alert)
resource "aws_cloudwatch_metric_alarm" "eip_unattached" {
count = var.enable_cloudwatch_alarm && var.alarm_sns_topic_arn != "" ? 1 : 0
alarm_name = "${var.name}-eip-unattached"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "EIPUnattached"
namespace = "CustomMetrics/EIP"
period = "3600" # 1 hour
statistic = "Sum"
threshold = "0"
alarm_description = "Alert when EIP ${aws_eip.this.public_ip} is unattached for >1 hour"
alarm_actions = [var.alarm_sns_topic_arn]
dimensions = {
AllocationId = aws_eip.this.id
}
}
# Outputs
output "public_ip" {
description = "The Elastic IP address"
value = aws_eip.this.public_ip
}
output "allocation_id" {
description = "The allocation ID of the Elastic IP"
value = aws_eip.this.id
}
output "association_id" {
description = "The association ID (if associated)"
value = var.instance_id != null ? aws_eip_association.this[0].id : null
}
output "tags" {
description = "Tags applied to the EIP"
value = aws_eip.this.tags_all
}
Final Thoughts and Next Steps
Understanding the AWS public IP vs Elastic IP distinction is fundamental to building reliable cloud infrastructure. The choice isn’t just technical—it impacts your costs, operational complexity, and system availability.
Key takeaway: Default to simplicity. Use auto-assigned public IPs unless you have a compelling reason for the persistence and control of Elastic IPs. And in most modern architectures, you shouldn’t expose instance IPs at all—use load balancers and DNS abstraction instead.
Your Action Plan
This week:
- Audit your current AWS accounts for orphaned Elastic IPs
- Document why each production Elastic IP exists
- Identify candidates for migration to ALB/NLB
This month: 4. Implement automated EIP monitoring and alerting 5. Standardize EIP allocation with Infrastructure as Code 6. Train your team on proper IP management
This quarter: 7. Evaluate AWS Global Accelerator for multi-region workloads 8. Plan IPv6 adoption for new projects 9. Review and optimize your entire IP strategy
Share Your Experience
Have you implemented creative solutions around AWS IP management? Discovered hidden costs in your EIP usage? Share your experiences in the comments below—let’s learn from each other’s successes and mistakes.
Stay Updated
AWS networking evolves rapidly. Subscribe to thedevopstooling.com newsletter for:
- Monthly AWS networking updates
- New Terraform modules and automation scripts
- Cost optimization discoveries
- Real-world case studies from the community
Bookmark this guide as your reference for all things AWS public IP and Elastic IP. The cheatsheets and code snippets are designed for quick copy-paste deployment when you need them most.
More AWS Resources: Strengthen Your Cloud & Security Skills
- AWS Account Setup: Complete Step-by-Step Guide for Beginners
- Master AWS VPC Fundamentals and Network Design: Build a Rock-Solid Cloud Network
- AWS Public IP vs Elastic IP: Common Mistakes and Best Practices
- What Is IAM in AWS? The Essential Guide for DevOps Engineers
