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.

stateDiagram-v2 direction TB [*] --> Allocated : aws ec2 allocate-address state Allocated { [*] --> Unassociated Unassociated --> Associated : aws ec2 associate-address Associated --> Unassociated : aws ec2 disassociate-address state Associated { Running : "Associated with
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
  1. Allocated + Running Instance: The only state where you pay zero for the EIP itself. The address is actively routing traffic.
  2. 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.
  3. Allocated + Unassociated: You explicitly detached the EIP from any resource. Still billed — the address is yours but unused.
  4. 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

  1. Navigate to EC2 Console → Network & Security → Elastic IPs.
  2. Select the Elastic IP address you want to release.
  3. If it is still associated: click Actions → Disassociate Elastic IP address, then confirm.
  4. 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

Popular posts from this blog

EC2 No Internet Access in Custom VPC: Attaching an Internet Gateway and Fixing Route Tables

IAM User vs. IAM Role: Why Your EC2 Instance Should Never Use a User

Lambda Infinite Loop with S3: How to Prevent Recursive Triggers