AWS Builder Center
Identify and Optimize NAT Gateway Traffic Using VPC Flow Logs and DNS Query Logs

Identify and Optimize NAT Gateway Traffic Using VPC Flow Logs and DNS Query Logs

As NAT Gateway traffic grows, a portion of it is destined for AWS services or SaaS providers that could be reached more cost-effectively — and privately — through VPC Endpoints or PrivateLink, but flow logs alone only show raw destination IPs, making it hard to know what's worth moving. This guide shows a serverless approach: enable VPC Flow Logs and Route 53 DNS query logs for a few days, then use Amazon Athena to produce a ranked list of your NAT destinations resolved to real domain names.

Networking Specialist Solutions Architect at AWS
NAT Gateways provide a managed, highly available path for private subnet workloads to reach the internet and AWS service endpoints. As environments grow, so does the traffic flowing through them, and with it the opportunity to route more efficiently. Traffic destined for AWS services like S3, CloudWatch, or ECR (and even third-party SaaS providers) can often be routed more efficiently through VPC Endpoints or private connectivity options, improving both cost and security posture.
The practical challenge is visibility: VPC Flow Logs include a pkt_dst_aws_service field, but in practice it only distinguishes a few services (S3, DynamoDB, EC2). The majority of AWS service traffic is labeled generically as AMAZON, which doesn't help you decide between a CloudWatch endpoint, an STS endpoint, or a KMS endpoint. Internet-bound traffic shows only a raw destination IP. Traditionally, teams perform manual reverse DNS lookups to identify those IPs, a tedious process that doesn't scale. By correlating flow logs with Route 53 DNS query logs, you get specific domain-level attribution automatically.
This article walks through a serverless approach to solve that problem. You'll enable VPC Flow Logs and Route 53 DNS Query Logs for a few days, then use Amazon Athena to produce a ranked list of NAT Gateway destinations, resolved to domain names and classified by AWS service. The setup takes about 30 minutes of hands-on time spread across a week.
Before investing the collection time, confirm there's a prize worth chasing: check your NAT Gateway's BytesOutToDestination metric in CloudWatch, or the NatGateway-Bytes usage type in Cost Explorer. If processing volume is already low, the optimization upside is limited and you can skip the rest.
What you get at the end: A table showing where your NAT Gateway traffic is going, ordered by volume, with enough detail to make routing decisions. Treat the results as directional: they rank destinations by approximate volume to guide where to act, not as an exact, flow-by-flow accounting.

The Approach

Two data sources, joined in Athena:
  1. VPC Flow Logs in Parquet format, where every flow record includes byte counts and, critically, the pkt_dst_aws_service field that tags traffic destined for AWS services
  2. Route 53 Resolver DNS Query Logs, capturing every DNS lookup made from the VPC, which lets us resolve raw destination IPs back to domain names like monitoring.us-east-1.amazonaws.com or api.datadoghq.com
The detection technique relies on a simple property of NAT translation: when a flow log record shows dstaddr ≠ pkt_dstaddr, that flow traversed a NAT Gateway. No need to hardcode any NAT Gateway IPs; the mismatch is the signature.
Architecture
A minimum of 3 days captures steady-state traffic patterns. Extend to 7 days if your environment includes weekly batch jobs, scheduled tasks, or CI/CD pipelines that run on specific days.

Prerequisites

  • An existing NAT Gateway carrying traffic you want to analyze. Private subnets in the VPC should already send their internet-bound traffic (0.0.0.0/0) to the NAT Gateway. The workloads generating that traffic are what you're profiling.
  • Amazon-provided DNS in the VPC. The analysis relies on Route 53 Resolver query logs, which only capture lookups that go through the VPC's default resolver. This is on by default; it won't work if your VPC's DHCP option set points domain-name-servers at a custom or non-AWS resolver.
  • IAM permissions to create S3 buckets and bucket policies, VPC Flow Logs, Route 53 Resolver query log configurations, and Athena tables.
