Logo
CloudWithSingh
Back to all posts
AWS
Cost Management
Best Practices

I Got an Unexpected $73 AWS Bill on the Free Tier — Here's What Happened

A breakdown of exactly where the AWS Free Tier tripped me up, the services that cost money when you're not looking, and how to set up billing alerts before it happens to you.

Parveen Singh
November 3, 2025
9 min read
TLDR

My $73 "free tier" AWS bill came from: NAT Gateway ($33), RDS left running ($19), CloudWatch metrics ($9), EBS snapshots ($5), Elastic IP ($4), data transfer ($2), and S3 requests ($0.50). Prevention: set up billing alerts on day one, check for running resources after every session, and never leave NAT Gateways running overnight.

Let me tell you about the most expensive lesson I've learned in cloud computing — and I've been doing this for years.

I'm not new to cloud — and I'm not new to AWS either. I've had my Cloud Practitioner certification since 2020. I manage Azure environments professionally, I teach cost management, and I literally wrote a blog post about Azure cost traps. I should have known better.

But when I set up a fresh AWS account to build labs and produce content, AWS still got me. $73.47 in the second month of running that account.

Here's the breakdown, because I think every Azure engineer working across both platforms needs to see this.

The Bill Breakdown

When I opened the AWS Billing Dashboard (which, by the way, is called "Billing and Cost Management" — not "Cost Management + Billing" like Azure, because of course they're different), here's what I saw:

ServiceChargeWhat Happened
NAT Gateway$33.48Left running 24/7 for a test VPC
Elastic IP$3.60Allocated but unattached after stopping an EC2 instance
EBS Snapshots$5.23Automated snapshots I forgot about
Data Transfer$2.40Inter-AZ traffic during testing
CloudWatch$8.76Custom metrics and log ingestion
RDS$19.50Forgot to stop a db.t3.micro after a tutorial
S3$0.50Request charges (not storage)

Total: $73.47 on what was supposed to be a free tier account.

The NAT Gateway Trap ($33.48)

This is the one that burns the most AWS newcomers, and it's the one Azure engineers are least prepared for.

In Azure, if you want a VM in a private subnet to access the internet, you have a few options — Azure NAT Gateway, a load balancer with outbound rules, or just default outbound access (which Azure is deprecating, but still). None of these surprise you with a $33/month bill for a test setup.

AWS NAT Gateway costs $0.045 per hour plus $0.045 per GB of data processed. That's roughly $33 per month just for it to exist, before any data flows through it.

I set up a VPC with public and private subnets using the AWS VPC wizard. The wizard helpfully created a NAT Gateway for the private subnet. I deployed a test EC2 instance, ran some commands, and moved on to learning other services.

The NAT Gateway just sat there. Running. Charging. For 24 days.

The fix: Use VPC endpoints for AWS services (S3, DynamoDB, etc.) instead of routing through NAT Gateway. If you genuinely need internet access from a private subnet for testing, create the NAT Gateway, do your work, and delete it immediately. Or better yet — use a public subnet for learning and skip NAT Gateway entirely.

Warning

AWS NAT Gateway costs $0.045/hour (~$33/month) just to exist, plus data processing charges. The VPC wizard creates one by default. If you're learning, use public subnets and skip NAT Gateway entirely — or delete it immediately after testing.

The Elastic IP Surprise ($3.60)

This one is uniquely AWS. In Azure, public IP addresses cost money whether they're attached or not (unless they're Basic SKU, which are being retired). But the pricing is transparent and you see it coming.

In AWS, an Elastic IP is free when it's attached to a running EC2 instance. The moment you stop that instance or detach the IP, AWS starts charging you $0.005 per hour. That's about $3.60 per month.

The reasoning makes sense — AWS has a finite pool of IPv4 addresses and doesn't want people hoarding them. But it catches you off guard when you stop an instance for the weekend and come back to charges.

What happened: I created an EC2 instance for IAM testing, allocated an Elastic IP, attached it, tested for a few hours, stopped the instance, and forgot about the Elastic IP. It sat there for three weeks, quietly charging.

The fix: When you stop an instance you don't need anymore, release the Elastic IP. Better yet, don't allocate Elastic IPs for test instances — use the auto-assigned public IP instead (it'll change on restart, but for testing that's fine).

Gotcha

Elastic IPs are free when attached to a running instance, but AWS charges $0.005/hour (~$3.60/month) when they're unattached or attached to a stopped instance. When you stop a test instance, always release the Elastic IP too.

CloudWatch: Death by a Thousand Metrics ($8.76)

In Azure, basic monitoring with Azure Monitor is included. You get platform metrics, activity logs, and basic alerts at no additional cost. Diagnostic settings start costing money, but the defaults are reasonable.

