TL;DR: Stop paying exorbitant NAT Gateway data processing fees for your Node.js microservices by routing internal AWS traffic through VPC Endpoints. This guide demonstrates how to use Amazon Athena to analyze VPC Flow Logs for cost leaks, deploy free Gateway Endpoints for S3 via Terraform, and utilize Interface Endpoints to cut data transfer costs by 77% for services like CloudWatch and Secrets Manager.
⚡ Key Takeaways
- Query VPC Flow Logs using Amazon Athena to identify exactly which external IP addresses and AWS services are saturating your NAT Gateway data transfer.
- Deploy completely free Gateway Endpoints at the route table level to eliminate data processing fees for high-volume Amazon S3 and DynamoDB traffic.
- Use the provided Terraform configuration to automate the provisioning and private route table association of your S3 Gateway Endpoints.
- Keep Node.js S3 requests within the same AWS region; cross-region requests bypass Gateway Endpoints and will still incur NAT Gateway charges.
- Provision Interface Endpoints for backend dependencies like CloudWatch, KMS, and Secrets Manager to drop data processing costs from $0.045/GB to $0.01/GB.
You architect your infrastructure by the book: public subnets for Application Load Balancers, and private subnets for your Node.js microservices and PostgreSQL databases. By default, the instances in your private subnets lack public IP addresses. To allow them to communicate with the outside world—including AWS services like S3, DynamoDB, Secrets Manager, and CloudWatch—you provision a NAT Gateway.
Then, the end-of-month invoice arrives. Your EC2 and Fargate compute costs are completely eclipsed by a silent budget killer: EC2: NAT Gateway - Data Processed.
AWS charges $0.045 per GB for data processed through a NAT Gateway, plus an hourly uptime fee. If you have a high-throughput Node.js worker pulling 50TB of image assets from S3 monthly, or pushing gigabytes of verbose JSON logs to CloudWatch daily, you are paying over $2,250 a month just to move data between AWS services. You are paying a premium to route traffic out to the public internet, only to route it immediately back into the AWS network.
The solution is to bypass the NAT Gateway entirely using AWS PrivateLink and VPC Endpoints. By strategically implementing these endpoints, you keep traffic destined for AWS services entirely on the Amazon private network backbone.
Pinpointing NAT Gateway Bleed with VPC Flow Logs and Athena
Before tearing down infrastructure, you need empirical proof of which services are saturating your NAT Gateway. AWS Cost Explorer shows the aggregate bill, but it won't tell you what your Node.js apps are communicating with.
To trace the data, you must query your VPC Flow Logs. If you aren't already piping flow logs to an S3 bucket, configure that immediately. Once the logs are in S3, use Amazon Athena to parse them.
The following Athena SQL query aggregates outbound traffic from your private subnet CIDR blocks to external IP addresses, sorting by the highest data transfer volume.
SELECT
destinationaddress,
SUM(numbytes) / 1073741824.0 AS gigabytes_transferred,
COUNT(*) AS connection_count
FROM vpc_flow_logs
WHERE
sourceaddress LIKE '10.0.%' -- Replace with your private subnet CIDR
AND action = 'ACCEPT'
AND destinationaddress NOT LIKE '10.0.%' -- Exclude internal VPC traffic
GROUP BY destinationaddress
ORDER BY gigabytes_transferred DESC
LIMIT 20;
When you run this, you will likely see high-volume traffic destined for AWS public IP ranges. You can cross-reference these IPs using the curl https://ip-ranges.amazonaws.com/ip-ranges.json dataset. If the top talkers are S3, DynamoDB, or ECR, you have a massive cost-optimization opportunity.
Implementing Gateway Endpoints for High-Volume Data (S3 & DynamoDB)
AWS provides two types of VPC Endpoints. Gateway Endpoints are available exclusively for Amazon S3 and DynamoDB, and they are 100% free. There is no hourly charge, and there is no data processing fee.
Instead of deploying elastic network interfaces (ENIs), Gateway Endpoints operate at the route table level. When you associate a Gateway Endpoint with your private subnet's route table, AWS injects a prefix list (e.g., pl-63a5400a) that intercepts traffic destined for S3 and routes it directly over the internal AWS network.
Here is a production-grade Terraform configuration to deploy an S3 Gateway Endpoint and attach it to your private route tables:
resource "aws_vpc_endpoint" "s3_gateway" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.aws_region}.s3"
vpc_endpoint_type = "Gateway"
# Apply a strict policy - don't leave this as FullAccess in production
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "*",
Effect = "Allow",
Resource = "*",
Principal = "*"
}
]
})
tags = {
Name = "prod-s3-gateway-endpoint"
}
}
resource "aws_vpc_endpoint_route_table_association" "private_s3" {
count = length(var.private_subnet_route_table_ids)
route_table_id = var.private_subnet_route_table_ids[count.index]
vpc_endpoint_id = aws_vpc_endpoint.s3_gateway.id
}
Production Note: Gateway Endpoints are region-specific. If your VPC is in
us-east-1, the S3 Gateway Endpoint will only intercept traffic destined for S3 buckets inus-east-1. If your Node.js application is fetching objects from an S3 bucket ineu-west-1, that traffic will bypass the Gateway Endpoint, fall back to the default route (0.0.0.0/0), and hit your NAT Gateway anyway.
Deploying Interface Endpoints for Backend Dependencies
For AWS services other than S3 and DynamoDB (such as Secrets Manager, SQS, SNS, KMS, and CloudWatch), you must use Interface Endpoints.
Unlike Gateway Endpoints, Interface Endpoints deploy actual ENIs into your subnets, consuming private IP addresses. They cost roughly $0.01 per GB processed plus an hourly fee (~$7.20/month per AZ). While not free, $0.01/GB is a massive 77% reduction compared to the NAT Gateway's $0.045/GB.
When optimizing DevOps and cloud deployment services for high-throughput clients, we frequently deploy Interface Endpoints for ECR (Elastic Container Registry) and CloudWatch Logs. Fargate tasks pulling Docker images and pushing application logs on every deployment cycle will severely bloat your NAT bill.
Here is how you deploy an Interface Endpoint for CloudWatch Logs (logs) using Terraform. Notice the security group configuration—because this is an ENI residing in your subnet, you must explicitly allow inbound HTTPS traffic from your microservices.
resource "aws_security_group" "vpc_endpoint_sg" {
name = "vpc-endpoint-sg"
description = "Allow TLS inbound for VPC Interface Endpoints"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTPS from Private Subnets"
from_port = 443
to_port = 443
protocol = "tcp"
# In production, restrict this to your specific private subnet CIDRs or workload Security Groups
cidr_blocks = [aws_vpc.main.cidr_block]
}
}
resource "aws_vpc_endpoint" "cloudwatch_logs" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.aws_region}.logs"
vpc_endpoint_type = "Interface"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.vpc_endpoint_sg.id]
# CRITICAL: This intercepts standard AWS SDK calls
private_dns_enabled = true
tags = {
Name = "prod-cloudwatch-logs-endpoint"
}
}
The private_dns_enabled = true flag is critical. It creates a private hosted zone in Amazon Route 53 that overrides the default AWS public DNS name for the service. When your Node.js app asks for logs.us-east-1.amazonaws.com, Route 53 resolves it to the local 10.0.x.x private IP of your Interface Endpoint, seamlessly keeping the traffic inside the VPC.
Configuring the Node.js AWS SDK v3 for High-Performance Endpoint Routing
If you set private_dns_enabled = true on your Interface Endpoints and configure your route tables correctly for Gateway Endpoints, your Node.js code usually requires zero modifications. The AWS SDK natively uses the standard service hostnames, which are automatically intercepted by DNS or the route table.
However, in advanced multi-account architectures or shared VPC setups, you might not be able to enable Private DNS due to overlapping namespace conflicts. In these cases, you must explicitly tell the AWS SDK v3 to route requests to the VPC Endpoint's specific DNS name.
Furthermore, while recent Node.js versions have improved default connection management, high-throughput applications routing thousands of SQS messages or S3 streams through an endpoint can still exhaust ephemeral ports and spike CPU on TLS handshakes. You should configure a custom NodeHttpHandler with optimized pooling.
Here is a production-grade AWS SDK v3 configuration for an SQS client pointing to an explicit VPC Interface Endpoint while optimizing TCP connection reuse:
import { SQSClient } from "@aws-sdk/client-sqs";
import { NodeHttpHandler } from "@smithy/node-http-handler";
import https from "https";
// Create a persistent HTTPS agent to prevent TLS handshake overhead
// on every request routed to the VPC Endpoint
const customAgent = new https.Agent({
keepAlive: true,
maxSockets: 50,
scheduling: "lifo",
timeout: 60000,
});
const sqsClient = new SQSClient({
region: "us-east-1",
// Override the endpoint if private_dns_enabled is false
endpoint: "https://vpce-0123456789abcdef0-12345678.sqs.us-east-1.vpce.amazonaws.com",
requestHandler: new NodeHttpHandler({
httpsAgent: customAgent,
connectionTimeout: 5000,
}),
maxAttempts: 3,
});
export const sendMessage = async (queueUrl: string, body: string) => {
// Traffic routes over the VPC Endpoint ENI, completely bypassing the NAT Gateway
// ...
};
Tip: If you are hardcoding the
endpointlike above, verify your security groups. If the Node.js process times out connecting to the VPC endpoint, it is almost always a security group issue on the endpoint's ENI blocking inbound port 443 from the application's security group.
Locking Down VPC Endpoint Policies
When you bypass the public internet, you are implicitly relying on your VPC boundaries for security. But what happens if a compromised Node.js worker tries to exfiltrate data by writing it to an S3 bucket owned by an attacker's AWS account?
Because the Gateway Endpoint routes all S3 traffic in that region, the worker can successfully reach the attacker's bucket.
To prevent data exfiltration, you must attach a strict VPC Endpoint Policy. By default, Terraform and the AWS Console assign a FullAccess policy. You must replace this with a policy that restricts access strictly to AWS accounts you own, or specific IAM roles within your organization.
Here is a restricted JSON policy block for an S3 Gateway Endpoint. It ensures that any traffic flowing through the endpoint can only interact with S3 buckets owned by your specific AWS Account ID:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RestrictToSpecificAccount",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:ResourceAccount": "111122223333"
}
}
},
{
"Sid": "AllowPublicReadForDependencies",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::nodesource-*/*"
}
]
}
Notice the second block: AllowPublicReadForDependencies. Node.js build processes (e.g., npm install) often pull underlying binaries (like node-sass or bcrypt bindings) from public S3 buckets. If you lock down the endpoint policy too aggressively, your CI/CD pipelines or Fargate task startups might fail because they suddenly lose access to these public asset buckets.
Architectural Trade-offs and the Break-Even Point
You shouldn't blindly provision an Interface Endpoint for every AWS service. Because each Interface Endpoint carries an hourly baseline cost (~$7.20 per month per AZ), provisioning 10 endpoints across 3 Availability Zones will cost you over $200/month just in baseline fees, before a single byte is transferred.
The Math:
- NAT Gateway Data Transfer: $0.045 / GB
- Interface Endpoint Data Transfer: $0.01 / GB
- Delta: $0.035 / GB saved.
To justify the ~$7.20/month hourly cost of a single Interface Endpoint in a single AZ, you need to push at least 205 GB of data per month to that specific AWS service ( $7.20 / $0.035 = 205.7 ).
If your service pulls 10MB of Secrets Manager data a month, let it ride the NAT Gateway. If your service pulls 2TB of ECR images a month, absolutely provision the ECR Interface Endpoints (com.amazonaws.region.ecr.api and com.amazonaws.region.ecr.dkr). Gateway Endpoints (S3/DynamoDB) carry zero hourly cost and should always be provisioned in every VPC, immediately.
Need help building this in production?
SoftwareCrafting is a full-stack dev agency — we ship fast, scalable React, Next.js, Node.js, React Native & Flutter apps for global clients.
Get a Free ConsultationFrequently Asked Questions
How can I find out which AWS services are driving up my NAT Gateway costs?
You can identify high-volume traffic sources by querying your VPC Flow Logs using Amazon Athena. By filtering outbound traffic from your private subnets and grouping by destination IP, you can cross-reference the top IP addresses with AWS public IP ranges to pinpoint the exact services causing the high costs.
What is the difference between Gateway Endpoints and Interface Endpoints in AWS?
Gateway Endpoints operate at the route table level, are completely free, and are exclusively available for Amazon S3 and DynamoDB. Interface Endpoints deploy Elastic Network Interfaces (ENIs) into your subnets for services like CloudWatch or Secrets Manager, charging an hourly rate and a reduced data processing fee of roughly $0.01 per GB.
Will an S3 Gateway Endpoint cover traffic to S3 buckets in other AWS regions?
No, Gateway Endpoints are strictly region-specific. If your VPC is in us-east-1 but your Node.js application fetches objects from a bucket in eu-west-1, that cross-region traffic will bypass the Gateway Endpoint and still incur NAT Gateway data processing fees.
Are Interface Endpoints actually cheaper than using a NAT Gateway?
Yes, Interface Endpoints provide a massive cost reduction for high-volume traffic. While NAT Gateways charge $0.045 per GB of data processed, Interface Endpoints only charge about $0.01 per GB, resulting in a 77% savings on data transfer costs for services like SQS, SNS, and CloudWatch.
How can SoftwareCrafting help my team implement VPC Endpoints and reduce AWS bills?
SoftwareCrafting provides expert DevOps consulting to audit your AWS infrastructure, identify hidden data transfer costs, and safely implement VPC Endpoints. We handle the Terraform automation and route table configurations so your Node.js microservices remain secure while drastically lowering your monthly AWS bill.
I'm not sure if my Node.js architecture needs a NAT Gateway at all. Can SoftwareCrafting review my setup?
Absolutely. The cloud architects at SoftwareCrafting can perform a comprehensive review of your VPC, subnets, and Node.js microservice dependencies. We can help you determine if you can eliminate your NAT Gateways entirely or if a hybrid approach using AWS PrivateLink is the most cost-effective solution for your specific workload.
📎 Full Code on GitHub Gist: The complete
vpc-flow-logs-query.sqlfrom this post is available as a standalone GitHub Gist — copy, fork, or embed it directly.
