plainstackBook a free call
← All guides

Networking11 min read

AWS data transfer costs explained

Nobody moves too much data by accident. They move a normal amount along a path that happens to be metered.

By Plainstack/

Data transfer is the only significant AWS charge with no console, no dashboard, and no service page. There is no "Data Transfer" in the services menu you can open to see what you're spending. It is billed as a side effect of things you did elsewhere, which is why it's simultaneously one of the largest lines on a mature bill and the one nobody can explain.

The good news is that the rules are simple, there aren't many of them, and once you know them the expensive shapes are obvious on sight.

Why you can't find it

Two accounting decisions make transfer nearly invisible.

It's billed under the service that caused it, not as its own line. Bytes leaving an EC2 instance are on the EC2 line. Bytes leaving S3 are on the S3 line. Bytes leaving through a load balancer are on the ELB line. So no single number in Cost Explorer ever represents "what we spent moving data" unless you deliberately assemble it.

The names are usage types, not English. The charge for traffic between availability zones is called DataTransfer-Regional-Bytes. Nothing in that string says "your application is talking to a database in another AZ, and you're paying twice for every gigabyte of it."

The practical consequence: transfer costs don't get investigated when they start — they get investigated years later, when someone finally asks what EC2 — Other is.

The price of a gigabyte depends entirely on the path

Same bytes, same application, same region. The only variable is which boundary they cross.

ONE GIGABYTE, PRICED BY THE BOUNDARY IT CROSSESSame AZ, private IPFREES3 via gateway endpointFREEAcross an AZ (both ways)$0.020Between regions$0.020NAT Gateway processing$0.045Out to the internet$0.090Through NAT, to the internet$0.135Same bytes, same application. The only variable is the path — and the path is a design choice.
Nothing here is about volume or efficiency. Every one of these prices is a consequence of a routing decision.

That bottom bar is the one worth staring at. Traffic that leaves a private subnet for the internet through a NAT Gateway pays the NAT processing charge and the egress charge. They stack. It is the most expensive ordinary path in AWS, and it is also the default path, because a private subnet with a NAT Gateway is what every reference architecture draws.

The rules, in full

PathChargeNotes
In from the internetFreeUploads cost nothing.
Out to the internet~$0.09/GBFirst 100 GB/month free across the account. Tiers down above 10 TB.
Between AZs, same region~$0.01/GB each waySo $0.02 for a gigabyte that crosses and comes back.
Within one AZ, private IPFreeThe private IP part is not optional — see below.
Within one AZ, public or Elastic IP~$0.01/GB each waySame rack, billed as if it left.
Between regions~$0.02/GBVaries by pair; higher out of some regions.
NAT Gateway~$0.045/GB processedPlus ~$0.045/hour per gateway, per AZ, idle or not.
S3 / DynamoDB gateway endpointFreeNo hourly, no per-GB.
Interface endpoint (PrivateLink)~$0.01/hour/AZ + ~$0.01/GBCheaper than NAT for the same traffic, not free.
CloudFront to viewers~$0.085/GBAnd AWS origin → CloudFront is free, which is the whole trick.
Transit Gateway~$0.02/GB + attachment hoursCharged per attachment, so hub-and-spoke adds up.

Prices are us-east-1 list at time of writing and vary by region — check current pricing before you build a business case on a number here. The ratios are what matter, and they've been stable for years.

What your paths actually cost

Put in roughly what your account moves in a month. If you don't know, that's the finding — the last section shows you how to get real numbers in about ten minutes.

Plainstack · Data transfer modelInteractive
2,000 GB
3,000 GB
5,000 GB
3
Internet egress$171 / mo
NAT processing$135 / mo

Charged on top of egress — traffic to the internet through NAT pays both.

NAT hourly$99 / mo

3 gateways × 730 hours, whether or not a byte moves.

Cross-AZ$100 / mo

Billed in both directions, and invisible in Cost Explorer until you split by usage type.

Moving bytes, per year$6,055

The two red lines — $2,820 a year here — are the ones that are usually architecture rather than necessity. Endpoints and AZ-aware routing remove most of them without changing what your application does.

us-east-1 list prices at time of writing. Egress rates fall in tiers above 10 TB and vary by region; NAT hourly is per gateway, per AZ. Check current rates for your region.

The NAT hourly slider is the one that catches people out. Three gateways across three AZs is standard high-availability practice, and it's about $98 a month before a single byte moves. That's fine and correct for production. It is not fine for the four dormant dev VPCs that also have three each.

The five shapes that cost the most

