En casi todas las cuentas de AWS que he revisado hay una línea en la factura que sorprende a todo el mundo: el NAT Gateway. No es un servicio que alguien eligiera conscientemente. Aparece porque el asistente de creación de VPC lo pone por defecto, porque tus tareas de Fargate viven en subredes privadas y necesitan salir a internet, y porque nadie miró después. Cobra por hora y por gigabyte procesado, en ambas direcciones. Y lo peor: buena parte de ese tráfico ni siquiera va a internet —va a S3, a DynamoDB, a Secrets Manager— y podrías haberlo sacado de ahí con una pieza que cuesta cero.
Por qué existe el NAT Gateway
Una subred privada, por definición, no tiene ruta a un Internet Gateway. Eso es lo que la hace privada: nada de fuera puede iniciar una conexión hacia dentro. Pero tus contenedores sí necesitan salir: a descargar una imagen de ECR, a leer un secreto, a llamar a una API de terceros.
El NAT Gateway resuelve eso: vive en una subred pública, las privadas enrutan su tráfico saliente hacia él, y él traduce las direcciones para que las respuestas vuelvan. Funciona, es gestionado, es altamente disponible dentro de su AZ. Y por eso todo el mundo lo pone y se olvida.
El problema es su modelo de precios. Pagas una tarifa por hora por cada NAT Gateway —y como cada uno vive en una AZ, la práctica recomendada de alta disponibilidad te pide uno por AZ, así que multiplica— más una tarifa por cada gigabyte que lo atraviesa. Ese cargo por gigabyte se aplica al tráfico procesado, y se acumula sin que nadie lo esté mirando.
El descubrimiento incómodo: buena parte de ese tráfico es interno
Aquí está el detalle que cambia el análisis. Cuando tu tarea de Fargate en una subred privada escribe un objeto en S3, ese tráfico sale por el NAT Gateway, va al endpoint público de S3 y vuelve. Estás pagando transferencia por hablar con un servicio de AWS que está en la misma región que tú.
Lo mismo con DynamoDB, con SQS, con Secrets Manager, con CloudWatch Logs —y CloudWatch Logs es el gran sospechoso silencioso, porque si tienes logs verbosos, cada línea es tráfico facturado— y con ECR cada vez que arranca una tarea y baja capas de imagen.
En arquitecturas serverless o de contenedores bien pobladas, la mayoría del tráfico saliente no va a internet de verdad. Va a AWS.
VPC endpoints: dos sabores, precios muy distintos
Un VPC endpoint permite que tu subred privada hable con un servicio de AWS sin pasar por internet ni por el NAT. Hay dos tipos, y confundirlos es caro.
Los gateway endpoints existen solo para S3 y DynamoDB. Son una entrada en la tabla de rutas. No cuestan nada: ni por hora, ni por gigabyte. No hay ninguna razón defendible para no tenerlos si usas S3 o DynamoDB desde subredes privadas. Es dinero tirado.
Los interface endpoints (PrivateLink) son para todo lo demás: SQS, Secrets Manager, ECR, CloudWatch, KMS. Crean una interfaz de red en tu subred con una IP privada. Estos sí cuestan: una tarifa por hora por endpoint y por AZ, más un cargo por gigabyte —bastante más barato que el del NAT, pero no cero.
💡 Los gateway endpoints de S3 y DynamoDB son gratis. Si tus subredes privadas no los tienen, estás pagando NAT Gateway por hablar con S3. Ese es el primer sitio donde mirar, y suele ser el más rentable.
Cómo decido qué endpoints crear
Con interface endpoints la cuenta no es automática, porque tienen coste fijo por hora y por AZ. Un endpoint vale la pena cuando el ahorro en tráfico NAT supera esa tarifa fija. La forma de saberlo es mirar los datos, no adivinar: activo Flow Logs en la interfaz elástica del NAT Gateway y agrupo por IP destino, resolviendo esas IPs contra los rangos publicados de AWS para saber qué servicio es cuál.
Con esa tabla delante la decisión es aritmética. Si el 60% de tu tráfico NAT es a S3, un gateway endpoint gratis se lo lleva entero. Si otro 20% es CloudWatch Logs, el interface endpoint se paga solo. Si hay un 5% a KMS, probablemente no compense el coste fijo.
En OpenTofu es tan poco código que casi da vergüenza no tenerlo:
# Gratis. Sin excusas.
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
}
# De pago: crea una ENI por subred. Justifícalo con datos.
resource "aws_vpc_endpoint" "logs" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.logs"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.endpoints.id]
private_dns_enabled = true # sin esto, tu SDK sigue yendo al endpoint público
}
Ese private_dns_enabled es el que hace que funcione sin tocar el código: resuelve el nombre público del servicio a la IP privada del endpoint. Si lo dejas en false, creas el endpoint, pagas por él, y tu aplicación sigue saliendo por el NAT tan tranquila. Es un error que solo se ve en la factura.
La pregunta más incómoda: ¿necesitas el NAT?
Antes de optimizar el NAT Gateway conviene preguntarse si hace falta. Si tus contenedores solo hablan con servicios de AWS y nunca llaman a una API de terceros, puedes cubrir todo con endpoints y quitarlo. Es la única optimización que lleva ese coste a cero.
En la práctica casi siempre hay algo que sale a internet: un webhook, una pasarela de pagos, un proveedor de LLM. Entonces el NAT se queda, pero con la mayoría del tráfico desviado, y su factura pasa de ser un misterio a ser una línea pequeña y explicable.
El trade-off honesto: añades componentes de red que hay que mantener, versionar en tu IaC y depurar cuando un security group mal configurado hace que un endpoint tire timeouts en vez de errores claros. A cambio, el tráfico interno deja de tocar internet —lo que además es mejor postura de seguridad— y la factura deja de crecer con tu volumen de logs. Para mí ha compensado siempre. Pero decídelo con Flow Logs, no con este artículo.