Home / AWS

AWSCheat sheetBeginner

Public IPv4 and Elastic IP cheat sheet: find, release and stop creating addresses you pay for

AWS charges for every public IPv4 address, in use or idle. CLI commands to list Elastic IPs and auto-assigned public IPs, see the cost, release safely and stop new ones appearing.

Deepak Bhardwaj5 min read

Applies toAmazon EC2, Amazon VPC (Elastic IPs, ENIs, subnets, launch templates), Cost Explorer, AWS CLI v2

Why this matters

Since February 2024, AWS charges an hourly fee for every public IPv4 address: Elastic IPs (attached or not), auto-assigned public IPs on instances, and the public IPs behind internet-facing load balancers and NAT gateways. Check the current Amazon VPC pricing page for the rate in your region.

Idle Elastic IPs are pure waste. Auto-assigned public IPs on instances that sit behind a load balancer or never talk to the internet are usually waste too. This sheet finds both and stops new ones.

All commands run against one region. Add --region <code> or loop over regions (last section).

See what you are paying

In Cost Explorer, filter Usage type for PublicIPv4. The two usage types are PublicIPv4:InUseAddress and PublicIPv4:IdleAddress, prefixed by a region code (for example USE1-). From the CLI:

aws ce get-cost-and-usage \
  --time-period Start=2026-10-01,End=2026-11-01 \
  --granularity MONTHLY \
  --metrics UnblendedCost UsageQuantity \
  --group-by Type=DIMENSION,Key=USAGE_TYPE \
  --query "ResultsByTime[].Groups[?contains(Keys[0], 'PublicIPv4')].[Keys[0],Metrics.UsageQuantity.Amount,Metrics.UnblendedCost.Amount]" \
  --output table

UsageQuantity is address-hours. Divide by the hours in the month to get the average number of addresses. Cost Explorer runs from us-east-1 regardless of your default region.

For a per-resource view across regions, the console's VPC IP Address Manager > Public IP insights page breaks addresses down by type (EIP, EC2 auto-assigned, service-managed). It works on the IPAM Free Tier.

Elastic IPs

List all Elastic IPs and what they are attached to

aws ec2 describe-addresses \
  --query "Addresses[].{IP:PublicIp,Alloc:AllocationId,Instance:InstanceId,ENI:NetworkInterfaceId,Name:Tags[?Key=='Name']|[0].Value}" \
  --output table

Find idle (unassociated) Elastic IPs

aws ec2 describe-addresses \
  --query "Addresses[?AssociationId==null].[PublicIp,AllocationId,Tags[?Key=='Name']|[0].Value]" \
  --output text

Elastic IPs on stopped instances

A stopped instance still holds its Elastic IP and you are still charged for it.

for i in $(aws ec2 describe-addresses --query "Addresses[?InstanceId!=null].InstanceId" --output text); do
  aws ec2 describe-instances --instance-ids "$i" \
    --query "Reservations[].Instances[?State.Name=='stopped'].[InstanceId,Tags[?Key=='Name']|[0].Value]" \
    --output text
done

Release an Elastic IP

Disassociate first if it is attached, then release:

aws ec2 disassociate-address --association-id eipassoc-0123456789abcdef0
aws ec2 release-address --allocation-id eipalloc-0123456789abcdef0

Warning

Before releasing, search for the address in DNS records, partner firewall allowlists, VPN configs and SaaS IP allowlists. A released IP goes back to the AWS pool. Anything that whitelisted it breaks, and you may not get it back.

Try to recover a released Elastic IP

If nobody else has allocated it yet, you can request the same address:

aws ec2 allocate-address --domain vpc --address 203.0.113.25

This fails if the address is now in use by another account. Treat it as a best-effort undo, not a safety net.

Tag Elastic IPs with an owner

Untagged EIPs are the ones nobody dares release. Tag them when you allocate:

aws ec2 create-tags --resources eipalloc-0123456789abcdef0 \
  --tags Key=Owner,Value=payments-team Key=Purpose,Value=partner-allowlist

Auto-assigned public IPs on instances

Instances with a public IP that is not an Elastic IP

aws ec2 describe-instances \
  --filters Name=instance-state-name,Values=running,stopped \
  --query "Reservations[].Instances[?PublicIpAddress!=null].[InstanceId,PublicIpAddress,SubnetId,Tags[?Key=='Name']|[0].Value]" \
  --output table

Compare the IPs against describe-addresses. Anything not in that list is auto-assigned.

All ENIs with a public IP, and who owns the IP

This catches load balancers, NAT gateways and other service-managed interfaces too:

aws ec2 describe-network-interfaces \
  --query "NetworkInterfaces[?Association.PublicIp!=null].[NetworkInterfaceId,InterfaceType,Association.PublicIp,Association.IpOwnerId,Description]" \
  --output table
IpOwnerId InterfaceType / Description Meaning Action
your account ID any Elastic IP Covered above
amazon interface, instance attached Auto-assigned public IP Remove if not needed
amazon Description starts ELB app/ or ELB net/ Internet-facing ALB/NLB Consolidate idle load balancers
amazon or account nat_gateway NAT gateway EIP Remove unused NAT gateways

Remove an auto-assigned public IP from a running instance

You no longer need to stop or replace the instance. This needs a recent AWS CLI v2 version:

aws ec2 modify-network-interface-attribute \
  --network-interface-id eni-0123456789abcdef0 \
  --no-associate-public-ip-address

Caution

This cuts the instance off from the internet immediately if it has no NAT gateway route, and drops any SSH sessions that came in over the public IP. Make sure you can still reach it through Session Manager or EC2 Instance Connect Endpoint first.

Stop new public IPs appearing

Subnets that auto-assign public IPs

aws ec2 describe-subnets \
  --filters Name=map-public-ip-on-launch,Values=true \
  --query "Subnets[].[SubnetId,VpcId,CidrBlock,Tags[?Key=='Name']|[0].Value]" \
  --output table

Turn it off on subnets where instances do not need a public address (instances behind an ALB, for example):

aws ec2 modify-subnet-attribute --subnet-id subnet-0123456789abcdef0 --no-map-public-ip-on-launch

Existing instances keep their addresses. Only new launches change.

Launch templates that force a public IP

aws ec2 describe-launch-template-versions \
  --launch-template-id lt-0123456789abcdef0 --versions '$Latest' \
  --query "LaunchTemplateVersions[].LaunchTemplateData.NetworkInterfaces[].AssociatePublicIpAddress"

true overrides the subnet setting. Create a new version with AssociatePublicIpAddress set to false and roll it through your Auto Scaling group.

Run it across all regions

for r in $(aws ec2 describe-regions --query "Regions[].RegionName" --output text); do
  n=$(aws ec2 describe-addresses --region "$r" \
      --query "length(Addresses[?AssociationId==null])" --output text)
  [ "$n" != "0" ] && echo "$r idle EIPs: $n"
done

Prevent it next time

  • Make "no public IP" the default: subnets with MapPublicIpOnLaunch=false, launch templates with AssociatePublicIpAddress=false, and public access only through load balancers.
  • Require an Owner tag on Elastic IPs, enforced with a tag policy or AWS Config rule, so clean-up has someone to ask.
  • Enable the AWS Config managed rule eip-attached to flag unattached Elastic IPs continuously.
  • Reach instances through Session Manager or EC2 Instance Connect Endpoint instead of public IPs.
  • Where clients support it, use IPv6 or dual-stack for outbound traffic and check whether an egress-only internet gateway fits your design.