Why Your Elastic IP Still Charges You After Stopping EC2 (And How to Fix It)
You stopped your EC2 instance to save money, only to find Elastic IP charges quietly accumulating on your AWS bill. This is one of the most common AWS cost surprises — and the billing logic behind it is counterintuitive until you understand AWS's design intent.
TL;DR
| Elastic IP State | Associated With Running Instance? | Charged? |
|---|---|---|
| Allocated + Associated (instance running) | ✅ Yes | ❌ No charge |
| Allocated + Associated (instance stopped) | ⚠️ Associated but idle | ✅ Charged |
| Allocated + Not Associated | ❌ No | ✅ Charged |
| Released | ❌ No (address returned to AWS) | ❌ No charge |
Bottom line: To stop all Elastic IP charges, you must Release the address. Disassociating alone is not sufficient — a disassociated-but-allocated EIP is still billed.
Why Does AWS Charge for Idle Elastic IPs?
IPv4 addresses are a globally scarce resource. AWS charges for Elastic IPs that are not actively in use to discourage hoarding. The billing rule is simple: if an EIP is allocated to your account and not attached to a running EC2 instance, you pay for it. A stopped instance does not count as "running."
Real-World Analogy: Think of an Elastic IP like a reserved parking spot in a downtown garage. You pay the reservation fee whether your car is parked there or not. The garage (AWS) only waives the fee when your car (a running instance) is actively occupying the spot. If you drive away (stop the instance) but keep the reservation (allocated EIP), the meter keeps running.
The Billing State Machine: How EIP Charges Work
The diagram below maps every possible Elastic IP state to its billing outcome.
RUNNING Instance" Stopped : "Associated with
STOPPED Instance" Running --> Stopped : Stop EC2 Instance Stopped --> Running : Start EC2 Instance } } Allocated --> Released : aws ec2 release-address Released --> [*] state "💚 NO CHARGE" as nc state "🔴 BILLED" as b1 state "🔴 BILLED" as b2 state "💚 NO CHARGE" as nc2 Running --> nc Stopped --> b1 Unassociated --> b2 Released --> nc2
- Allocated + Running Instance: The only state where you pay zero for the EIP itself. The address is actively routing traffic.
- Allocated + Stopped Instance: The EIP is still "reserved" in your account. AWS bills you because the address is sitting idle — no running workload justifies holding it.
- Allocated + Unassociated: You explicitly detached the EIP from any resource. Still billed — the address is yours but unused.
- Released: The address is returned to AWS's pool. Billing stops immediately. You can no longer use this specific IP.
Disassociate vs. Release: What's the Difference?
| Action | What It Does | Stops Billing? | IP Retained? |
|---|---|---|---|
| Disassociate | Detaches EIP from an instance/ENI. EIP stays in your account. | ❌ No | ✅ Yes |
| Release | Returns EIP to AWS. Removed from your account permanently. | ✅ Yes | ❌ No |
When to Disassociate without Releasing: Use disassociation as a temporary step — for example, when migrating an EIP from one instance to another. Never leave an EIP disassociated for extended periods if you want to avoid charges.
Step-by-Step: Stop the Charges
Option A: AWS Management Console
- Navigate to EC2 Console → Network & Security → Elastic IPs.
- Select the Elastic IP address you want to release.
- If it is still associated: click Actions → Disassociate Elastic IP address, then confirm.
- With the EIP now unassociated, click Actions → Release Elastic IP addresses, then confirm.
Option B: AWS CLI (Recommended for Automation)
🔽 [Click to expand] — CLI commands to disassociate and release an EIP
# Step 1: Find your Elastic IP allocation ID and association ID
aws ec2 describe-addresses \
--query 'Addresses[*].{AllocationId:AllocationId,AssociationId:AssociationId,PublicIp:PublicIp,InstanceId:InstanceId}' \
--output table
# Step 2: Disassociate the EIP (only needed if currently associated)
# Replace 'eipassoc-0abc1234def567890' with your actual Association ID
aws ec2 disassociate-address \
--association-id eipassoc-0abc1234def567890
# Step 3: Release the EIP to stop all charges
# Replace 'eipalloc-0abc1234def567890' with your actual Allocation ID
aws ec2 release-address \
--allocation-id eipalloc-0abc1234def567890
# Verify: Confirm the address no longer appears in your account
aws ec2 describe-addresses --output table
Option C: Find and Release ALL Unassociated EIPs (Audit Script)
🔽 [Click to expand] — Bash script to release all idle (unassociated) EIPs
#!/bin/bash
# WARNING: This releases ALL unassociated EIPs in the current region.
# Review the list before confirming.
REGION="us-east-1" # Change to your target region
echo "Fetching unassociated Elastic IPs in region: $REGION"
UNASSOCIATED_EIPS=$(aws ec2 describe-addresses \
--region "$REGION" \
--filters Name=domain,Values=vpc \
--query 'Addresses[?AssociationId==null].AllocationId' \
--output text)
if [ -z "$UNASSOCIATED_EIPS" ]; then
echo "No unassociated Elastic IPs found. Nothing to release."
exit 0
fi
echo "The following Elastic IPs will be released:"
echo "$UNASSOCIATED_EIPS"
echo ""
read -p "Are you sure? This cannot be undone. (yes/no): " CONFIRM
if [ "$CONFIRM" == "yes" ]; then
for ALLOC_ID in $UNASSOCIATED_EIPS; do
echo "Releasing: $ALLOC_ID"
aws ec2 release-address \
--region "$REGION" \
--allocation-id "$ALLOC_ID"
done
echo "Done. All unassociated EIPs released."
else
echo "Aborted. No changes made."
fi
IAM Permissions Required
Follow the principle of least privilege. The operator performing these actions needs only the following permissions — avoid granting broad ec2:* access.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ManageElasticIPs",
"Effect": "Allow",
"Action": [
"ec2:DescribeAddresses",
"ec2:DisassociateAddress",
"ec2:ReleaseAddress"
],
"Resource": "*"
}
]
}
Note: ec2:DescribeAddresses and ec2:ReleaseAddress do not support resource-level permissions and require "Resource": "*". This is an AWS service constraint, not a misconfiguration.
Proactive Cost Governance: Detect Idle EIPs Automatically
Use AWS Cost Explorer or AWS Trusted Advisor (Business/Enterprise support tiers) to surface idle EIPs. Alternatively, set up a scheduled AWS Config rule or EventBridge + Lambda to alert on unassociated EIPs before they accumulate charges.
A simple AWS CLI one-liner to check for idle EIPs across a region:
aws ec2 describe-addresses \
--filters Name=domain,Values=vpc \
--query 'Addresses[?AssociationId==null].[AllocationId,PublicIp]' \
--output table
Glossary
| Term | Definition |
|---|---|
| Elastic IP (EIP) | A static, public IPv4 address allocated to your AWS account, designed for dynamic cloud computing. |
| Allocation ID | The unique identifier (e.g., eipalloc-xxx) assigned to an EIP when it is allocated to your account. |
| Association ID | The identifier (e.g., eipassoc-xxx) created when an EIP is linked to a specific EC2 instance or ENI. |
| Disassociate | Detaches an EIP from an instance or network interface. The EIP remains allocated to your account. |
| Release | Permanently returns an EIP to AWS's address pool, removing it from your account and stopping all associated charges. |
Next Steps
- 📖 Official Docs: AWS Elastic IP Addresses — EC2 User Guide
- 💰 Current Pricing: Always verify current EIP pricing at the EC2 On-Demand Pricing page — rates vary by region and are subject to change.
- 🔍 Cost Audit: Run the idle EIP detection command above across all your active regions as a quick hygiene check.
- 🤖 Automate: Consider tagging EIPs with an owner and expiry policy, enforced via AWS Config custom rules.
Comments
Post a Comment