Expected spend. Plan for a few dollars total across the 3–7 day collection window: CloudWatch Vended Logs delivery charges for both flow logs and DNS logs, S3 storage, and Athena query cost (~$0.05–$0.10 per analysis run). See CloudWatch Vended Logs pricing  and Athena pricing  for current rates.

Step 1: Create an S3 Bucket

Create a bucket in the same region as your VPC to receive both log types. A naming convention like nat-traffic-analysis-<ACCOUNT_ID>-<REGION> keeps things identifiable. Enable "Block all public access" and SSE-S3 encryption. See Creating a bucket  if you need a walkthrough.
You'll create the two log sources next, then apply a single bucket policy that authorizes both to write (Step 4). Applying the policy after the log sources exist avoids a sequencing pitfall: creating a flow log in the Console replaces any existing bucket policy, which would otherwise wipe out the Route 53 statement.

Step 2: Enable VPC Flow Logs

Create a flow log on the target VPC with the following settings. The field order matters: it must match the Athena DDL in Step 6 exactly.
SettingValue
FilterAll
Aggregation interval10 minutes
DestinationS3 bucket with /flow-logs/ prefix
Log record formatCustom, v2 through v5 fields in the order below
File formatParquet
Hive-compatible S3 prefixEnabled
Partition by timeEvery 1 hour
Field order (29 fields, versions 2–5):
1
version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status vpc-id subnet-id instance-id tcp-flags type pkt-srcaddr pkt-dstaddr region az-id sublocation-type sublocation-id pkt-src-aws-service pkt-dst-aws-service flow-direction traffic-path
Important: The flow log must capture the NAT Gateway's elastic network interface. The simplest way is to scope the flow log at the VPC level (as below), which captures all ENIs including the NAT GW in the public subnet. If you scope the flow log only to a private subnet or a specific instance ENI, the dstaddr ≠ pkt_dstaddr detection will return zero rows, because the NAT translation is only visible from the NAT GW ENI's perspective.
For Console setup, see Publish flow logs to Amazon S3 . For CLI:
1
2
3
4
5
6
7
8
9
10
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids "${VPC_ID}" \
--traffic-type ALL \
--log-destination-type s3 \
--log-destination "arn:aws:s3:::${BUCKET_NAME}/flow-logs/" \
--max-aggregation-interval 600 \
--log-format '${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status} ${vpc-id} ${subnet-id} ${instance-id} ${tcp-flags} ${type} ${pkt-srcaddr} ${pkt-dstaddr} ${region} ${az-id} ${sublocation-type} ${sublocation-id} ${pkt-src-aws-service} ${pkt-dst-aws-service} ${flow-direction} ${traffic-path}' \
--destination-options "FileFormat=parquet,HiveCompatiblePartitions=true,PerHourPartition=true" \
--tag-specifications 'ResourceType=vpc-flow-log,Tags=[{Key=Name,Value=nat-traffic-analysis}]'
Why Parquet? It's columnar, compresses ~5× better than text, and Athena charges by data scanned, so smaller files mean cheaper queries. See the VPC Flow Logs in Parquet  blog post for details.

Step 3: Enable Route 53 Resolver DNS Query Logging

This captures every DNS query made by resources in the VPC. We need it to map the raw destination IPs from flow logs back to human-readable domain names.
Point it at the same S3 bucket under a /dns-logs/ prefix, and associate it with your target VPC. See Resolver query logging  for the Console walkthrough, or use the CLI:
1
2
3
4
5
6
7
8
CONFIG_ID=$(aws route53resolver create-resolver-query-log-config \
--name "nat-traffic-dns-logs" \
--destination-arn "arn:aws:s3:::${BUCKET_NAME}/dns-logs/" \
--query 'ResolverQueryLogConfig.Id' --output text)

aws route53resolver associate-resolver-query-log-config \
--resolver-query-log-config-id "${CONFIG_ID}" \
--resource-id "${VPC_ID}"
Once the bucket policy is in place (Step 4), DNS logs start appearing in S3 within a few minutes of the first query. Note that CloudWatch Vended Logs delivery charges apply to Resolver query logs even when published directly to S3.

