DEV Community

Gonzalo Gjuka
Gonzalo Gjuka

Posted on Originally published at controlcloud.dev on

Unused public IPs: what AWS and GCP charge and how to find them

A public IP looks free because it has no CPU and no disk. It is not. Since February 1, 2024, AWS bills USD 0.005 per hour for every public IPv4 address, whether it is attached to a resource or not. That is USD 3.65 per month per address (0.005 × 730 hours). One of them does not move the invoice; forty forgotten across three years and several accounts do: USD 146 per month that buys nothing.

What each cloud charges for a public IP

  • AWS: USD 0.005 per hour for every public IPv4 address, including unassociated Elastic IPs and the public IPs AWS assigns to instances, load balancers and NAT gateways. The rule applies since February 1, 2024 and is stated in the official AWS announcement of the public IPv4 charge.
  • GCP: a reserved static external IP that is not assigned to anything costs USD 0.01 per hour; the same address in use by a standard VM costs USD 0.005 per hour, in US regions according to the Google Cloud network pricing page. Unused costs twice as much: USD 7.30 per month against USD 3.65.
  • Azure: a Standard public IP bills by the hour for as long as it exists, associated or not. An IP left hanging from a NIC with no VM bills the same as one in use.

What sets an IP apart from other forgotten resources is that it shows up on no performance dashboard: it has no metrics, fires no alerts and takes no space. It only shows up on the invoice, as a small line that repeats every month.

Why orphaned IPs are left behind

Almost always because of an incomplete deletion. Deleting a VM by hand from the console removes the VM, but not necessarily what surrounded it:

  • On AWS, an Elastic IP outlives the instance: when you terminate it, the address stays allocated to your account, unassociated, billing.
  • On Azure, deleting the VM can leave the NIC and the public IP behind, unless the option to delete them with the VM was selected. The IP stays associated with a NIC that serves nobody, and it cannot be released until it is dissociated.
  • On GCP, an address reserved as static goes back to the RESERVED status when the VM that used it is deleted.

The other common source is testing: somebody reserves an IP for a temporary environment, the environment gets deleted and the IP does not.

How to find them, cloud by cloud

Read-only permissions are enough: that is least privilege in practice, because finding does not require the right to delete. On AWS, the Elastic IPs with no association (the command is per region):

aws ec2 describe-addresses \
  --query "Addresses[?AssociationId==null].[PublicIp,AllocationId]" \
  --output table
Enter fullscreen mode Exit fullscreen mode

There is one case this filter does not show: an Elastic IP associated with a network interface (ENI) that was left loose, with no instance. It reads as associated and serves nobody. To see it, list the ENIs in the available status with aws ec2 describe-network-interfaces --filters Name=status,Values=available.

On GCP, the reserved external addresses nobody uses:

gcloud compute addresses list \
  --filter="status=RESERVED AND addressType=EXTERNAL" \
  --format="table(name,address,region,status)"
Enter fullscreen mode Exit fullscreen mode

On Azure, the public IPs with no IP configuration attached, and the NICs with no VM:

az network public-ip list \
  --query "[?ipConfiguration==null].{name:name, rg:resourceGroup, ip:ipAddress}" -o table

az network nic list \
  --query "[?virtualMachine==null && privateEndpoint==null].{name:name, rg:resourceGroup}" -o table
Enter fullscreen mode Exit fullscreen mode

The privateEndpoint==null filter is not decoration: the NIC of a Private Endpoint has no VM either, and without that filter an interface in use shows up as orphaned. Deleting it cuts private access to a service.

Before deleting: proven orphan or unknown

A list of candidates is not a deletion list. If a command could not read a field, because of permissions or a region that did not answer, the honest answer is "I do not know", not "it is free". Releasing an IP in use is worse than paying for it one more month: the address does not come back, and if it was in a DNS record or on a customer allowlist, the outage is immediate.

An unused IP costs USD 3.65 per month on AWS. An IP released by mistake can cost an incident.

How Control Cloud handles it

Control Cloud reads the inventory of your clouds with read-only credentials and gives every IP one of three verdicts: orphaned, in use or unknown. The cost advisor lists only the proven orphans, each with its monthly cost: an Elastic IP that is unassociated or associated with an ENI whose instance no longer exists, a GCP static IP nobody uses, an Azure public IP held by a NIC with no VM. Unknown never counts as a saving, and on GCP an IP with no verdict is priced at the in-use rate, the lower one, so the number is not inflated.

The advisor total is the exact sum of the listed rows, at the same rates as the rest of the cost page, and those rates can be edited if your contract has different ones. To estimate what shutting VMs down can cut in non-prod, the savings calculator does the math, and shutting down dev after hours details what drops and what does not.

Top comments (0)