← Back to blog

AWS VPC Endpoints: Cost Comparison of Gateway, Interface, NAT, and Transit Gateway

· Phil Stevenson
aws cost endpoints networking terraform vpc

There are four ways to make AWS services accessible from private subnets, and the costs vary dramatically. Gateway Endpoints are free but limited to S3 and DynamoDB. Interface Endpoints cost around $8.76/month/AZ. NAT Gateways run around $37.96/month/AZ. Transit Gateway attachments run around $43.80/month/AZ plus endpoint costs. I had to dig to find all the costs in one place, so here they are with the Terraform configuration for the VPC endpoints I use in my EKS clusters. Prices depend on the region.

The following VPC endpoints are the minimum required for managed nodes to join an EKS cluster when running in private subnets: ec2, ecr.dkr, ecr.api, sts, s3.

Reference: https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html#managed-node-group-concepts

Cost comparison

https://pcg.io/insights/vpc-endpoints-explanation-and-cost-comparison/

There are four options to make AWS services accessible from private subnets:

Option Cost Notes
Gateway Endpoint Free S3 and DynamoDB only. Needs an internet gateway, route table configuration, and security group rules/NACLs to allow traffic to the prefix list
Interface Endpoint ~$8.76/month/AZ + ~$0.01/GB Most AWS services. Provides a private IP in your subnet. Needs a security group rule allowing traffic to it
NAT Gateway ~$37.96/month/AZ + ~$0.052/GB Any service with a public API. For less critical workloads, a NAT instance via fck-nat.dev avoids NAT data processing fees. You only pay EC2 instance hours and EBS storage
Transit Gateway ~$43.80/month/AZ + ~$0.02/GB Interface Endpoint fees still apply. Likely only cost-effective across multiple connected VPCs

Terraform

S3 and DynamoDB use Gateway Endpoints (free, no security group required, traffic routes via route tables). All other services use Interface Endpoints.

module "endpoints" {
  source  = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints"
  version = "6.6.1"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  security_group_ids = [
    aws_security_group.vpc_endpoints_default.id,
  ]

  endpoints = {
    "sts" = {
      service             = "sts"
      private_dns_enabled = true
      tags                = { Name = "${module.vpc.name}-sts-vpc-endpoint" }
    },
    dynamodb = {
      service         = "dynamodb"
      service_type    = "Gateway"
      route_table_ids = flatten([module.vpc.private_route_table_ids])
      tags            = { Name = "${module.vpc.name}-dynamodb-vpc-endpoint" }
    },
    s3 = {
      service         = "s3"
      service_type    = "Gateway"
      route_table_ids = flatten([module.vpc.private_route_table_ids])
      tags            = { Name = "${module.vpc.name}-s3-vpc-endpoint" }
    },
    "kinesis-streams" = {
      service             = "kinesis-streams"
      private_dns_enabled = true
      tags                = { Name = "${module.vpc.name}-kinesis-streams-vpc-endpoint" }
    },
    "kinesisanalytics" = {
      service             = "kinesisanalytics"
      private_dns_enabled = true
      tags                = { Name = "${module.vpc.name}-kinesisanalytics-vpc-endpoint" }
    },
    "logs" = {
      service             = "logs"
      private_dns_enabled = true
      tags                = { Name = "${module.vpc.name}-logs-vpc-endpoint" }
    },
    "monitoring" = {
      service             = "monitoring"
      private_dns_enabled = true
      tags                = { Name = "${module.vpc.name}-monitoring-vpc-endpoint" }
    },
    "sns" = {
      service             = "sns"
      private_dns_enabled = true
      tags                = { Name = "${module.vpc.name}-sns-vpc-endpoint" }
    },
    "sqs" = {
      service             = "sqs"
      private_dns_enabled = true
      tags                = { Name = "${module.vpc.name}-sqs-vpc-endpoint" }
    },
    "secretsmanager" = {
      service             = "secretsmanager"
      private_dns_enabled = true
      tags                = { Name = "${module.vpc.name}-secretsmanager-vpc-endpoint" }
    },
    "execute-api" = {
      service             = "execute-api"
      private_dns_enabled = true
      tags                = { Name = "${module.vpc.name}-execute-api-vpc-endpoint" }
    },
  }

  tags = local.tags
}

# Security group for interface endpoints
# Gateway endpoints (S3, DynamoDB) do not use security groups
resource "aws_security_group" "vpc_endpoints_default" {
  name        = "${module.vpc.name}-vpc-endpoints-default"
  description = "Default Security group for VPC endpoints"
  vpc_id      = module.vpc.vpc_id

  tags = { Name = "${module.vpc.name}-vpc-endpoints-default" }
}

# Allow HTTPS from within the security group (i.e. resources that share this SG)
resource "aws_security_group_rule" "vpc_endpoints_default_self_ingress" {
  description       = "Allow all traffic from self VPC endpoints group"
  security_group_id = aws_security_group.vpc_endpoints_default.id
  type              = "ingress"
  protocol          = "tcp"
  from_port         = 443
  to_port           = 443
  self              = true
}

resource "aws_security_group_rule" "vpc_endpoints_default_self_egress" {
  description       = "Allow secure traffic to self VPC endpoints group"
  security_group_id = aws_security_group.vpc_endpoints_default.id
  type              = "egress"
  protocol          = "tcp"
  from_port         = 443
  to_port           = 443
  self              = true
}

# Gateway endpoints are reached via prefix lists, not security groups
resource "aws_security_group_rule" "vpc_endpoints_default_dynamodb_egress" {
  description       = "Allow secure traffic to DynamoDB via prefix list"
  security_group_id = aws_security_group.vpc_endpoints_default.id
  type              = "egress"
  protocol          = "tcp"
  from_port         = 443
  to_port           = 443
  prefix_list_ids   = [module.endpoints.endpoints.dynamodb.prefix_list_id]
}

resource "aws_security_group_rule" "vpc_endpoints_default_s3_egress" {
  description       = "Allow secure traffic to S3 via prefix list"
  security_group_id = aws_security_group.vpc_endpoints_default.id
  type              = "egress"
  protocol          = "tcp"
  from_port         = 443
  to_port           = 443
  prefix_list_ids   = [module.endpoints.endpoints.s3.prefix_list_id]
}