Written by Formiga, an independent AWS consultancy. This page contains no product, nothing to buy on it, and no capability claim about any tool. It quotes no prices, on purpose — section 6 explains why and links the pages that publish them.
Every AWS cost review we have ever been asked to run starts the same way: someone has sorted the bill descending, and the top of the list is not compute. It is NAT Gateway data processing, or data transfer — regional, or a five-figure line for requests against an S3 bucket whose storage costs almost nothing, or CloudWatch Logs ingestion that grew faster than the traffic did.
These four are the lines teams try to cut first, because they read as pure overhead. Nobody designed them. Nobody asked for them. They have no feature attached. And that is exactly why they are dangerous to cut: each of these lines is the meter on an architectural property you may have chosen deliberately — private egress, multi-AZ resilience, durability, or the forensic record you need during an incident. The invoice charges you for the property. It does not name it.
The question is never “how do we make this line smaller”. It is “what am I buying with it, and do I still want that?” A cut that answers the first question and skips the second is not an optimisation. It is an undocumented change to your risk position, paid for in a currency the finance report does not track.
Before touching any of the four, we ask three questions in this order. They are cheap, they are answerable from data you already have, and skipping the first one is the most common mistake we see.
What generates it. Every byte that a resource in a private subnet sends to or receives from anything outside the VPC through a NAT Gateway is charged for processing, in addition to the gateway’s hourly charge and any onward data-transfer charge. Crucially, this includes traffic to AWS’s own regional service endpoints — S3, DynamoDB, ECR, Secrets Manager, CloudWatch, SSM — when those calls are routed out through NAT rather than through a VPC endpoint. A container fleet that pulls a large image on every task start, or a job that streams objects out of S3 by the terabyte, pays the NAT meter for traffic that never leaves AWS.
The usual fix. Put VPC endpoints in front of the services that dominate the volume. Gateway endpoints for S3 and DynamoDB, interface endpoints (PrivateLink) for the rest.
What the fix costs you, and this is the part that is usually skipped. Gateway and interface endpoints are not the same product and do not price the same way: interface endpoints carry their own hourly charge per endpoint per Availability Zone plus their own per-GB processing. On a low-volume, many-services estate it is entirely possible to add twelve interface endpoints across three AZs and spend more than the NAT processing you removed. Endpoints are also an access-control surface: an endpoint policy is a new place where a permission can be wrong, and it fails closed, in a way that looks like a service outage rather than a policy error. Do the volume analysis per service first. The fix is right for the two or three services that carry the traffic and is frequently wrong for the tail.
What generates it. AWS’s own pricing page states that data transferred “in” to and “out” from EC2, RDS, Redshift, DAX, ElastiCache and elastic network interfaces across Availability Zones in the same Region is charged in each direction. That last phrase is the one people miss: a byte crossing a zone boundary between two of your own resources meters twice. It accumulates in places nobody thinks of as “data transfer”: a service mesh with random endpoint selection, a Kafka or Cassandra cluster replicating across zones by design, chatty call graphs where one user request fans out to twenty internal calls, a batch job reading a dataset held in one zone from workers spread over three.
Two exceptions worth knowing before you go hunting, because they redirect the search. First, load balancers are not uniform: AWS states that you are not charged regional data transfer for cross-zone load balancing on an Application Load Balancer (where it is always on), and that you are charged for it on a Network Load Balancer (where it is off by default and you turned it on). If your bill has a large regional-transfer line and an ALB in front, the ALB is not where it is coming from — keep looking at what your targets do next. Second, same-zone traffic is free only over private addresses: AWS charges the same in-each-direction rate for traffic to and from a public or Elastic IPv4 address. A service that reaches its neighbour by public DNS name pays the cross-zone rate even when the neighbour is on the next rack. That one is common, it is invisible in code review, and it is usually a one-line fix.
The usual fix. Zonal affinity — keep a request’s hops inside one Availability Zone, use topology-aware routing, colocate the chatty pair.
What the fix costs you. This is the one where the cheap answer and the correct answer diverge most sharply, and it is why we put it second rather than first. The cheapest possible version of this fix is to stop spanning zones at all. Cross-AZ transfer is the meter on multi-AZ resilience. If you reduce it by collapsing into a single AZ, you have not saved money; you have sold an insurance policy and booked the premium as profit. The honest version of the fix keeps the redundancy and removes the gratuitous zone crossings — the ones no availability argument requires. Telling those apart requires knowing which call graphs must survive a zone failure, which is an architecture question and not a billing one. If your recovery objectives have never actually been exercised, you do not yet know what you are about to give up: that problem has its own page.
What generates it. S3 charges per request as well as per stored byte, at different rates for different request classes. An architecture that writes one small object per event — per IoT reading, per log line, per transaction — can pay far more for the PUTs than for the bytes. The same is true of workloads that LIST large prefixes on a schedule, or read a manifest object once per worker per invocation, or use a bucket as a job queue by polling it.
The usual fix. Fewer, larger objects: buffer and batch on the write path, compact small files, use a real queue for queueing, cache the manifest, replace scheduled LIST with notifications or an inventory.
What the fix costs you. Batching converts a request cost into a latency cost and an operational cost. The compaction job you just introduced is a thing that runs, fails, backs up and wakes someone. Buffering means a window in which data exists in one place only — which is a change to your recovery point, quietly made in a cost review. And the neighbouring “saving” is worse: moving small, frequently-read objects to a colder storage class reduces the storage line while raising the per-request cost and adding retrieval charges and minimum-duration charges. We have seen a storage-class change presented as a saving that increased the total. Model the request profile, not just the byte count.
What generates it. Logs are charged principally on ingestion, so the cost is driven by how much your applications emit, not by how much anyone reads. It is the line that most reliably grows without a decision: debug logging left on after an incident, a library upgraded to a chattier default, VPC Flow Logs enabled at full fidelity to investigate something in March and never turned down, one health check logging one line per second per task per environment.
The usual fix. Emit less: production log levels, sampling on high-volume paths, structured logs instead of stack-trace walls, dropping health-check noise at source, an infrequent-access log class for what you must retain but rarely query, and shipping long-tail retention to object storage instead of keeping it hot.
What the fix costs you, and for a regulated business this is the one to be careful with. Log ingestion is the meter on the evidence you will have after something happens. You cannot sample retroactively. The cheapest reductions — turn off Flow Logs, drop request logs, shorten retention — are the ones that remove precisely the record an incident review, a fraud investigation, a security audit or a regulator asks for. If you operate under an obligation to reconstruct what happened, the retention question belongs to whoever owns that obligation, not to the person who sorted the bill descending. Cut the noise that nobody would ever query. Do not cut the record you would be asked to produce.
Sourcing for section 3. The three charging behaviours quoted above are AWS’s, read at source on 2026-08-21: the in-each-direction cross-zone rule, the same-Availability-Zone exemption and the public/Elastic IPv4 exception from the EC2 on-demand pricing page (“Data Transfer within the same AWS Region”); the Application vs Network Load Balancer split from the Elastic Load Balancing FAQ. We paraphrase what those pages say is charged and deliberately reproduce none of the rates — see section 6. Everything else in section 3, in particular every judgement about what a fix costs you, is our own operational opinion.
Read sections 3.2 and 3.4 together and a pattern falls out that we think is the single most useful thing on this page.
Of the four lines, the two that are easiest to shrink dramatically — collapse the zones, stop logging — are the two whose meters are attached to things you only ever need on your worst day. They are cheap to cut because nothing breaks when you cut them. Nothing breaks for a long time. The bill improves immediately, the graph is presented in a meeting, and the cost of the decision is paid, in full and at once, on a date nobody in that meeting chose.
This is not an argument against cutting them. It is an argument that these two cuts are risk decisions wearing a finance costume, and should be taken by the person who would have to answer for the outcome — in writing, with the trade named. In our experience the conversation is much easier than teams expect once the trade is stated honestly. What people object to is discovering afterwards that it was made.
Here is the practical obstacle, and it is why cost reviews stall. The bill attributes these charges to the resource that carried the traffic, not to the workload that caused it. NAT Gateway processing is billed to the NAT Gateway. Cross-AZ transfer is billed against the account, not the call graph. A shared bucket’s requests are billed to the bucket. So the four biggest lines on the bill are frequently the four you cannot assign to a team, and the review dies there.
Attribution is its own small project, and it is the one worth doing first:
And note the honest circularity, because nobody mentions it: Flow Logs are themselves billed on log ingestion. Turning them on to find out why your data-transfer line is high raises your logging line. Scope them — a subnet, a window, a sampling — rather than enabling them estate-wide and forgetting. Measuring this costs money, which is a real reason teams do not measure it, and it is a bad reason to guess instead.
We have deliberately quoted no prices. Every one of these charges is region-scoped and tier-scoped, several are charged differently on each side of a transfer, and AWS revises them. A price copied into an article is correct on the day it is written and wrong on some later day that neither the writer nor the reader can predict — and nothing about the article tells you which day you are on. An article that hands you a stale number you then act on is worse than one that hands you none.
Read them at the source, for your Region, on the day you are doing the analysis:
And if the number is going into a model, a spreadsheet or a chargeback calculation, do not read it off a marketing page at all. Those pages are JavaScript-rendered and switch region from a dropdown, which is exactly how a US price ends up in a European estimate. AWS publishes the same data as plain JSON, per service and per Region, at a predictable URL:
https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/<OfferCode>/current/<region>/index.json
Substitute the offer code for the service (AmazonVPC, AmazonEC2,
AmazonS3, AmazonCloudWatch) and your Region id
(eu-west-1, eu-central-1). Two practical notes that save an hour:
publicationDate. That is the field that
tells you how old the number you are about to use is — there is no equivalent on the rendered
pricing page. Record it beside any figure you quote onward.AmazonEC2, not AmazonVPC, so a search of the VPC file for “NAT”
returns nothing and looks like an answer. It is not an answer; it is the wrong file. If a price
lookup returns empty, confirm you are in the right offer before concluding the charge does not exist.If you do nothing else, do this. It takes an afternoon and it changes the conversation from “the bill is too high” to a list of decisions with owners.
It does not claim your estate has any of these problems — we have not seen it. It quotes no AWS prices, no savings figure and no percentage, and you should not infer one: whether any change here saves you anything depends entirely on your volumes and your Region. It names AWS services as things we work with, not as things we sell or integrate with. It is not legal, audit, tax or compliance advice, and it is not a statement about what any particular regulation or certification requires of you — confirm that with your auditor. The mechanics described in section 3 are how these services are documented by AWS to charge; everything else on this page, including every recommendation and every judgement about what a fix costs you, is our own operational opinion, offered as opinion.
If you want this pass run against your estate by someone with no stake in the answer, that is the work we do: what we do and what it costs. There is a form on the front page and a person reads it.