Objetivos de aprendizaje
Después de leer este artículo podrás:
- Reconocer el error ECONNREFUSED en Node.js, Python y psql
- Diagnosticar la causa con pruebas de puerto y de dirección
- Corregir un servidor detenido, un host incorrecto, un bind local y el orden de arranque
Arrancas la aplicación, intenta abrir la conexión con la base de datos y se detiene en el acto con ECONNREFUSED. Es uno de los errores más comunes de quien empieza con PostgreSQL, MySQL, MongoDB o Redis, y aparece tanto en la primera configuración local como en el primer deploy.
La buena noticia es que el error es concreto. No habla de contraseñas, permisos ni consultas: dice que, en la dirección y el puerto que usó tu código, ningún programa aceptó la conexión. Tu trabajo es averiguar por qué.
¿Cómo aparece el error?#
En Node.js, con los drivers pg, mysql2 y redis, el mensaje muestra la IP y el puerto que se intentaron:
Error: connect ECONNREFUSED 127.0.0.1:5432En el driver oficial de MongoDB, el mismo problema llega envuelto en el error de selección de servidor:
MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017Si el host es localhost, las versiones recientes de Node.js prueban IPv6 e IPv4. Cuando las dos fallan, el error se convierte en un AggregateError con el código ECONNREFUSED, y cada intento queda dentro de él:
AggregateError [ECONNREFUSED]:
connect ECONNREFUSED ::1:5432
connect ECONNREFUSED 127.0.0.1:5432En PostgreSQL, psql muestra el texto de abajo, y los drivers de Python basados en libpq (como psycopg) repiten el mismo mensaje dentro de un OperationalError:
psql: error: connection to server at "127.0.0.1", port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?La documentación de Node.js define el código así: no se pudo establecer la conexión porque la máquina de destino la rechazó activamente, lo que suele ocurrir al intentar hablar con un servicio inactivo en el host. La de PostgreSQL lo refuerza: "Connection refused" no significa que el servidor recibió tu petición y la rechazó; significa que no había ningún servidor escuchando ahí.
¿Qué diferencia a ECONNREFUSED de otros errores de conexión?#
Identificar el código correcto ahorra tiempo, porque cada uno apunta a una capa distinta:
| Error | Qué pasó | Dónde mirar |
|---|---|---|
ECONNREFUSED |
El host respondió, pero el puerto está cerrado | Base de datos detenida, puerto equivocado, bind local |
ETIMEDOUT |
Ninguna respuesta a tiempo | Firewall, red, IP inalcanzable |
ENOTFOUND |
El nombre del host no resolvió en el DNS | Nombre de host mal escrito o inexistente |
password authentication failed / Access denied |
Conectó, pero la credencial falló | Usuario y contraseña |
too many connections |
Conectó, pero se acabó el límite de conexiones | Pool y conexiones abiertas |
Si tu error es el último, el camino es otro: mira too many connections.
¿Cuáles son las causas más comunes?#
De la más a la menos frecuente:
- La base de datos no está en ejecución. El servicio no se inició, se cayó por falta de memoria o de disco, o su contenedor se detuvo.
- El host es incorrecto. El código usa
localhost, pero la base de datos está en otra máquina o en otro contenedor. Es la causa número uno de "funciona en local, falla en producción". - El puerto es incorrecto. Los puertos por defecto son 5432 (PostgreSQL), 3306 (MySQL), 27017 (MongoDB) y 6379 (Redis). Una base de datos configurada en otro puerto rechaza el puerto por defecto.
- La base de datos solo escucha en localhost. El proceso está activo, pero solo acepta conexiones de la misma máquina.
- La aplicación arrancó antes que la base de datos. En Docker Compose, o en un servidor que acaba de reiniciarse, la app intenta conectarse mientras la base de datos todavía está arrancando.
- IPv6 frente a IPv4.
localhostresuelve a::1, pero la base de datos solo escucha en127.0.0.1(o al revés).
¿Cómo diagnosticar la causa?#
Primero, confirma si el puerto está abierto, sin involucrar a tu código. En Linux y macOS, nc prueba cualquier puerto, y cada base de datos tiene su propia prueba:
nc -vz 127.0.0.1 5432
pg_isready -h 127.0.0.1 -p 5432
redis-cli -h 127.0.0.1 -p 6379 ping
mongosh "mongodb://127.0.0.1:27017" --eval "db.runCommand({ ping: 1 })"En Windows, usa PowerShell:
Test-NetConnection 127.0.0.1 -Port 5432Si el puerto está cerrado, revisa si el proceso existe y en qué dirección escucha. En Linux:
sudo ss -ltnp | grep -E '5432|3306|27017|6379'La columna de dirección local muestra el bind: 127.0.0.1:5432 solo acepta conexiones locales; 0.0.0.0:5432 o *:5432 las acepta en cualquier interfaz. Si no aparece nada, la base de datos está detenida: mira su log (journalctl -u postgresql, docker logs <contenedor>) para saber por qué se cayó.
Por último, imprime lo que tu código usa de verdad. Una variable de entorno vacía o con un valor antiguo explica muchos casos:
node -e "console.log(new URL(process.env.DATABASE_URL).host)"¿Cómo corregir cada causa?#
Base de datos detenida. Inicia el servicio (sudo systemctl start postgresql, docker start <contenedor>) y averigua por qué se detuvo. Si se quedó sin memoria o sin disco, volverá a caerse si nada cambia.
Host incorrecto en producción o en un contenedor. Dentro de un contenedor, localhost es el propio contenedor, no tu máquina. En Docker Compose, usa el nombre del servicio como host (db, por ejemplo). En producción, usa el host que te da tu proveedor de base de datos, siempre mediante una variable de entorno, nunca fijo en el código.
Bind solo en localhost. Cada base de datos tiene su propio ajuste, y todos exigen reiniciar el servicio:
- PostgreSQL:
listen_addressesvalelocalhostpor defecto. Para aceptar conexiones externas, cámbialo enpostgresql.confy permite el acceso enpg_hba.conf. - MongoDB:
mongodescucha en localhost por defecto. Cambianet.bindIpenmongod.conf. - Redis: el
redis.confpor defecto traebind 127.0.0.1 -::1y el protected mode activado. - MySQL: el valor por defecto de
bind_addresses*(todas las interfaces), pero revisa tu archivo de configuración: el paquetemysql-serverde Ubuntu, por ejemplo, definebind-address = 127.0.0.1en/etc/mysql/mysql.conf.d/mysqld.cnf.
Un ejemplo en postgresql.conf:
listen_addresses = '10.0.0.5' # IP de la interfaz privada, no '*' sin firewallAbrir la base de datos a internet sin firewall, sin una contraseña fuerte y sin TLS es un riesgo serio. Prefiere escuchar solo en la red privada y permitir únicamente las IP de tu aplicación.
La app antes que la base de datos. En Docker Compose, un depends_on simple solo espera a que el contenedor arranque, no a que esté listo. Usa un healthcheck:
services:
db:
image: postgres:18
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
app:
build: .
environment:
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
depends_on:
db:
condition: service_healthyFuera de Compose, haz que tu propio código reintente con una espera creciente. Con pg:
import pg from "pg";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function connectWithRetry(tries = 5) {
for (let i = 1; ; i++) {
const client = new pg.Client({ connectionString: process.env.DATABASE_URL });
try {
await client.connect();
return client;
} catch (err) {
if (i >= tries || err.code !== "ECONNREFUSED") throw err;
const wait = 1000 * 2 ** (i - 1); // 1 s, 2 s, 4 s, 8 s
console.error(`Base de datos no disponible (intento ${i}/${tries}), nuevo intento en ${wait} ms`);
await sleep(wait);
}
}
}
const db = await connectWithRetry();
const { rows } = await db.query("select now()");
console.log(rows[0]);
await db.end();El código solo reintenta con ECONNREFUSED: un error de contraseña o un host inexistente no se arreglan esperando. Un pg.Client que falló no se puede reutilizar, por eso el bucle crea uno nuevo en cada intento.
IPv6 frente a IPv4. Cambia localhost por 127.0.0.1 en la cadena de conexión, o haz que la base de datos escuche en las dos direcciones.
¿Cómo evitar que el error vuelva?#
- Guarda host, puerto, usuario y contraseña en una sola variable de conexión, definida en cada entorno. Mira qué es un archivo .env.
- Haz que la aplicación falle con un mensaje claro si la variable no existe, en lugar de usar
localhostpor defecto. - Configura un healthcheck de la base de datos y el reinicio automático para ella y para la app.
- Vigila la memoria y el disco del servidor de la base de datos: quedarse sin alguno de los dos es la causa más común de una caída silenciosa.
- Si prefieres no ocuparte del bind, el firewall y los reinicios, considera un hosting de base de datos separado de tu máquina.
Cómo hacerlo en Square Cloud#
Square Cloud ofrece hosting de MongoDB, PostgreSQL, MySQL y Redis a partir del plan Standard. Creas la base de datos en el panel, y se aprovisiona en segundos en el mismo datacenter que tus aplicaciones, con una latencia media de alrededor de 1 ms. El plan Hobby no incluye bases de datos.
La base de datos no corre dentro del contenedor de la aplicación: tiene su propio host y puerto, que el panel muestra junto con la URL de conexión completa. Por eso el error más común al migrar es que la app siga apuntando a localhost o al puerto por defecto del motor. Guarda la URL del panel en una variable de entorno de la aplicación, configurada en el panel, en la CLI o con la API; el cambio se aplica en el siguiente reinicio, sin rebuild.
TLS es obligatorio: descarga el certificado en el panel y apunta el driver a la CA (en PostgreSQL y MongoDB el cliente también presenta el certificado .pem; en Redis, usa rediss://). Como la base de datos acepta conexiones externas, puedes ejecutar la prueba de puerto y el cliente del motor desde tu máquina, con el mismo host, puerto y certificado, para separar un problema de red de uno de código.
Puedes tomar snapshots manuales de la base cuando quieras, y todo plan que incluye bases de datos tiene también un snapshot automático diario. Mira los detalles en hosting de bases de datos.
Hosting de bases de datos
MongoDB, PostgreSQL, MySQL y Redis junto a tu app
Preguntas frecuentes
¿ECONNREFUSED es un error de contraseña?
No. El rechazo ocurre antes de cualquier autenticación: nada escucha en ese puerto. Una contraseña incorrecta genera otro mensaje, como password authentication failed en PostgreSQL o Access denied en MySQL.
¿Por qué aparece ::1 en el error si configuré localhost?
Porque localhost puede resolver a la IPv6 ::1 y a la IPv4 127.0.0.1. Node.js prueba las dos y, si ambas fallan, agrupa los fallos en un AggregateError. Si la base de datos solo escucha en 127.0.0.1, usa esa IP en lugar de localhost.
¿Cuál es la diferencia entre ECONNREFUSED y ETIMEDOUT?
ECONNREFUSED es un rechazo inmediato: la máquina existe, pero el puerto está cerrado. ETIMEDOUT es una falta de respuesta, común cuando un firewall descarta los paquetes o el host no es alcanzable.
Funciona en mi computadora y falla en producción. ¿Por qué?
Casi siempre la variable de conexión en producción todavía apunta a localhost, o no se definió y el código usó un valor por defecto. En producción la base de datos está en otro host, que tiene que estar en la cadena de conexión.
Más en Bases de datos
Ver los 24 artículos- ¿Cuánto cuesta una base de datos en la nube?Una base de datos en la nube cuesta desde cero hasta cientos de dólares al mes. Qué define el precio: instancia, disco, tráfico, snapshots y cómo estimar el tuyo.
- Cómo hospedar una base de datos en la nubePara hospedar una base de datos, elige el motor, crea la instancia, guarda la cadena de conexión en una variable de entorno y prueba la conexión. Guía paso a paso.
- Qué es Redis y para qué sirveRedis es una base de datos en memoria que guarda pares clave-valor y estructuras de datos. Mira cómo funciona, para qué sirve y cuándo no usarlo.
- Cómo hacer un respaldo de tu base de datosRespalda MySQL, PostgreSQL y MongoDB con mysqldump, pg_dump y mongodump, y restaura el dump. Mira los comandos, cómo verificar y los errores comunes.