M
arch: €8,400. June: €17,900. Requests over the same period were up six percent.The meeting to explain it had four people in it and produced four theories. The CTO thought it was the new search feature. The backend lead was fairly sure it was the analytics warehouse. Somebody suggested the instance sizes, which is what somebody always suggests, and forty minutes went on comparing m6i.2xlarge against m6i.xlarge while the actual cause sat in a different service entirely.
When an AWS bill increased without a traffic increase, the cause is nearly always a step rather than a slope, and the step happened somewhere between two and six weeks before the invoice that made you look. Most spend at this scale is a function of provisioned capacity and architectural decisions, not of request volume, so the useful question is what changed in the infrastructure, not what changed in the traffic. Find the step, find its commit date, and you have usually found the whole delta in one line.
Why did the bill double when traffic didn't?
Because the majority of what you pay for is sitting there whether requests arrive or not.
An instance costs the same at three requests per second as at thirty. A NAT Gateway bills by the hour before it moves a byte. A database provisioned for a launch that went well stays provisioned when the launch is over. Genuinely traffic-proportional costs, meaning per-request compute and egress, are a minority of the bill for most companies at this stage, which is why a six percent traffic change cannot produce a hundred percent cost change and why looking for one wastes the meeting.
The timing is the part that misleads everyone. Billing lags the cause. A change merged on 14 May runs for half of May and all of June, so the June invoice is the first one showing its full monthly cost, and by then the pull request is five weeks back in the history and nobody connects the two. Teams search for causes in the billing period they are looking at. The cause is almost always in the one before it.
So the first move is not Cost Explorer grouped by service. It is Cost Explorer grouped by service with a twelve-month range and daily granularity, looking for the day the line steps up. That date is your search term, and you take it to the deploy log rather than to a meeting.
Where does the money actually sit at this scale?
The ranges below are what I have found across companies spending somewhere between €5,000 and €50,000 a month. They overlap and they do not sum to a hundred, because the mix moves a lot with architecture.
| Line | Typical share | Why it grows without anyone deciding |
|---|---|---|
| Compute instances | 35 to 55% | Sized during a load test, never reverted |
| Managed database | 15 to 25% | Provisioned for peak, storage only grows, snapshots retained forever |
| Data transfer and NAT | 8 to 20% | No dashboard attributes it to a service |
| Log and metric ingestion | 5 to 15% | Scales with verbosity, not with traffic |
| Non-production environments | 10 to 20% | Runs at 3am on Sunday |
| Object and block storage | 3 to 8% | Orphaned EBS snapshots from instances that no longer exist |
Data transfer is the row worth staring at, because it is the one nobody can see. A NAT Gateway runs about $0.045 per hour plus roughly the same per gigabyte processed, which is around $33 a month before traffic, and a conventional three availability zone setup runs three of them. That is roughly $100 a month for the privilege of having them, then per-gigabyte charges on everything your private subnets send outward, including every pull from S3.
The fix for the S3 half is free and most teams do not know it exists. A Gateway VPC Endpoint for S3 or DynamoDB costs nothing and takes that traffic off the NAT path entirely. Interface endpoints, the PrivateLink kind, do cost money, which is where the confusion comes from. Check whether you have the free ones before optimising anything else, because it is a ten-minute change.
Cross-availability-zone transfer is the other quiet one, billed in both directions at a cent per gigabyte each way. A chatty service whose replicas got spread across zones for availability pays that toll on every internal call, and nothing in the console labels it as belonging to that service.
Log ingestion deserves a mention because it grows in a way that feels unrelated to anything. CloudWatch charges around $0.50 per gigabyte ingested, and log groups default to never expiring, so a debug level switched on during an incident in February and never switched back is still costing money in July. If you are paying for observability, it is worth knowing what logs, metrics and traces each cost you and which of the three you are over-buying.
How do I attribute cost to a feature?
Tag at provisioning time, in your infrastructure code, and accept that the result will be incomplete.
Retroactive tagging does not work, and there is a specific trap here: cost allocation tags apply from the moment you activate them in the billing console and do not backfill. Activate them today and you get a useful report in a month, which is an argument for doing it before you need it rather than during the panic.
The realistic target is around eighty percent of spend attributed. Chasing the last twenty is an audit rather than a decision, and some of it genuinely cannot be attributed, because NAT charges and cross-AZ transfer belong to no single service by construction. Allocate the remainder proportionally and move on. What you want at the end is a sentence like "search costs us €2,100 a month and earns nothing", which is a product decision. Perfect attribution that arrives in six weeks changes nothing.
The same cost shape shows up on platform pricing, not only on raw infrastructure, and what Vercel actually charges at scale is the clearest worked example of a bill that looks like usage and is really a set of step functions. If your answer to any of this is that the hosting model itself is wrong, that is a bigger question covered in VPS versus managed cloud versus serverless, and it is a decision worth making deliberately rather than as a cost reaction.
What is the single-cause pattern?
The largest part of a doubling is usually one thing, and it is usually not the thing being argued about.
One unindexed query that started scanning after a table crossed a size threshold. One retry policy without backoff. One cron job that fans out per customer and now has four times the customers. One service that became chatty after a refactor moved a call inside a loop.
The worst one I have traced took a while precisely because the cost had a respectable explanation. A downstream service started timing out intermittently. The client retried without jitter, so every caller retried in lockstep, which tripled read load on the database replicas. The team saw replica CPU pegged, did the responsible thing under pressure, and scaled the replicas up. The timeouts stopped. Everybody moved on, and the larger replicas stayed, because nothing ever tells you to scale back down.
That is the shape to watch for. The bill increase was not the bug. It was the remediation of the bug, made permanent, and the original defect was fixed three weeks later by someone else without anyone revisiting the capacity decision. Any time you find a step in the graph, check whether it was a deliberate scale-up in response to an incident, and then check whether the incident is still happening.
What should I not optimise?
Anything where the engineering time costs more than the saving, which is a larger category than founders expect.
A week of senior engineering time is roughly €2,500 fully loaded. Saving €200 a month pays that back in a bit over a year, assuming the saving persists and nothing you built needs maintaining, which it will. Below roughly €500 a month of recurring saving, the work is a loss and should be left alone. Write the finding down, group several of them, and do them together when one person has a quiet week.
The gotcha here catches disciplined teams rather than careless ones. Savings Plans and Reserved Instances bought during a growth spike lock in the wrong shape. You commit for one or three years at a usage level measured at your peak, then two months later a refactor drops steady-state usage by forty percent, and you keep paying for the commitment you no longer use. Commit against your trough rather than your peak, layer smaller commitments over time instead of one large one, and prefer Compute Savings Plans over EC2 Instance Savings Plans, since the former stay flexible across instance family and region while the latter do not.
Non-production is worth a look for a different reason. It is the one line where a schedule, rather than an architecture change, does the work, and shutting development environments outside working hours removes about two thirds of their cost for an afternoon of effort. It also gets more expensive in a way nobody forecasts once you start running a separate stack per enterprise customer, which is a decision made for sales reasons with an infrastructure bill attached.
Below about €5,000 a month of cloud spend, hiring anyone to do this work will not pay for itself, and that includes hiring us. Open Cost Explorer, group by service, set daily granularity across twelve months, and look for steps. Most bills of that size give up their answer in twenty minutes to somebody who has never seen the system, because the step is visible and the deploy log is right there. Paid help earns its cost when spend is large enough that a ten percent finding exceeds the engagement, or when the difficulty is attribution across a genuinely complicated estate rather than one line that moved.
One structural thing has changed in your favour, incidentally. AWS, Google Cloud and Microsoft all announced free egress for customers leaving in early 2024, ahead of the EU Data Act applying from 12 September 2025, so the exit cost that used to make this analysis academic is largely gone. Whether you should move is a separate question with a mostly different answer, but the transfer bill is no longer the thing deciding it.
Questions founders ask on billing day
Why did my AWS bill increase when traffic stayed flat? Because most of the bill is provisioned capacity rather than usage. Look for a step in daily cost during the previous billing period, then match that date against your deploy history.
What is the most common hidden cost on an AWS bill? NAT Gateway processing and cross-availability-zone data transfer, because neither appears attributed to the service causing it. Both are billed per gigabyte on traffic that looks internal and free from inside the application. Check whether you have the free S3 and DynamoDB Gateway endpoints configured before optimising anything else, since that one change removes a large share of NAT traffic in most setups and takes about ten minutes.
How do I find out which feature is costing me money? Activate cost allocation tags and enforce them in your infrastructure code. Tags do not apply retroactively, so expect a useful report a month after you start rather than immediately.
Are Reserved Instances or Savings Plans worth it for a startup? Yes, provided you size them against your trough rather than your peak and layer smaller commitments over time.
When is cloud cost optimisation worth paying someone for? Roughly above €5,000 a month of spend, or when the difficulty is attributing cost across many services rather than explaining one line that moved. Below that, an afternoon in Cost Explorer usually finds it.
Set Cost Explorer to daily granularity across twelve months and look for a step rather than a slope. Steps have causes, causes have commit dates, and that date narrows the search from your entire infrastructure to whatever shipped that week.