Why Is My AWS Bill So High? Easily finding NAT Gateway, Data Transfer, Networking and other Costs with CUR and Athena and Kiro
A practical guide for engineering and finance teams who want to understand their AWS spend at the resource level — without spending hours in the console.
Understanding AWS Billing Granularity
AWS billing is designed to be precise. With over 200 services, thousands of instance types, and pricing dimensions that vary by region, availability zone, usage type, and operation, the billing system captures an extraordinary level of detail. That granularity exists for good reason: it enables accurate cost attribution, supports chargeback models across teams, and gives you the data you need to optimise at every layer of your architecture.
That same granularity, however, creates a real challenge for day-to-day cost management. A single workload — say, a containerised application running on EKS — can generate charges across a dozen different billing dimensions: cluster hours, EC2 compute, EBS volumes, NAT Gateway hours, NAT Gateway data processing, cross-AZ data transfer, VPC endpoint hours, Elastic IP addresses, load balancer LCUs, CloudWatch logs, and ECR storage. Each of these appears as a separate line item, often under different service categories.
This is not a flaw in the billing system. It reflects the actual structure of what you're consuming. But it does mean that understanding the true cost of a service requires correlating charges across multiple categories — and that correlation is not something the AWS billing console does automatically.
The result is a common pattern: engineering teams spend significant time each month manually reconciling charges, finance teams receive bills they cannot interpret without engineering support, and cost anomalies — like unexpected data transfer charges — can go unnoticed for months before someone traces them to their source.
The Architecture: CUR + Athena + AWS Glue
The foundation of any serious AWS cost visibility solution is the AWS Cost and Usage Report (CUR). CUR is the most detailed billing dataset AWS provides. It captures every charge at the resource level, including:
-
line_item_resource_id— the ARN of the specific resource being charged
-
line_item_usage_type— the precise usage dimension (e.g.USE1-NatGateway-Hours)
-
line_item_operation— what the resource was doing (e.g.InterZone-Out,NatGateway,VpcEndpoint)
-
line_item_availability_zone— the AZ where the charge originated
-
product_region— the AWS region, already decoded
-
line_item_line_item_description— AWS's own description of the charge
When configured with Parquet format and Athena integration, CUR delivers data to S3 and AWS Glue automatically creates and maintains a queryable table. The architecture looks like this:
┌─────────────────────────────────────────────────────────────────┐│ AWS Account ││ ││ ┌──────────────┐ hourly ┌─────────────────────────┐ ││ │ AWS Billing │ ─────────────▶│ S3 Bucket (Parquet) │ ││ │ (CUR) │ │ costreport-emb1/ │ ││ └──────────────┘ └────────────┬────────────┘ ││ │ ││ ┌─────────▼──────────┐ ││ │ AWS Glue Crawler │ ││ │ (auto-updates │ ││ │ schema/partitions│ ││ └─────────┬──────────┘ ││ │ ││ ┌─────────▼──────────┐ ││ │ Glue Data Catalog │ ││ │ cur_database. │ ││ │ cost_and_usage_ │ ││ │ report │ ││ └─────────┬──────────┘ ││ │ ││ ┌──────────────┐ ┌─────────▼──────────┐ ││ │ Your laptop │◀─────────────────│ Amazon Athena │ ││ │ (Python + │ SQL results │ (serverless SQL) │ ││ │ boto3) │ └────────────────────┘ ││ └──────────────┘ │└─────────────────────────────────────────────────────────────────┘Enabling CUR with Athena Integration
In the AWS Billing console, navigate to Cost and Usage Reports → Create report and configure:
Setting | Value |
|---|---|
Report name | costreport-<your-account-alias> |
Include resource IDs | ✅ Enabled |
Time granularity | Hourly |
Report versioning | Overwrite existing report |
Format | Parquet |
Compression | Parquet |
Additional schema elements | RESOURCES |
Additional artifacts | Amazon Athena |
Once enabled, AWS delivers the first report within 24 hours and creates a CloudFormation template in your S3 bucket that sets up the Glue crawler and catalog table automatically. Run the crawler once to initialise the table, then set it on a daily schedule to pick up new partitions.
The resulting Athena table is partitioned by
year and month , which keeps query costs low — a typical monthly analysis query scans only the relevant partition.
The Challenge of Hard to find Networking Costs
One category of charges deserves special attention because it consistently surprises teams: networking costs that appear under non-networking service categories.
AWS networking charges are distributed across the billing system in ways that reflect the underlying service ownership rather than a logical cost grouping:
Charge | Appears under | Why |
|---|---|---|
NAT Gateway hours | EC2 - Other | NAT Gateways are an EC2 networking primitive |
NAT Gateway data processing | EC2 - Other | Same resource, different dimension |
Cross-AZ data transfer | EC2 - Other | Charged at the EC2 layer |
VPC Endpoint hours | Amazon VPC | VPC service resource |
Transit Gateway attachment hours | Amazon VPC | VPC service resource |
Public IPv4 address charges | Amazon VPC | Introduced Feb 2024, $0.005/hr per IP |
Inter-region data transfer | Varies by source service | Charged at the originating service |
This distribution is architecturally correct — each charge belongs to the service that owns the resource. But it means that a team asking "what does our networking cost?" cannot get the answer from a single line item. They need to aggregate across multiple services and usage type patterns.
Building the Solution with Agentic Coding Assistant
Rather than writing these scripts from scratch, we used an agentic coding tool (Kiro) to generate the implementation. The key is giving the agent enough context about the CUR schema and the specific problem to produce accurate, working code.
Prompt 1: Hidden Networking Cost Analyzer (analyze-cost-transfer.py)
I have AWS Cost and Usage Reports (CUR) stored in S3 and queryable via Amazon Athena. The Glue catalog database iscur_databaseand the table iscost_and_usage_report. The S3 output location for Athena results iss3://my-bucket/athena-results/. The table is partitioned byyearandmonth(stored without zero-padding, e.g. month='2' not '02').Write a Python script using boto3 that queries this CUR table via Athena to surface all hidden networking costs — specifically: NAT Gateway hours and bytes (under AmazonEC2), cross-AZ data transfer (DataTransfer-Regional usage types under AmazonEC2), Transit Gateway attachment hours (under AmazonVPC), VPC Endpoint hours and bytes (under AmazonVPC), and Public IPv4 address charges (PublicIPv4 usage types under AmazonVPC).For each line item, include: usage type, service (product_code), region (product_region), cost, quantity, and the resource ARN (line_item_resource_id). Group the summary by category (NAT Gateway, Cross-AZ Transfer, etc.) and show individual resource ARNs with their costs under each category. Include a direct AWS Console URL for each category so the user can navigate straight to the relevant resource list. Output a formatted text report and a JSON file. Accept optional start/end date arguments, defaulting to the last full calendar month.
Prompt 2: Finance-Friendly Bill Explainer (generate-bill-explainer.py)
Using the same CUR Athena setup (database:cur_database, table:cost_and_usage_report, Athena output:s3://my-bucket/athena-results/), write a Python script that generates a self-contained HTML report suitable for non-technical finance stakeholders.The report should:* Show total monthly spend prominently* List every AWS service with its cost and percentage of total bill* For each service, provide a plain-English explanation of what the service does and why it costs money (e.g. "Amazon EKS charges $0.10/hour per cluster regardless of workload size")* Include saving tips where applicable (e.g. "Upgrade past end-of-life Kubernetes versions to remove the extended support surcharge")* Allow clicking a service row to expand and show the underlying line items with usage type, operation, region, quantity, and cost
- Be a single HTML file with no external dependencies — embeddable in email or shareable via a file linkCover at minimum these services with plain-English explanations: Amazon Bedrock (token-based pricing), Amazon SageMaker (GPU instances, Studio notebooks, storage), Amazon EKS (per-cluster fee, extended support surcharge), EC2 (instance hours, NAT Gateway, cross-AZ transfer), Amazon OpenSearch (OCU pricing), VPC (endpoints, Transit Gateway, public IPs), Amazon Comprehend, Amazon GuardDuty, Amazon Kinesis, RDS, ElastiCache, CloudWatch, KMS, ELB, and AWS Directory Service.
What the Scripts Produce
Hidden Networking Cost Report
Running
analyze-cost-transfer.py against a real account produces output like this:════════════════════════════════════════════════════════════════════════ HIDDEN NETWORKING COST REPORT | 2026-02-01 → 2026-03-01 Source: CUR Athena (cur_database.cost_and_usage_report)════════════════════════════════════════════════════════════════════════
NAT Gateway — $342.36NOTE: Billed under EC2, not under Networking or Data Transfer
$146.57 NatGateway-Hours | region: us-east-1 resources (9 found): $30.24 arn:aws:ec2:us-east-1:123456789012:natgateway/nat-01f14ff135e1v7we1 $30.24 arn:aws:ec2:us-east-1:123456789012:natgateway/nat-0dda7b685bd864577 $23.90 arn:aws:ec2:us-east-1:123456789012:natgateway/nat-0e9c905a4da5934fe ... console: https://console.aws.amazon.com/vpc/home?region=us-east-1#NatGateways:
VPC Endpoints / PrivateLink — $221.77 $161.28 USW2-VpcEndpoint-Hours | region: us-west-2 resources (8 found): $20.16 arn:aws:ec2:us-west-2:123456789012:vpc-endpoint/vpce-0a280ebe2f7fa1854 $20.16 arn:aws:ec2:us-west-2:123456789012:vpc-endpoint/vpce-0fa7a77cdb13f4511 ...
SUMMARY $342 NAT Gateway $221 VPC Endpoints / PrivateLink $100 Transit Gateway $80 Public IPv4 Addresses $5 Cross-AZ Transfer───────────────────────────────────────────────────── $750 TOTAL (11.5% of $6,532 monthly spend)Every resource ARN is actionable. You can take the NAT Gateway ID directly to the VPC console, identify which VPC it belongs to, and determine whether it can be consolidated or replaced with VPC endpoints for AWS service traffic.
Finance Bill Explainer
The HTML report opens in any browser. The top of the page shows the total spend for the month. Below it, each service appears as a row with cost, percentage of bill, and a one-line description. Clicking a row expands it to show:
- A plain-English explanation of what the service does
- Why the specific charges are incurred
- A saving tip where one exists
- The underlying line items with usage type, region, quantity, and cost
For example, the EKS row expands to:
Kubernetes cluster management fee AWS charges $0.10/hour (~$73/month) per Kubernetes cluster to manage the control plane, regardless of how many workloads are running on it. 💡 One cluster in us-west-2 is running a Kubernetes version past its standard support end date. AWS applies an extended support surcharge of $0.60/hour on top of the normal fee. Upgrading to a supported version removes this charge.
No AWS console access required. No technical knowledge needed. The file can be emailed or shared via a link.
Findings From a Real Account
Running these scripts against a production account for February 2026 surfaced several items worth acting on:
Idle Elastic IP addresses — Three public IPs allocated but not attached to any running resource. At $0.005/hour each, the cost is small individually, but they signal a cleanup opportunity and are trivially easy to release.
EKS Extended Support surcharge — One cluster running a Kubernetes version past its end-of-support date. The surcharge adds $0.60/hour on top of the standard $0.10/hour cluster fee — a 600% increase for that cluster. Upgrading the Kubernetes version removes the charge entirely.
13 VPC Interface Endpoints — Eight in us-west-2 at $20.16/month each, five in us-east-1. Interface endpoints are valuable for keeping traffic off the public internet, but unused endpoints still bill. Auditing which endpoints are receiving traffic (via CloudWatch metrics) and removing unused ones is a straightforward optimisation.
NAT Gateway data asymmetry — One NAT Gateway processed significantly more data than the others in the same region. This is a signal worth investigating: it may indicate that a service is routing traffic through NAT that could instead use a VPC endpoint, eliminating the per-GB processing charge.
Running the Scripts
Both scripts require (Scripts are generated by Kiro and for brevity have not been provided):
- AWS credentials with
athena:StartQueryExecution,athena:GetQueryResults,s3:GetObject, ands3:PutObjectpermissions on the CUR bucket
- CUR enabled with Athena integration and the Glue crawler having run at least once
# Install dependencypip install boto3
# Hidden networking cost report — last full monthpython3 cost_management/analyze-cost-transfer.py
# Specific date rangepython3 cost_management/analyze-cost-transfer.py 2026-01-01 2026-02-01
# Finance HTML reportpython3 cost_management/generate-bill-explainer.py# Output: cost_management/aws-bill-explainer-2026-02.html
Summary
AWS Cost and Usage Reports, combined with Amazon Athena and the AWS Glue Data Catalog, provide a complete and queryable record of every charge in your account at the resource level. The complexity of AWS billing reflects the genuine complexity of what you are consuming — but that complexity does not have to be opaque.
With the right queries and a small amount of Python, you can:
- Surface networking costs that are distributed across multiple service categories and attribute them to specific resource ARNs
- Generate finance-ready reports that translate technical billing dimensions into plain English, without requiring AWS console access
Agentic coding tools accelerate this significantly. With a well-structured prompt that describes the CUR schema, the Athena configuration, and the specific cost categories you want to surface, a tool like Kiro can generate working, production-quality scripts in a single session — including the HTML report with per-service explanations.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article