Step 4: Apply the Bucket Policy

Both the VPC Flow Logs delivery service and Route 53 Resolver need s3:PutObject permission on the bucket. The combined policy below covers both; apply it now that both log sources exist.
Applying it at this point, rather than at bucket creation, sidesteps a sequencing pitfall: creating a flow log in the Console replaces any existing bucket policy (the CLI appends to it instead), so a policy applied earlier could be silently overwritten. AWS needs the policy in place at delivery time, not at creation time, so the log sources begin delivering as soon as you apply it.
In the S3 Console, go to your bucket → Permissions → Bucket policy, and apply all three statements below. For background on the required permissions, see flow log S3 permissions  and Resolver logging targets .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSLogDeliveryWrite",
"Effect": "Allow",
"Principal": {"Service": "delivery.logs.amazonaws.com"},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::BUCKET_NAME/flow-logs/AWSLogs/aws-account-id=ACCOUNT_ID/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control",
"aws:SourceAccount": "ACCOUNT_ID"
},
"ArnLike": {
"aws:SourceArn": "arn:aws:logs:REGION:ACCOUNT_ID:*"
}
}
},
{
"Sid": "AWSLogDeliveryAclCheck",
"Effect": "Allow",
"Principal": {"Service": "delivery.logs.amazonaws.com"},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::BUCKET_NAME",
"Condition": {
"StringEquals": {"aws:SourceAccount": "ACCOUNT_ID"},
"ArnLike": {"aws:SourceArn": "arn:aws:logs:REGION:ACCOUNT_ID:*"}
}
},
{
"Sid": "Route53ResolverLoggingWrite",
"Effect": "Allow",
"Principal": {"Service": "delivery.logs.amazonaws.com"},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::BUCKET_NAME/dns-logs/AWSLogs/ACCOUNT_ID/*",
"Condition": {
"StringEquals": {"s3:x-amz-acl": "bucket-owner-full-control"}
}
}
]
}
Replace BUCKET_NAME, ACCOUNT_ID, and REGION with your values. The flow-logs Resource uses the aws-account-id= prefix to match the Hive-partitioned delivery path enabled in Step 2.
The Route 53 statement intentionally omits aws:SourceAccount because the Resolver delivery service doesn't populate that context variable on PutObject requests.

Step 5: Collect Data (3–7 Days)

Both log sources begin delivering within about 10–15 minutes of the policy being applied. Verify by listing objects under s3://BUCKET_NAME/flow-logs/ and s3://BUCKET_NAME/dns-logs/.
Let the logs accumulate:
  • 3 days minimum, sufficient for steady-state workloads (web apps, APIs, microservices) where traffic patterns repeat daily
  • 7 days recommended if your environment includes weekly batch jobs, scheduled ETL, CI/CD pipelines, or cron tasks that only run on certain days

Step 6: Create Athena Tables

With logs in S3, create two external tables in Athena. Both use partition projection , so new hourly partitions are discovered automatically, with no MSCK REPAIR TABLE needed.

Flow Logs Table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
CREATE EXTERNAL TABLE IF NOT EXISTS nat_flow_logs (
version int,
account_id string,
interface_id string,
srcaddr string,
dstaddr string,
srcport int,
dstport int,
protocol bigint,
packets bigint,
bytes bigint,
start bigint,
`end` bigint,
action string,
log_status string,
vpc_id string,
subnet_id string,
instance_id string,
tcp_flags int,
type string,
pkt_srcaddr string,
pkt_dstaddr string,
region string,
az_id string,
sublocation_type string,
sublocation_id string,
pkt_src_aws_service string,
pkt_dst_aws_service string,
flow_direction string,
traffic_path int
)
PARTITIONED BY (
`aws-account-id` string,
`aws-service` string,
`aws-region` string,
`year` string,
`month` string,
`day` string,
`hour` string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION 's3://BUCKET_NAME/flow-logs/AWSLogs/'
TBLPROPERTIES (
'EXTERNAL'='true',
'projection.enabled'='true',
'projection.aws-account-id.type'='enum',
'projection.aws-account-id.values'='ACCOUNT_ID',
'projection.aws-service.type'='enum',
'projection.aws-service.values'='vpcflowlogs',
'projection.aws-region.type'='enum',
'projection.aws-region.values'='REGION',
'projection.year.type'='integer',
'projection.year.range'='2024,2030',
'projection.month.type'='integer',
'projection.month.range'='1,12',
'projection.month.digits'='2',
'projection.day.type'='integer',
'projection.day.range'='1,31',
'projection.day.digits'='2',
'projection.hour.type'='integer',
'projection.hour.range'='0,23',
'projection.hour.digits'='2',
'storage.location.template'=
's3://BUCKET_NAME/flow-logs/AWSLogs/aws-account-id=${aws-account-id}/aws-service=${aws-service}/aws-region=${aws-region}/year=${year}/month=${month}/day=${day}/hour=${hour}'
);

DNS Query Logs Table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE EXTERNAL TABLE IF NOT EXISTS dns_query_logs (
version string,
account_id string,
region string,
vpc_id string,
query_timestamp string,
query_name string,
query_type string,
query_class string,
rcode string,
answers array<struct<Rdata:string, Type:string, Class:string>>,
srcaddr string,
srcport int,
transport string,
srcids struct<instance:string, resolver_endpoint:string>
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://BUCKET_NAME/dns-logs/AWSLogs/ACCOUNT_ID/vpcdnsquerylogs/VPC_ID/'
TBLPROPERTIES ('classification'='json');
Replace BUCKET_NAME, ACCOUNT_ID, REGION, and VPC_ID in both DDL statements.
Verify the DNS table parses correctly. Run this after creating the table:
1
2
3
SELECT query_name, rcode, srcaddr, answers
FROM dns_query_logs
LIMIT 5;
All four columns should populate. If query_name/rcode/srcaddr show data but answers is NULL on every row, the JSON structure doesn't match the DDL's answers array<struct<Rdata, Type, Class>> schema. (An empty answers: [] array on some rows is normal: it means the response had no answer records, such as an NXDOMAIN or a name with no record of the queried type.)
To inspect the raw JSON and compare field names:
1
2
aws s3 cp s3://BUCKET_NAME/dns-logs/AWSLogs/ACCOUNT_ID/vpcdnsquerylogs/VPC_ID/YYYY/MM/DD/<file>.gz - \
| gunzip | head -1 | jq .

Step 7: Run the Analysis Query

This query identifies all traffic that traversed the NAT Gateway in both directions (uploads and downloads), groups by destination, joins DNS logs to resolve IPs to domain names, and ranks by total bytes transferred.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
WITH nat_flows AS (
SELECT
pkt_dstaddr AS destination_ip,
dstport,
protocol,
bytes AS total_bytes,
pkt_dst_aws_service,
'outbound' AS direction
FROM nat_flow_logs
WHERE year = 'YYYY' AND month = 'MM' AND day BETWEEN 'DD' AND 'DD'
AND dstaddr <> pkt_dstaddr
AND log_status = 'OK'
UNION ALL
SELECT
pkt_srcaddr AS destination_ip,
srcport AS dstport,
protocol,
bytes AS total_bytes,
pkt_src_aws_service AS pkt_dst_aws_service,
'inbound' AS direction
FROM nat_flow_logs
WHERE year = 'YYYY' AND month = 'MM' AND day BETWEEN 'DD' AND 'DD'
AND srcaddr <> pkt_srcaddr
AND log_status = 'OK'
),
dns_map AS (
SELECT ip, MAX(domain) AS domain
FROM (
SELECT DISTINCT
ans.Rdata AS ip,
query_name AS domain
FROM dns_query_logs
CROSS JOIN UNNEST(answers) AS t(ans)
WHERE ans.Type = 'A'
)
GROUP BY ip
)
SELECT
COALESCE(dm.domain, NULLIF(nf.pkt_dst_aws_service, '-'), nf.destination_ip) AS destination_name,
arbitrary(NULLIF(nf.pkt_dst_aws_service, '-')) AS aws_service,
COUNT(DISTINCT nf.destination_ip) AS distinct_ips,
COUNT(*) AS flow_count,
SUM(nf.total_bytes) AS total_bytes,
ROUND(SUM(nf.total_bytes) / 1073741824.0, 3) AS total_gb
FROM nat_flows nf
LEFT JOIN dns_map dm ON nf.destination_ip = dm.ip
GROUP BY 1
ORDER BY total_bytes DESC
LIMIT 50;
Update the date filters (YYYY, MM, DD) to match your collection window. Always include them. Without partition filters, Athena scans all historical data and the query will be slow and expensive.
Performance: A 7-day dataset from a medium VPC (~5 GB Parquet) typically completes in 15–30 seconds at a cost of $0.05–$0.10. For repeated exploration, consider materializing with CREATE TABLE AS SELECT.
Results are grouped by resolved destination, with distinct_ips showing how many addresses each destination was served from. To break a destination back out by IP, add destination_ip to the SELECT and GROUP BY.
Note on DNS attribution: Domain resolution is best-effort: it covers only the lookups that went through the VPC's Route 53 Resolver during the collection window, so traffic to hardcoded IPs or cached records won't resolve. When a destination resolved, destination_name shows one of the domains seen for that IP (indicative, not a guaranteed 1:1 mapping per flow); when it didn't, the column falls back to the AWS service tag or the raw IP.
Note on private NAT Gateways: If your VPC uses a private NAT Gateway  for inter-VPC routing (e.g., overlapping CIDRs), that traffic will also appear in the results, identifiable by private destination IPs (10.x, 172.16–31.x, 192.168.x). This doesn't affect the accuracy of the report; simply focus on public IP and AWS service destinations when evaluating VPC Endpoint candidates.

Step 8: Reading the Results

The output gives you two categories of destinations worth acting on. Sort them by reading the destination_name (domain) column; the aws_service tag is too coarse to rely on for this.

AWS Service Traffic: VPC Endpoint Candidates

Look for destination_name values ending in amazonaws.com. These are AWS service endpoints currently reached through NAT that could go through a VPC Endpoint instead. The subdomain identifies the service (monitoring.*, sts.*, kms.*, and so on).
AWS PrivateLink now supports 200+ AWS services . For almost all of them, a domain of <service>.*.amazonaws.com maps to the endpoint com.amazonaws.<REGION>.<service>, so a monitoring.* domain means the com.amazonaws.us-east-1.monitoring endpoint, and so on.
The services that most commonly top the results: s3, dynamodb, monitoring, logs, ssm, ssmmessages, ec2messages, sts, kms, secretsmanager, sqs, sns, execute-api, lambda, elasticfilesystem, bedrock-runtime, and ec2. A few endpoint names don't match their domain exactly (for example, Kinesis and ECR), so confirm the precise name in the PrivateLink service catalog  when you create the endpoint.
Gateway endpoints (s3 and dynamodb) are free with no hourly or per-GB charges, so deploy these regardless of volume. Note that S3 also offers a (priced) Interface endpoint  for cross-region or on-premises (Direct Connect) access; the free Gateway endpoint is the right choice for same-region VPC traffic.
Every other service above uses an Interface endpoint, which carries a fixed hourly cost per AZ plus $0.01/GB processed. For high-volume destinations, they're significantly cheaper than NAT's $0.045/GB processing charge. The Appendix at the end of this article includes the break-even math, but as a quick rule of thumb: if a single destination is moving more than ~400 GB/month through your 2-AZ NAT Gateway, an Interface endpoint will likely cost less.
Note: While PrivateLink coverage is extensive, not every service is available in every region. Verify availability for your specific region at AWS services that integrate with AWS PrivateLink .

Third-Party SaaS Traffic: Private Connectivity Candidates

Look for destination_name values that are recognizable vendor domains rather than amazonaws.com. These are third-party services reached over NAT.
A growing number of these providers support private connectivity via AWS PrivateLink. Some common examples:
ProviderTypical domains in resultsDocumentation
Datadog*.datadoghq.comConnect to Datadog over AWS PrivateLink 
MongoDB Atlas*.mongodb.netAtlas Private Endpoints 
Snowflake*.snowflakecomputing.comAWS PrivateLink & Snowflake 
Confluent Cloud*.confluent.cloudSet up AWS PrivateLink 
Elastic Cloud*.elastic-cloud.comPrivate connectivity with AWS PrivateLink 
HashiCorp Vault (HCP)*.hashicorp.cloudConfigure AWS PrivateLink 
Splunk Cloud*.splunkcloud.comAbout private connectivity 
Switching these flows to PrivateLink removes NAT Gateway data processing charges for that traffic and keeps it on the AWS network rather than traversing the public internet.
The list above is not exhaustive; many other SaaS providers support PrivateLink. If you see significant traffic to any third-party domain in your results, check the vendor's networking or security documentation for PrivateLink support, or reach out to them directly.

Next Steps: Deploying VPC Endpoints

Once you've identified your top destinations, the following resources walk through the implementation:
Core documentation:

Step 9: Cleanup

Once you have your results:
  1. Delete the VPC Flow Log (VPC Console → Flow Logs tab, or aws ec2 delete-flow-logs)
  2. Disassociate the VPC from the Route 53 DNS Query Log config, then delete the config (Route 53 Console → Resolver → Query logging, or aws route53resolver disassociate-resolver-query-log-config followed by aws route53resolver delete-resolver-query-log-config). The config can't be deleted while a VPC is still associated.
  3. Empty and delete the S3 bucket, or add a lifecycle rule to auto-expire objects
  4. Drop the Athena tables:
1
2
DROP TABLE IF EXISTS nat_flow_logs;
DROP TABLE IF EXISTS dns_query_logs;


Appendix: Interface Endpoint Break-Even

Interface endpoints are cheaper than NAT for high-volume destinations, but carry a fixed hourly cost. Here's how to decide.
The economics: NAT charges $0.045/GB. An Interface endpoint charges $0.01/GB plus $0.01/hour per Availability Zone. The net savings when you move a flow to an endpoint is $0.035/GB, but you need enough volume to cover the endpoint's fixed hourly cost.
Solving for the break-even:
1
2
3
Total_GB × $0.045 = (AZs × 730 hrs × $0.01) + (Total_GB × $0.01)
Total_GB × $0.035 = AZs × $7.30
Total_GB = AZs × 208.6
Endpoint AZ countFixed cost/monthBreak-even traffic to one destination
1 AZ$7.30~209 GB/month
2 AZs$14.60~417 GB/month
3 AZs$21.90~626 GB/month
Reading this against your query output: The total_gb column is the combined traffic to that destination across all AZs. If a row exceeds the break-even for your planned endpoint AZ count, the endpoint will be cheaper than continuing through NAT.
Quick check, at exactly 417 GB with 2 AZs:
  • NAT: 417 × $0.045 = $18.77
  • Endpoint: $14.60 + (417 × $0.01) = $18.77 ✓
Above that, every additional GB saves $0.035. (Note: the $0.01/GB endpoint data processing rate is tiered, dropping to $0.006/GB above 1 PB/month and $0.004/GB above 5 PB/month per region. For most environments this first tier applies.) Below it, NAT remains the more economical path for that specific destination.
These rates apply to standard commercial regions (us-east-1, eu-west-1, etc.). Pricing varies by region; confirm at NAT Gateway pricing  and PrivateLink pricing .

Further Reading

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article