Compute is rarely the surprise
People budget for the server. The bills that shock people come from four other places.
Egress. Data leaving the cloud. Coming in is free; going out is $0.08–$0.12 per GB on the large providers. Do the arithmetic once: a 4MB hero image on a page with 500,000 views is 2TB of egress, about $170–180 for one image. This is why people move media to Cloudflare R2 or Backblaze B2, which charge nothing for egress. It is the single highest-leverage cost decision in most small projects.
Per-request pricing with no ceiling. Serverless bills per invocation and per gigabyte-second. Wonderful at your traffic. Unbounded if something loops.
Things you forgot are running. An idle load balancer is roughly $18 a month for doing nothing. A stopped VM still bills for its attached disk. Log ingestion at around $0.50/GB with 90-day retention accumulates quietly. Go and read your line items; there is usually something on there you cannot identify.
Per-token API calls. These scale with your users' enthusiasm, and with anyone who discovers an endpoint of yours that does not require an account.
The three shapes of a shock bill
- A recursive trigger. A function writes to a bucket; a write to that bucket triggers the function. This has produced five-figure overnight bills more than once, at real companies.
- A retry storm. Something downstream slows down, clients retry, every retry is another billed invocation, and the retries make the slowdown worse.
- Someone else using your endpoint. An unauthenticated route that calls a paid API is a free paid API for the entire internet, and it will be found by scanners within days of going live.
Alerts tell you; caps stop it
Set budget alerts — every provider has them. Put one at half of what you can genuinely afford and one at the full amount. Then be clear with yourself about what an alert is: most cloud providers do not offer a hard stop. The alert is a notification, often delayed by hours, sent to a human who may be asleep. It is a smoke detector, not a sprinkler.
The sprinklers are the things that act without you:
- Maximum instance count, or maximum concurrency, on your functions.
- A hard monthly spend limit on the API key at the provider that offers one.
- A queue with a bounded size in front of expensive work.
Prefer a cap that breaks a feature over an uncapped bill you have to pay in full.
Rate limits are cost control, not only abuse control
Every write path, and every path that costs money per call, gets a limit. Three decisions:
What you key on. Per-IP is weak: a whole university in Lagos or an office in Manila can share one address, so you punish a crowd; and an abuser rotates addresses for cents. Per-account is stronger. Per-API-key is strongest. Most real systems run both — a loose per-IP limit to blunt the crude stuff, a tight per-identity limit that actually protects the expensive thing.
The algorithm. A token bucket is the sensible default: a bucket holds N tokens, refills at R per second, each request takes one. It permits a burst and then enforces a steady rate. Fixed windows have a well-known flaw — send N at 11:59:59 and N at 12:00:00 and you have sent 2N in a second while staying "within limits".
The failure mode. When the rate limiter itself is unavailable, do you let traffic through or reject it? Fail open on reads, so a limiter outage is not a site outage. Fail closed on expensive or destructive writes. Decide this on a calm afternoon, because otherwise you discover your choice during the incident.
When you refuse, return 429 with Retry-After: 30. A silent drop or a generic 500 makes well-behaved clients retry harder.
The cheapest control is caching
A response served from a CDN never reaches your billed compute at all. Cache-Control: public, max-age=60 on a page that changes about once a minute removes most of your load and most of your bill, and it takes one line.
Before you move on