VPC networking decisions feel like hygiene when you make them: put everything in private subnets, route outbound traffic through a NAT Gateway, move on. They turn into budget surprises because AWS charges for data movement, not just for compute and storage. A pipeline can be efficient in every technical respect and still produce a networking line item that dwarfs the compute behind it.
This article walks through where those charges accumulate and which changes reduce them without giving up isolation. The prices below are typical figures for the us-east-1 region; they differ by region and change over time, so always confirm on the current AWS pricing pages.
Where NAT Gateway costs come from
A NAT Gateway has two charges: an hourly fee per gateway (roughly $0.045 per hour, about $32 per month) and a data processing fee (roughly $0.045 per GB) for everything that passes through it. The hourly fee is small. The per-GB charge dominates when a workload moves a lot of data to destinations outside the VPC, because the default route makes every such byte go through the gateway, and nothing in the architecture diagram shows that.
Typical sources of NAT traffic in a data pipeline:
- Workers reading from or writing to S3 when no S3 gateway endpoint exists.
- Polling external SaaS or partner APIs with large paginated responses.
- Calls to AWS services that you have not given a VPC endpoint.
- Shipping logs and metrics to a third-party observability service.
A common misconception: a Lambda function does not download its dependencies on each cold start. Packages are bundled with the code or the container image at build time, so dependency downloads are not a source of NAT traffic at runtime unless your own code fetches things.
An illustrative monthly ledger (invented numbers)
The figures below are hypothetical, chosen to show how the pieces add up.
| Traffic | Volume | Via NAT (about $0.045/GB) | Better path |
|---|---|---|---|
| Workers reading S3 | 2,400 GB | $108 | S3 gateway endpoint: no hourly or per-GB charge |
| Polling external APIs | 1,200 GB | $54 | Run the caller outside the VPC if it needs no private resources |
| Logs to a third party | 300 GB | $13.50 plus about $27 internet transfer | Filter volume; forward from outside the VPC |
None of these is dramatic alone. Together, and multiplied by every environment, they are how a networking bill becomes a line item people notice.
VPC endpoints
VPC endpoints let resources in private subnets reach AWS services without a NAT Gateway or internet gateway. There are two kinds:
- Gateway endpoints exist for S3 and DynamoDB and have no hourly or data processing charge. There is almost never a reason not to add them.
- Interface endpoints cover most other services (Secrets Manager, SQS, ECR and more). They are billed per endpoint per availability zone per hour (about $0.01) plus a per-GB processing fee (about $0.01). For low-volume services they are cheap, and often cheaper than pushing the same traffic through NAT.
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = [aws_route_table.private.id]
}
For each interface endpoint, compare its fixed monthly cost across the availability zones you use with the NAT processing cost of the traffic it would replace.
When a VPC is the wrong default
Private subnets are valuable for databases and other resources that must not be reachable from the internet. For stateless functions that only call public HTTPS endpoints and AWS services, the picture is different. A Lambda function that needs no private resources does not have to be attached to a VPC at all: it runs in an AWS-managed network with outbound internet access, and its traffic never touches your NAT Gateway. Its security rests on IAM, TLS and bucket policies, not on where a subnet sits.
Two cautions. First, putting a Lambda function in a public subnet does not give it internet access, because its network interface has no public IP; VPC-attached Lambda functions need a NAT Gateway (or endpoints) to reach beyond the VPC. The "public subnet instead of NAT" trick works for resources that can have public IPs, such as EC2 instances or ECS tasks with public addressing. Second, regulated environments sometimes require egress control and inspection, in which case private subnets with a NAT or firewall are the right design and the cost is a requirement, not a mistake.
Also note that an IPv6 egress-only internet gateway can provide outbound access to IPv6-capable destinations without NAT processing charges, which is worth knowing for chatty API clients.
Data transfer pitfalls beyond NAT
- Cross-region: transfer between regions is charged per GB (about $0.02) on the sending side. Loading a large dataset from an S3 bucket in one region into a warehouse in another multiplies quickly. Terraform modules inherit the provider's region, so a misconfigured provider or an inherited module can put a bucket in an unexpected region.
- Cross-availability-zone: traffic between AZs inside a region is also charged (about $0.01 per GB in each direction). Chatty workers in a different AZ from the data store pay for it every time.
- Out to the internet: standard transfer out is billed per GB after the free allowance (about $0.09 per GB for the first tier).
Make regions and AZ placement explicit, and add a check that fails the deployment when they do not align:
# Sketch of an infrastructure test (helper functions assumed)
def test_region_alignment():
s3_region = get_bucket_region("source-data-bucket")
warehouse_region = get_cluster_region("analytics-cluster")
assert s3_region == warehouse_region, \
f"Region mismatch: bucket in {s3_region}, cluster in {warehouse_region}"
The logging trap
Forwarding logs to a third party from inside a VPC pays NAT processing (about $0.045 per GB) and then internet transfer out (about $0.09 per GB), on top of the vendor's own ingestion fee. Reduce it from both ends: filter or sample debug-level entries and aggregate repetitive messages before they leave the application, and consider running the forwarder outside the VPC if it needs no private access.
A decision framework
- Start by asking whether a function or container needs to be in the VPC at all. It does if it reaches a private database, cache or on-premises network.
- When you do need a VPC, add the S3 and DynamoDB gateway endpoints immediately.
- List every outbound destination and estimate monthly volume before adding a NAT Gateway. If the total is large, look for endpoints, relocation outside the VPC, or less verbose logging first.
- Decide on one NAT Gateway per AZ based on availability requirements; one gateway is cheaper but turns AZ failures and cross-AZ traffic into trade-offs.
- Track networking as its own cost line: use Cost Explorer filtered by NAT Gateway usage types, NAT Gateway CloudWatch metrics for bytes processed, and VPC Flow Logs to find the top talkers. Set budget alerts.
VPCs are not inherently expensive. The default configurations hide the per-GB charges until they are large. Making networking choices per workload, and checking their cost before the architecture becomes load-bearing, keeps isolation where it matters and removes it where it only adds a bill.
Comments
Post a Comment