What the Cloud Actually Costs
The Number Nobody Can Explain
Every organisation running on the cloud has the same monthly ritual. An invoice arrives, somebody notices it is larger than last month, and a question goes round: why? Very often nobody can answer. Not because the people involved are careless, but because a cloud bill is not one price for one thing. It is thousands of tiny charges, in units nobody chose, for resources that several different people created at different times for reasons that were good at the time.
An earlier lesson in this series worked through a small monthly bill line by line and named the two most common surprises. This one is about the machinery underneath that bill: how the pricing model is actually constructed, why the expensive parts are so consistently the parts nobody looks at, and what the practice of keeping it under control looks like when it is done properly rather than in a panic.
The Distinction That Explains Almost Everything
There is one idea that, once it lands, makes most cloud billing behaviour obvious. Every charge is either for capacity you reserved, or for work that was done. Reserved capacity bills continuously from the moment you create it until the moment you delete it, whether anybody uses it or not. Work bills only when work happens.
People arrive at the cloud expecting the second model and are quietly billed on the first. The mental picture is a utility - use more, pay more; use nothing, pay nothing. That is genuinely how a few services work. It is not how most of them work, and the gap between those two pictures is where surprise bills live.
Look at what dominates that total. It is not the server running your code. It is the NAT gateway and the load balancer - two pieces of plumbing that do nothing on their own, that most people never think about after setting them up, and that together cost more than the application server and its database combined. Meanwhile the two lines that genuinely track usage sit at zero, which is exactly what people expect the whole bill to do.
This is also the honest answer to why a hobby project can cost forty dollars a month with no users. Nothing is wrong and nothing is being wasted in an obvious sense. You built an architecture whose fixed costs are simply larger than its variable ones, which is normal, and only becomes a problem when nobody realises it.
The Units Are Deliberately Small
Cloud prices are quoted in units chosen to look harmless. Four and a half cents an hour. Two thousandths of a cent per request. A tenth of a cent per gigabyte-month. None of these sound like money, and that is precisely the problem: a human being cannot estimate an annual cost from a per-hour figure without doing arithmetic, and almost nobody does the arithmetic before creating the resource.
What the quoted price actually means over a month and a year
$0.005 / hour an idle public IPv4 address $3.65 / mo $43 / yr
$0.0104 / hour a t3.micro instance $7.59 / mo $91 / yr
$0.0225 / hour a load balancer, doing nothing $16.43 / mo $197 / yr
$0.045 / hour a NAT gateway, before traffic $32.85 / mo $394 / yr
$0.08 / GB-month 100 GB of EBS disk $8.00 / mo $96 / yr
$0.05 / GB-month a 500 GB snapshot nobody deletes $25.00 / mo $300 / yr
$0.50 / GB 1 TB/month of CloudWatch logs $500.00 / mo $6,000 / yr
$0.09 / GB 1 TB/month out to the internet $90.00 / mo $1,080 / yr
$0.01 / GB 1 TB/month across two AZs, both
directions $20.00 / mo $240 / yr
The multiplier from "per hour" to "per year" is 8,760.
It is worth memorising, because nothing on any pricing page reminds you of it.The CloudWatch line in there is not a typo, and it is worth pausing on because it is one of the most reliably shocking discoveries people make. Log ingestion is charged per gigabyte, at a rate far higher than storage, and a debug logging level left switched on in production after an incident can generate terabytes. It is entirely routine for a team's logging bill to exceed the cost of the servers producing the logs.
The Largest Lever Is a Purchasing Decision
Most cost-reduction advice is about using less. The single biggest saving available to most teams is not that at all - it is paying a different price for exactly the same thing. Cloud providers sell identical capacity at wildly different rates depending on what you are willing to commit to in return.
The four options are worth understanding as a spectrum of commitment rather than a menu. On-demand is the default and the most expensive; you promise nothing and can stop at any moment. Savings Plans are a commitment to spend a certain amount per hour on compute for one or three years, and in exchange everything up to that level is discounted; crucially they are flexible about which instances you run, so they survive you changing your architecture. Reserved Instances discount more deeply but tie you to a specific instance family in a specific region. Spot sells you spare capacity at a steep discount on the condition that it can be taken back with about two minutes of warning.
It is worth naming what these actually are, because it clarifies how to think about them. A Savings Plan is a forward contract. You are agreeing today to buy a certain quantity at a certain price for a year, and the discount is the seller paying you for the certainty. That framing tells you exactly where the risk sits: if your usage falls below the level you committed to, you pay for it anyway. Commit to your floor, never to your peak.
The strategy that follows is straightforward. Work out the baseline you are confident you will still be running in a year - the steady, boring, always-on part - and buy commitments for that. Leave the variable part on-demand, since that is what on-demand is for. Put anything genuinely interruptible on spot: batch jobs, CI runners, data processing, anything that can be restarted without a human noticing. Most teams do none of this and pay list price on everything, indefinitely.
Do not buy a three-year Reserved Instance in your first year on the cloud. Your architecture will change more than you expect, and a commitment to a specific instance family becomes an obstacle to improving anything. Start with a one-year Savings Plan sized to a baseline you are genuinely sure about. An under-sized commitment costs you a missed discount; an over-sized one costs you real money every month until it expires.
Where the Surprises Actually Hide
Surprise line items are not random. They cluster in a small number of predictable places, and once you know the list you can check it in an afternoon. Almost all of them share a property: nothing in the console suggests, at the moment you create the thing, that it will cost anything meaningful.
Storage that outlived its purpose is the most common. Disks detached from deleted instances keep billing at full rate. Snapshots taken before a risky change accumulate for years. Old machine images sit behind snapshots that keep charging even after the image is deregistered. None of these appear anywhere you would normally look, because nothing is running.
Then there is capacity reserved and forgotten: a load balancer left behind after the service it fronted was retired, a public IPv4 address allocated for a machine that no longer exists, provisioned database throughput set high during a launch and never lowered. And there is the whole category of non-production environments - staging, development, per-developer sandboxes - running at full size through every night and weekend, which is roughly seventy percent of the hours in a week.
But the one that scales, and therefore the one that eventually dominates, is data transfer.
The Cross-Zone Charge Deserves Its Own Section
Of everything on that map, traffic between availability zones is the one that catches out the most competent teams, and it is worth working through slowly because the reason is uncomfortable: it is caused by doing the right thing.
You were told, correctly, to spread across availability zones so that losing one building does not take your application down. So your application servers run in zone A and zone B, and your database has a standby in a different zone from its primary. Now consider what happens on every single database query from an application server that happens to be in a different zone from the database's current primary: the query crosses a zone boundary, and so does the response. Both directions are charged, at a cent per gigabyte each.
One cent per gigabyte sounds like nothing. A moderately busy application that moves a terabyte a month between its application tier and its database is paying twenty dollars a month for the privilege of being resilient. Add a caching layer in another zone, a message queue, service-to-service calls in a microservice architecture where nothing is zone-aware, and this line grows quietly and in direct proportion to how successful you are.
The wrong response is to collapse everything into one availability zone to save the transfer cost. That converts a predictable twenty dollars a month into an outage risk, which is a much worse trade. The right responses are to know the charge exists, keep chatty components zone-aware where the framework allows it, use read replicas so reads stay local, and cache aggressively - all of which reduce the traffic rather than removing the resilience.
You Cannot Cut What You Cannot See
Every cost-reduction effort runs into the same wall on day one: the bill says what was spent, but not who spent it or what it was for. A line reading "EC2 - other, $4,182" is true and completely useless. The fix is tagging, and it has to be in place before you need it, because tags cannot be applied retroactively to costs already incurred.
The important detail people miss is that tags do nothing for cost reporting until they are explicitly activated as cost allocation tags in the billing console. You can tag diligently for six months, go to build a report, and find none of it available - the tags were on the resources, but nobody switched them on for billing. Activate them the same day you start using them.
# The reliable way to tag is to stop relying on people remembering.
# default_tags applies these to every resource this provider creates,
# so a tag is only missing if somebody deliberately removed it.
provider "aws" {
region = "eu-central-1"
default_tags {
tags = {
Environment = "production" # split prod from the rest
Team = "platform" # who to ask about this
Service = "checkout" # which product surface it serves
ManagedBy = "terraform" # anything without this was hand-made
CostCentre = "eng-infra" # who the bill belongs to
}
}
}
# Then, once, in the billing console: Cost Allocation Tags -> activate each
# of these. Until you do, none of them appear in any cost report, no matter
# how correctly the resources are tagged.That `ManagedBy` tag earns its place faster than the others. Filtering a cost report to everything without it gives you, immediately, the list of resources somebody created by hand and never wrote down - which is both a cost problem and the archaeology problem from the Infrastructure as Code lesson, showing up on an invoice.
Total Cost Is the Wrong Number
Here is where cost stops being an operations question and becomes an economics one. "Our cloud bill went up thirty percent" is not information. It is only meaningful next to what the business did over the same period. If usage doubled and the bill rose thirty percent, that is excellent news. If usage was flat and the bill rose thirty percent, something is wrong. The absolute number cannot distinguish those two cases, and teams routinely panic about one while missing the other.
What you want is a unit cost: cost divided by whatever your business actually counts. Cost per customer, per order, per thousand API requests, per gigabyte processed. Pick the denominator that matters for your product and track the ratio over time rather than the total.
The same six months, read two ways
cloud bill customers cost per customer
January $12,400 820 $15.12
February $13,100 910 $14.40
March $14,800 1,050 $14.10
April $16,200 1,240 $13.06
May $18,900 1,510 $12.52
June $21,300 1,780 $11.97
Total bill: up 72% -> looks alarming in a board meeting
Cost per customer: down 21% -> the infrastructure is getting more
efficient as it grows
Same numbers. Opposite conclusions. Only the second one is actionable.The direction of that ratio is the thing to watch. Falling unit cost means you have economies of scale: growth makes you more efficient, and the fixed costs from the first diagram are being spread over more customers. Flat unit cost means you are essentially reselling infrastructure at a markup, and growth will never improve your margins on its own. Rising unit cost is a genuine alarm - each new customer costs more than the last - and it is the shape that quietly kills otherwise successful products.
FinOps, Without the Consultancy Language
FinOps is the name that got attached to treating cloud cost as an ongoing engineering practice rather than an annual fire drill. Stripped of the vocabulary, it is three phases that repeat, and the useful insight is not what they contain but which ones organisations skip.
The pattern is remarkably consistent. A bill arrives that nobody expected, somebody spends a week deleting things and rightsizing instances, the number comes down, everyone is relieved, and nothing changes structurally. Six to nine months later the same meeting happens again. The cleanup worked; it just was not a system, so it decayed at exactly the rate that new resources were created.
The phase that actually changes outcomes is the third one, and it is the least technical. Somebody has to own the number. It has to be looked at monthly, when it is a small correction rather than a crisis. And cost has to enter the conversation at design time, when choosing between two architectures is free, rather than at invoice time when changing your mind is expensive.
What to Do on Monday
In rough order of how much they return for the effort involved. First, set a budget alert if you do not have one, which takes about two minutes and is the difference between finding out now and finding out in five weeks. Second, find the orphans: unattached volumes, old snapshots, unassociated IP addresses, load balancers with no healthy targets. This is usually a few hours and frequently pays for itself immediately.
Third, turn off non-production environments outside working hours. Nights and weekends are around seventy percent of the week, and a scheduled shutdown is a genuinely large saving for a small amount of automation. Fourth, look at your steady baseline and buy a one-year Savings Plan against the part you are confident about. Fifth, open the data transfer section of your bill specifically - not the total, the transfer lines - and find out which boundary your traffic is crossing and why.
Only then is it worth rightsizing individual instances, which is where most people start. It is slower, it needs real measurement to do safely, and it usually returns less than any of the five things above.
Before adopting any new managed service, find its pricing page and work out what it costs at zero usage, then at ten times your current usage. Two numbers, five minutes. It will not always change the decision, but it will occasionally change it dramatically - and it is far easier to have that conversation before the thing is load-bearing than after.
None of this is about being frugal for its own sake. Spending money on infrastructure that earns more than it costs is exactly what you should do. The point is to know which of your spending does that, and right now, for most teams, a meaningful share of the bill is paying for capacity that nothing is using and nobody remembers creating.
Further reading
- AWS Pricing CalculatorModel an architecture's cost before building it. The habit of doing this at design time is most of what this lesson is arguing for.
- AWS Data Transfer PricingThe authoritative version of the transfer map above, including the cross-availability-zone charges that are easy to miss.
- Savings PlansHow the commitment discounts actually work, including the difference between compute and instance plans.
- Activating Cost Allocation TagsThe step that makes tags visible in billing. Tagging without doing this produces nothing.
- The FinOps FoundationThe framework behind the inform, optimize and operate loop, from the group that defined it.
- The Duckbill Group's BlogIndependent AWS cost consultants writing candidly about billing behaviour, including the parts vendor documentation is diplomatic about.
- AWS Well-Architected: Cost Optimization PillarAmazon's own structured guidance. Useful as a checklist, and worth reading alongside a source that does not sell you the capacity.