VPC en AWS con EC2 y RDS: guía práctica paso a paso
Monté desde cero una VPC en AWS con subredes públicas y privadas, EC2 y RDS. Estos son los pasos reales y los errores que me encontré en el camino.
Tener el diagrama de una VPC clara en la cabeza es una cosa. Llevarlo a la consola de AWS y que todo hable entre sí sin que un Security Group mal ubicado te tire un error críptico, es otra completamente distinta. En este artículo documento paso a paso cómo levanté una arquitectura clásica de dos capas en AWS —EC2 en una subred pública, RDS en una subred privada— con capturas reales de cada etapa, y sin esconder los errores que fui encontrando en el camino, porque son justo esos errores los que más cuestan encontrar en la documentación oficial.
Si estás empezando con AWS y quieres pasar de la teoría de redes a algo desplegado y funcionando de verdad, esta guía es ese camino recorrido.
La arquitectura que vamos a construir
El objetivo es el patrón más recomendado por AWS para una aplicación web tradicional: un servidor web accesible desde internet, y una base de datos completamente aislada que solo ese servidor puede alcanzar.
Diagrama de referencia: VPC 10.0.0.0/16, subred pública 10.0.1.0/24 con la EC2, subred privada 10.0.2.0/24 con la RDS.
Los componentes:
- VPC:
10.0.0.0/16, el contenedor de red completo. - Subred pública:
10.0.1.0/24, con salida directa a internet vía Internet Gateway. Aquí vive la EC2. - Subred privada:
10.0.2.0/24, sin acceso directo desde internet. Aquí vive la RDS. - Internet Gateway (IGW): la puerta de entrada/salida para la subred pública.
- NAT Gateway: permite que la subred privada salga a internet (para actualizaciones, por ejemplo) sin que nadie desde afuera pueda entrar.
- Security Groups: el firewall a nivel de instancia para la EC2 y para la RDS.
Paso 1: crear la VPC y las subredes
Lo primero es el terreno. En la consola de AWS, dentro de VPC, el asistente “VPC and more” (“VPC y más”) permite crear en un solo flujo la VPC, las subredes, el Internet Gateway y las tablas de ruteo básicas, evitando errores manuales de configuración.
Creación de la VPC con el bloque CIDR 10.0.0.0/16.
Subred pública 10.0.1.0/24 y subred privada 10.0.2.0/24, ambas en la misma zona de disponibilidad.
Nota importante: ambas subredes deben crearse en la misma zona de disponibilidad (AZ) para replicar exactamente el diagrama inicial — aunque, como vas a ver más adelante, esto va a generar un problema específico cuando lleguemos a RDS.
Paso 2: Internet Gateway y tabla de ruteo pública
El Internet Gateway (IGW) es lo que le da a la subred pública una salida real al mundo exterior. Una vez creado y adjuntado a la VPC, hay que decirle a la subred pública que lo use:
- Destino:
0.0.0.0/0 - Target: el Internet Gateway (
igw-xxxxxxxx)
Tabla de ruteo pública asociada a la subred 10.0.1.0/24, con la ruta 0.0.0.0/0 → IGW.
Sin esta ruta explícita, no importa que llames “pública” a la subred: técnicamente seguirá aislada.
Paso 3: NAT Gateway para la subred privada
Este es el paso donde más dudas suelen aparecer, porque el NAT Gateway le da salida a la subred privada, pero físicamente vive en la subred pública.
Configuración del NAT Gateway: subred pública, tipo de conectividad “Public”, e IP elástica asignada.
Los campos clave al crearlo:
| Campo | Valor | Por qué |
|---|---|---|
| Subnet | La subred pública | El NAT necesita hablar directo con el IGW |
| Connectivity type | Public | Va a comunicarse con internet público |
| Elastic IP | Asignar una nueva | Le da al NAT una IP fija para “dar la cara” hacia afuera |
| Primary private IPv4 address | En blanco | AWS asigna una IP privada interna automáticamente; lo único que importa es la IP elástica pública |
Después de crear el NAT Gateway, el paso que es fácil olvidar: editar la tabla de ruteo privada y agregar la ruta 0.0.0.0/0 apuntando al NAT Gateway recién creado. Sin este paso, el NAT existe pero nadie lo está usando.
Tabla de ruteo privada con 0.0.0.0/0 apuntando al NAT Gateway.
Paso 4: Security Groups, el firewall de cada capa
Antes de lanzar cualquier instancia, se definen sus reglas de acceso:
- Security Group de la EC2: permite entrante en el puerto 80/443 (HTTP/HTTPS) desde
0.0.0.0/0. - Security Group de la RDS: permite entrante en el puerto 5432 (PostgreSQL) únicamente con origen el Security Group de la EC2 — nunca abierto a internet.
Security Group de la EC2 (izquierda) y de la RDS (derecha), con el segundo restringido al primero como origen.
Paso 5: lanzar la EC2 en la subred pública
Al lanzar la instancia, dos detalles no son opcionales:
- Elegir la subred pública.
- Habilitar “Asignar automáticamente IP pública” — sin esto, la instancia queda en la subred correcta pero sigue sin IP visible desde internet.
EC2 lanzada en la subred pública, con IP pública automática habilitada y el Security Group del paso anterior asociado.
Paso 6: el primer error real — RDS y EC2 “en VPCs distintas”
Al intentar crear la base de datos PostgreSQL, este fue el error exacto que me detuvo:
The DB instance and EC2 security group are in different VPCs.
The DB instance is in vpc-0f7d0fb137341da72 and the EC2 security
group is in vpc-09175c2e6f13c712f
El error de AWS al intentar asociar un Security Group que vive en una VPC distinta a la de la RDS.
La causa raíz: los Security Groups en AWS están atados a una VPC específica, no son globales a la cuenta. Si tu cuenta tiene una VPC por defecto además de la que creaste para este ejercicio, es muy fácil terminar seleccionando —sin darte cuenta— un Security Group que pertenece a la VPC equivocada.
La solución: crear el Security Group de la RDS explícitamente dentro de la misma VPC donde vive la EC2, y volver a intentar la creación seleccionando esa VPC de forma consistente en todo el asistente.
Paso 7: el segundo error — el DB Subnet Group necesita dos zonas
Con el Security Group corregido, apareció otro obstáculo, esta vez silencioso: el asistente de RDS exige un DB Subnet Group con subredes en al menos dos zonas de disponibilidad distintas, por diseño de alta disponibilidad de AWS.
Como en el Paso 1 ambas subredes (pública y privada) quedaron en la misma AZ para calzar con el diagrama original, no había forma de armar ese grupo sin agregar una subred más.
La solución: crear una tercera subred (10.0.3.0/24) en una zona de disponibilidad diferente a las otras dos, y asociarla a la tabla de ruteo privada — para que se comporte exactamente igual que la primera subred privada: sin acceso directo desde internet, pero con salida vía NAT Gateway.
DB Subnet Group con la subred privada original y la nueva subred privada en otra AZ.
Con esto, el panorama de subredes quedó así:
| Subred | CIDR | Zona | Tabla de ruteo |
|---|---|---|---|
| Pública | 10.0.1.0/24 | AZ-a | Pública → IGW |
| Privada 1 | 10.0.2.0/24 | AZ-a | Privada → NAT |
| Privada 2 | 10.0.3.0/24 | AZ-b | Privada → NAT |
Paso 8: lanzar RDS en la subred privada
Con el Security Group y el Subnet Group correctos, la base de datos se creó sin más fricción: subred privada, acceso público: No, motor PostgreSQL.
RDS PostgreSQL en la VPC correcta, con acceso público deshabilitado.
Paso 9: el tercer error — no me dejaba conectar por SSH
Al intentar entrar a la EC2 con el botón “Conectar” del navegador (EC2 Instance Connect), la consola devolvió:
Failed to connect to your instance
Error establishing SSH connection to your instance. Try again later.
Error de conexión SSH desde el navegador de AWS.
Las tres causas más comunes de este error, en orden de probabilidad:
- El Security Group no tiene el puerto 22 abierto. Hay que agregar una regla de entrada tipo SSH, con origen
My IP(recomendado) o0.0.0.0/0solo para pruebas rápidas. - La subred no tiene ruta real al Internet Gateway — se valida revisando que la tabla de ruteo asociada tenga la fila
0.0.0.0/0 → igw-xxxxxxxx. - La instancia no tiene IP pública asignada — se soluciona asociando una Elastic IP desde la sección correspondiente.
Cuando el navegador sigue fallando aun con los tres puntos correctos (algo común en imágenes Ubuntu muy recientes donde el agente de Instance Connect no viene preinstalado), la alternativa robusta es conectarse directo desde una terminal local con la clave .pem descargada al crear la instancia:
chmod 400 mi-clave.pem
ssh -i "mi-clave.pem" ec2-user@TU_IP_PUBLICA_EC2
Paso 10: el cuarto “error” — apt no existe
Ya dentro de la instancia por SSH, el primer intento de instalar dependencias falló:
[ec2-user@ip-10-0-1-149 ~]$ sudo apt update
sudo: apt: command not found
El prompt ec2-user ya lo delataba: la instancia corre Amazon Linux, no Ubuntu/Debian — y Amazon Linux usa dnf como gestor de paquetes, no apt:
sudo dnf update -y
sudo dnf install -y nodejs
node -v
npm -v
Paso 11: script de prueba end-to-end (EC2 → RDS)
Con Node.js instalado, un script mínimo permite validar dos cosas a la vez desde el navegador: que la EC2 responde desde internet, y que desde ahí puede alcanzar la RDS por su IP privada.
// server.js — servidor HTTP mínimo que valida EC2 <-> RDS
const http = require("http");
const { Client } = require("pg");
const dbConfig = {
host: "TU_ENDPOINT_RDS.rds.amazonaws.com",
user: "postgres",
password: "tu_password",
database: "postgres",
port: 5432,
};
async function testDatabaseConnection() {
const client = new Client(dbConfig);
try {
await client.connect();
await client.end();
return { success: true, message: "Conexión exitosa a la RDS." };
} catch (error) {
return { success: false, message: error.message };
}
}
const server = http.createServer(async (req, res) => {
res.setHeader("Content-Type", "application/json");
const dbStatus = await testDatabaseConnection();
res.writeHead(dbStatus.success ? 200 : 500);
res.end(
JSON.stringify(
{
status: "EC2 en línea (acceso público OK)",
database_connection: dbStatus,
},
null,
2,
),
);
});
server.listen(80, () => console.log("Servidor corriendo en el puerto 80"));
mkdir app-test && cd app-test
npm init -y
npm install pg
sudo node server.js
Con el puerto 80 habilitado también en el Security Group de la EC2, abrir http://TU_IP_PUBLICA_EC2 desde el navegador debería devolver el JSON de estado.
Paso 12: el quinto error — PostgreSQL rechazando la conexión
La primera ejecución del script devolvió esto en el navegador:
"database_connection": {
"success": false,
"message": "no pg_hba.conf entry for host \"10.0.1.149\", user \"postgres\", database \"postgres\", no encryption"
}
El error confirma que la red ya funciona: la EC2 sí llegó hasta la RDS. El rechazo es a nivel de PostgreSQL, no de red.
Este mensaje en realidad es una buena señal a medias: significa que no hubo timeout, o sea, la conexión de red EC2 → RDS funcionó perfecto (Security Groups y subredes correctos). El rechazo viene de PostgreSQL, que en RDS exige por defecto que las conexiones vayan cifradas:
const dbConfig = {
host: "TU_ENDPOINT_RDS.rds.amazonaws.com",
user: "postgres",
password: "tu_password",
database: "postgres",
port: 5432,
ssl: {
rejectUnauthorized: false, // acepta el certificado administrado por AWS
},
};
Con ese único bloque agregado y el servidor reiniciado, la respuesta cambió a lo esperado:
{
"status": "EC2 en línea (acceso público OK)",
"database_connection": {
"success": true,
"message": "Conexión exitosa a la RDS."
}
}
Resultado final: la arquitectura completa funcionando de punta a punta.
Tabla resumen: errores reales y su solución
| Error | Causa real | Solución |
|---|---|---|
DB instance and EC2 security group are in different VPCs | Security Group seleccionado pertenece a otra VPC (a veces la default de la cuenta) | Crear el Security Group explícitamente en la misma VPC que la RDS |
| No se puede crear el DB Subnet Group | Las subredes están todas en la misma zona de disponibilidad | Crear una subred adicional en otra AZ y asociarla a la tabla de ruteo privada |
Error establishing SSH connection (Instance Connect) | Puerto 22 cerrado, falta ruta al IGW, o no hay IP pública asignada | Revisar Security Group, tabla de ruteo pública y Elastic IP; como alternativa, conectar por terminal con .pem |
sudo: apt: command not found | La AMI es Amazon Linux, no Ubuntu | Usar dnf en vez de apt |
no pg_hba.conf entry ... no encryption | RDS exige conexión cifrada por defecto | Agregar ssl: { rejectUnauthorized: false } en la configuración del cliente |
Buenas prácticas antes de llevar esto a producción
Esta guía usa 0.0.0.0/0 para SSH y accesos rápidos porque el objetivo es validar la arquitectura sin fricción. Antes de un entorno real, conviene ajustar:
- SSH restringido a IP fija o, mejor aún, reemplazarlo por AWS Systems Manager Session Manager, que elimina la necesidad de abrir el puerto 22 por completo.
- Rotar credenciales de la base de datos fuera del script (variables de entorno o AWS Secrets Manager), nunca hardcodeadas como en el ejemplo.
- Multi-AZ real para la RDS (no solo el Subnet Group), si la disponibilidad de la base de datos es crítica para el negocio.
- HTTPS en la EC2 en vez de HTTP plano, típicamente detrás de un Application Load Balancer.
Conclusión
Lo que en el diagrama se ve como cuatro cajas y unas flechas, en la práctica implica resolver Security Groups atados a la VPC correcta, zonas de disponibilidad para el Subnet Group, permisos SSH, el gestor de paquetes correcto según la AMI, y el modo SSL que RDS exige por defecto. Ninguno de estos errores estaba “mal explicado” en la documentación — simplemente no aparecen hasta que los tocas en la práctica. Si te está pasando algo parecido montando tu propia infraestructura en AWS, lo más probable es que sea uno de los cinco errores de la tabla de arriba.
¿Tu equipo necesita ayuda diseñando o auditando una arquitectura de red en AWS? Agenda una asesoría técnica gratuita de 15 minutos y lo revisamos juntos.
¿Tienes un proyecto en mente?
Convierte tu idea en un producto real
Desarrollo web, aplicaciones a medida y consultoría tecnológica para empresas y startups. Cuéntame tu proyecto y te respondo en menos de 24 horas.