AWS CloudWatch has a free tier, but it's easy to exceed:

  • 5 custom metrics are free. After that, it's $0.30 per metric per month.
  • 5 GB of log ingestion is free. After that, $0.50 per GB.
  • Basic monitoring (5-minute intervals) is free. Detailed monitoring (1-minute) costs extra.

Here's what got me: I enabled detailed monitoring on my EC2 instances (because why wouldn't you want more frequent data?), and some services I deployed created custom metrics automatically. The CloudWatch Logs agent was also ingesting more data than I realised from test applications.

The fix: For learning environments, stick with basic monitoring. Set up a CloudWatch billing alarm (yes, you can use CloudWatch to alert you about CloudWatch costs — AWS inception). Review the CloudWatch console's "Usage" tab regularly.

RDS: The "I'll Stop It Later" Problem ($19.50)

AWS RDS has a free tier: 750 hours of db.t3.micro per month for 12 months. But here's the catch — that's single-AZ only, and the moment you select Multi-AZ (which is the default recommendation for production), you lose free tier eligibility.

I launched a db.t3.micro for a tutorial on connecting EC2 to RDS. The tutorial was maybe 2 hours of work. But I left the database running for 13 days because I thought I'd come back to it.

In Azure, you might do the same thing with Azure SQL, but Azure SQL basic tier is about $5/month. AWS RDS adds up faster because the instance is always running — there's no true "serverless" option for RDS MySQL/PostgreSQL at the micro tier.

The fix: Use Aurora Serverless v2 for learning if you want a database that scales to near-zero. Or just stop/delete test RDS instances the same day you create them. Write your connection strings and config somewhere so you can recreate quickly.

What Azure Does Better on Cost Transparency

I want to be fair here. After experiencing both platforms' billing:

  • Azure Cost Management gives you better cost visualization out of the box. The cost analysis blade with resource group breakdowns is more intuitive than AWS Cost Explorer.
  • Azure Advisor actively warns you about idle resources. AWS has similar tools (Trusted Advisor, Compute Optimizer), but the free tier of Trusted Advisor is limited.
  • Azure spending limits on PAYG subscriptions can prevent runaway costs entirely. AWS doesn't have an equivalent — there's no way to hard-cap spending.

What AWS Does Better

  • AWS Budgets let you set custom budgets with alerts. The setup is more flexible than Azure's budget alerts.
  • Cost Explorer has better historical analysis and forecasting.
  • AWS Free Tier tracking — there's a dedicated page showing exactly where you stand against free tier limits. Azure doesn't have anything this clear.

The Prevention Checklist I Now Use

After this experience, here's what I do in every new AWS account:

  1. Day one: Set up billing alerts

    • Create a zero-dollar AWS Budget
    • Configure SNS notifications to email
    • Enable Cost Explorer (takes 24 hours to activate)
  2. After every learning session:

    • Check for running EC2 instances: aws ec2 describe-instances --filters "Name=instance-state-name,Values=running"
    • Check for NAT Gateways: aws ec2 describe-nat-gateways --filter "Name=state,Values=available"
    • Check for unattached Elastic IPs: aws ec2 describe-addresses
    • Check for running RDS instances: aws rds describe-db-instances
  3. Weekly review:

    • Open the Billing Dashboard
    • Check the Free Tier usage page
    • Review Cost Explorer for any unexpected line items
  4. Use tags religiously:

    • Tag everything with Environment: learning
    • Use AWS Resource Groups to find all learning resources
    • Makes cleanup much easier
Pro Tip

Create a "cleanup" shell script with the resource-checking commands from the checklist above. Run it at the end of every AWS learning session. Five minutes of checking saves $50+ of forgotten resources.

The Real Lesson

The $73 wasn't the point. I could afford it. But imagine a student following an AWS tutorial, launching a NAT Gateway and an RDS instance during a weekend lab session, and not checking their bill for a month. That's potentially $50-100 they didn't budget for. If you're learning AWS, especially coming from Azure where the cost patterns are different, respect the billing differences. AWS gives you incredible power and flexibility, but the trade-off is that it also gives you incredible ability to spend money without realising it.

Set up your billing alerts first. Check your bill weekly. And for the love of all things cloud — delete your NAT Gateways when you're done testing.

Resource

Next: S3 for Blob Storage Users

What Azure Blob Storage engineers need to know about S3 — buckets, storage classes, and permission models.

Read Next

Part 3 of my "Azure to AWS" series. Previously: AWS IAM for Azure Admins. Next: diving into S3 and what Azure Blob Storage users need to know.

Read Next