1. Same-region S3 traffic going through NAT. An application in a private subnet reading from S3 has its traffic routed out through the NAT Gateway, processed at ~$0.045/GB, for data that never leaves the region. A VPC gateway endpoint for S3 makes that path free — no hourly charge, no per-GB charge. It's a route table entry, it takes minutes, and it is the single highest-return change in this entire guide. The same applies to DynamoDB. The NAT Gateway guide covers this one in depth, because it's usually the largest.

2. Chatty services in different AZs. An application tier in us-east-1a talking to a database in us-east-1b pays ~$0.01/GB out and ~$0.01/GB back for every query and every result set. For a busy service this is continuous, and the architecture diagram that produced it looks completely reasonable — AZ spread is what you're told to do. It is right for the database's replica placement; it is not automatically right for every hop in between.

3. Private traffic taking the public path. If a service reaches another service inside your VPC by its public DNS name or Elastic IP, the traffic exits and re-enters, and is billed accordingly — even inside one AZ. This is almost always accidental: a config value copied from a laptop, a hostname that was public in staging. It's free to fix and invisible until you look.

4. Serving bytes to the internet directly. Static assets served from S3 or straight through an ALB pay full egress. Put CloudFront in front and the origin fetch becomes free, with the viewer-facing egress priced lower than direct S3. For any real volume of static content this is a straightforward reduction.

5. Cross-region replication nobody remembers. S3 Cross-Region Replication, cross-region snapshot copies, or a global database enabled for a compliance requirement that has since changed. These bill quietly and forever, and the decision that created them is usually two org charts old.

Decide

You've found a large transfer charge. What do you do first?

It's NAT processingAdd gateway endpoints for S3 and DynamoDB. Free, fast, no application change. Do this before anything else — it needs no analysis to justify.
It's cross-AZFind the chatty pair with VPC Flow Logs, then decide whether that specific hop needs to cross an AZ. Often it's a read replica placement or a cache that could be local.
It's internet egress on static filesPut CloudFront in front. Origin fetches from AWS become free and viewer egress is cheaper than serving directly.
It's inter-regionEstablish what it's for before touching it. Replication and DR are legitimate — but confirm the requirement still exists rather than assuming it does.
You can't tell whichDon't optimise yet. Group Cost Explorer by usage type first — guessing here wastes days on the wrong path.

Finding yours, in about ten minutes

Step one — Cost Explorer, grouped by usage type. Not by service. Service tells you which team's budget it lands in; usage type tells you what physically happened. Filter to the last three months and look for these strings:

  • NatGateway-Bytes — traffic processed by NAT. The usual winner.
  • NatGateway-Hours — gateways existing. Divide by 730 to count them.
  • DataTransfer-Regional-Bytes — cross-AZ, in region. The invisible one.
  • DataTransfer-Out-Bytes — egress to the internet.
  • <REGION>-<REGION>-AWS-Out-Bytes — inter-region, one line per pair.

Those five strings account for the overwhelming majority of transfer spend in a typical account.

Step two — VPC Flow Logs, for the cross-AZ number only. Cost Explorer tells you how much crossed an AZ boundary; it cannot tell you which two things were talking. Flow logs can. Enable them to S3 for a week, query with Athena, group by source and destination, and the chatty pair is usually obvious in the first result row. Turn them off afterwards — flow logs at full volume are themselves a real CloudWatch cost, which is a mildly embarrassing way to increase a bill you were trying to reduce.

Step three — check your endpoints. In the VPC console, list endpoints per VPC. If a VPC has workloads using S3 or DynamoDB and no gateway endpoint, you've found paid traffic that should be free, and you don't need any further evidence than that.

0 / 8

The thing worth remembering

Almost every expensive transfer charge is a routing problem, not a volume problem. Nobody moves too much data by accident. They move a normal amount of data along a path that happens to be metered, chosen at design time by someone optimising for availability or for getting it working, both of which are correct priorities that simply don't include this.

That's also why it's worth fixing properly rather than repeatedly: an endpoint added once keeps being free, and a service moved next to the thing it talks to stays next to it.

If you'd rather see the split before spending an afternoon in Cost Explorer, the free teardown reads an export in your browser and breaks out EC2 — Other — the bucket where NAT and cross-AZ transfer both hide. No upload, no signup, nothing leaves the page.

Check your own account

Does this apply to you? Find out in two seconds.

Drop a Cost Explorer export into the free teardown. It reads your service mix and tells you which of these patterns show up in your bill — in your browser, with no upload and no email.