ERROR 2003: Can't connect on host
El ERROR 2003 aparece cuando el cliente intenta conectarse a un servidor MySQL a través de la red (TCP/IP) y no consigue abrir la conexión. Es el error típico al conectarse a un servidor remoto por primera vez, al desplegar una aplicación en una máquina distinta de la base de datos o al trabajar con contenedores. Igual que ocurre con otros errores de conexión, aquí todavía no se ha comprobado ni el usuario ni la contraseña: el problema está antes, en el camino de red entre el cliente y el servidor. Por eso la solución casi nunca pasa por tocar permisos de MySQL, sino por revisar si el servidor escucha, en qué dirección y puerto lo hace, y si algo en medio bloquea el tráfico.
El mensaje de error
El mensaje que muestra el cliente mysql incluye la dirección y el puerto a los que ha intentado conectarse, y termina con un número entre paréntesis que es la clave para el diagnóstico:
ERROR 2003 (HY000): Can't connect to MySQL server on '192.168.1.50:3306' (111)
Ese número final es el código de error del sistema operativo que devolvió la llamada de red. Aunque el texto principal es siempre el mismo, cada código apunta a una causa distinta, así que merece la pena fijarse en él antes de hacer nada más.
El código 111 significa "Connection refused". La máquina de destino existe y ha respondido, pero ha rechazado la conexión porque ningún proceso escucha en ese puerto y esa dirección. Suele indicar que MySQL está parado, que escucha solo en 127.0.0.1 o que usa otro puerto.
El código 110 significa "Connection timed out". El cliente ha enviado la petición y nadie ha contestado en el tiempo de espera. Es la firma típica de un firewall que descarta los paquetes en silencio, o de una dirección IP que no corresponde a ninguna máquina encendida.
El código 113 significa "No route to host". El sistema no sabe cómo llegar a esa dirección, o un firewall responde activamente indicando que el destino no es accesible. Aparece con frecuencia con firewalld en distribuciones como Rocky Linux o AlmaLinux.
En Windows los códigos son distintos porque vienen de la pila de red de Windows. El 10061 equivale al 111 (conexión rechazada) y el 10060 equivale al 110 (tiempo de espera agotado):
ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost:3306' (10061)
Desde una aplicación el error llega con el mismo código dentro de la excepción del conector. En PHP con PDO verás algo como SQLSTATE[HY000] [2002] Connection refused, porque PDO informa del fallo de conexión de forma diferente al cliente de consola, mientras que los conectores de Node.js y Python suelen mostrar ECONNREFUSED o ETIMEDOUT junto con la dirección.
Diferencia con el ERROR 2002
Es fácil confundir este error con su vecino, el ERROR 2002, porque ambos dicen "Can't connect". La diferencia está en el tipo de conexión. El 2002 aparece cuando el cliente intenta usar el socket Unix local, algo que ocurre en Linux y macOS cuando el host es localhost o no se indica. El 2003 aparece cuando la conexión va por TCP/IP, es decir, cuando el host es una dirección IP (incluida 127.0.0.1), un nombre de dominio o cualquier máquina remota.
Esta distinción ya es una pista. Si un comando con -h localhost da 2002 y el mismo con -h 127.0.0.1 da 2003, lo más probable es que el servidor simplemente no esté en marcha, porque falla por los dos caminos. Si en cambio localhost funciona y la IP no, el servidor está vivo pero no escucha por TCP en esa dirección.
Causas más frecuentes
La causa más simple es que el servidor MySQL no esté en ejecución. Si no hay proceso mysqld, nada escucha en el puerto 3306 y la máquina responde con conexión rechazada (código 111). Esto pasa tras un reinicio si el servicio no está habilitado, o si el servidor falló al arrancar por un error de configuración.
La segunda causa, y probablemente la más habitual al conectarse desde otra máquina, es la opción bind-address. Esta variable indica en qué dirección IP escucha el servidor. El valor por defecto de MySQL 8 es *, que acepta conexiones en todas las interfaces, pero los paquetes de Debian y Ubuntu instalan un archivo de configuración con bind-address = 127.0.0.1. Con ese valor el servidor solo acepta conexiones que vienen de la propia máquina, y cualquier cliente remoto recibe un 111 aunque el servidor funcione perfectamente.
También puede ocurrir que el servidor escuche en otro puerto. Si alguien cambió la variable port en la configuración, o si tienes varias instancias en la misma máquina, conectarte al 3306 fallará. Lo mismo sucede si la aplicación tiene un puerto equivocado en su configuración.
Los firewalls son la causa típica de los códigos 110 y 113. En Ubuntu puede estar activo ufw, en RHEL y derivados firewalld, y en la nube hay además una capa externa: los grupos de seguridad de AWS, las reglas de firewall de Google Cloud o los grupos de seguridad de red de Azure. Basta con que una sola de estas capas no permita el puerto 3306 desde tu dirección para que la conexión no llegue nunca.
Otra causa menos conocida es la opción skip-networking. Cuando está activa, el servidor desactiva por completo las conexiones TCP/IP y solo acepta el socket local. Es una medida de seguridad legítima en servidores donde la aplicación corre en la misma máquina, pero produce exactamente este error en cuanto alguien intenta conectarse por red.
Con Docker hay dos escenarios clásicos. El primero es arrancar el contenedor sin publicar el puerto, de modo que MySQL escucha dentro del contenedor pero nada lo expone en el anfitrión. El segundo es usar localhost o 127.0.0.1 desde otro contenedor: dentro de un contenedor, localhost es el propio contenedor, no el de la base de datos, y la conexión se rechaza.
Por último, está el error más humano: una dirección IP o un nombre de host equivocado. Una IP que cambió con DHCP, un servidor que se migró o una errata en el archivo .env bastan para intentar conectar a una máquina que no tiene MySQL.
Diagnóstico paso a paso
Lo más eficaz es recorrer el camino de la conexión desde el servidor hacia el cliente. El primer paso, en la máquina del servidor, es comprobar que el servicio está activo. En Debian y Ubuntu el servicio se llama mysql, y en RHEL, Rocky o Fedora se llama mysqld:
sudo systemctl status mysqlSi el servicio está activo, el siguiente paso es ver en qué dirección y puerto escucha realmente. El comando ss muestra los puertos TCP en escucha y el proceso asociado:
sudo ss -ltnp | grep mysqldLISTEN 0 151 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",pid=1234,fd=23))
LISTEN 0 70 127.0.0.1:33060 0.0.0.0:* users:(("mysqld",pid=1234,fd=21))
La columna importante es la dirección local. Si aparece 127.0.0.1:3306, el servidor solo acepta conexiones locales y ahí está tu problema. Si aparece 0.0.0.0:3306 o *:3306, escucha en todas las interfaces. El puerto 33060 que puede aparecer junto a él pertenece al protocolo X de MySQL y no interviene en este error. Si no aparece ninguna línea con 3306 pero el servicio está activo, revisa si hay skip-networking o un puerto distinto.
Desde el propio servidor también puedes consultar estos valores directamente a MySQL, conectándote por el socket local:
SELECT @@bind_address, @@port, @@skip_networking;+----------------+--------+-------------------+
| @@bind_address | @@port | @@skip_networking |
+----------------+--------+-------------------+
| 127.0.0.1 | 3306 | 0 |
+----------------+--------+-------------------+
El tercer paso se hace desde la máquina cliente: comprobar si el puerto es alcanzable, independientemente de MySQL. La herramienta nc (netcat) es ideal para ello:
nc -zv 192.168.1.50 3306Si responde que la conexión ha tenido éxito, la red está bien y el problema ya no es de conectividad. Si responde "Connection refused", nada escucha en esa dirección y puerto (vuelve a revisar bind-address, el puerto y que el servicio esté activo). Si se queda esperando hasta agotar el tiempo, hay un firewall en medio que descarta los paquetes. En Windows puedes obtener la misma información desde PowerShell con Test-NetConnection 192.168.1.50 -Port 3306.
Si el servidor ni siquiera arranca, el registro de errores te dirá por qué. Está en /var/log/mysql/error.log en Debian y Ubuntu, y en /var/log/mysqld.log en RHEL y derivados:
sudo tail -n 50 /var/log/mysql/error.logSoluciones
La solución depende de lo que haya revelado el diagnóstico. Lo recomendable es ir de lo más simple a lo más complejo y probar la conexión después de cada cambio.
Arrancar el servidor
Si el servicio estaba parado, arráncalo y habilítalo para que se inicie solo en cada arranque de la máquina. Si falla al arrancar, el registro de errores indicará la opción o el archivo que lo impide:
sudo systemctl start mysql
sudo systemctl enable mysqlCambiar bind-address para aceptar conexiones remotas
Si el servidor escucha solo en 127.0.0.1 y necesitas conexiones desde otras máquinas, hay que cambiar bind-address en el archivo de configuración. En Ubuntu y Debian la línea está en /etc/mysql/mysql.conf.d/mysqld.cnf, dentro de la sección [mysqld]. Puedes indicar la IP concreta de la interfaz privada del servidor, que es lo más seguro, o 0.0.0.0 para escuchar en todas:
[mysqld]
bind-address = 192.168.1.50La variable bind_address no se puede cambiar en caliente, así que después hay que reiniciar MySQL y volver a comprobar con ss que la dirección ha cambiado:
sudo systemctl restart mysql
sudo ss -ltnp | grep 3306Desde MySQL 8.0.13, bind-address admite varias direcciones separadas por comas, lo que permite escuchar a la vez en 127.0.0.1 y en la IP privada sin abrirlo a todas las interfaces.
Abrir el puerto en el firewall
Si nc se quedaba esperando, hay que permitir el tráfico al puerto 3306. Lo prudente es abrirlo solo para la IP o la red desde la que se conecta la aplicación. Con ufw en Ubuntu:
sudo ufw allow from 192.168.1.0/24 to any port 3306 proto tcpCon firewalld en RHEL, Rocky o AlmaLinux, la forma equivalente usa una regla enriquecida limitada al origen:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="3306" protocol="tcp" accept'
sudo firewall-cmd --reloadSi el servidor está en la nube, recuerda revisar también el grupo de seguridad o la regla de firewall del proveedor. Es habitual abrir el puerto en el sistema operativo y seguir viendo el timeout porque la capa del proveedor lo sigue bloqueando.
Quitar skip-networking o corregir el puerto
Si @@skip_networking devolvía 1, elimina la línea skip-networking de la configuración y reinicia el servidor. Si el servidor usa otro puerto, tienes dos opciones: conectarte indicando ese puerto con -P, o actualizar la configuración de la aplicación para que use el correcto:
mysql -h 192.168.1.50 -P 3307 -u app_tienda -pConectar correctamente con Docker
Si MySQL corre en un contenedor y te conectas desde el anfitrión, el puerto debe estar publicado al crear el contenedor con -p. Sin esa opción, el puerto solo existe dentro de la red de Docker:
docker run -d --name mysql-tienda -p 3306:3306 -e MYSQL_ROOT_PASSWORD=ClaveSegura2026 mysql:8.4
mysql -h 127.0.0.1 -P 3306 -u root -pSi te conectas desde otro contenedor de la misma red, por ejemplo con Docker Compose, el host no es localhost sino el nombre del servicio de la base de datos (algo como DB_HOST=db en el .env de la aplicación), y el puerto es el interno del contenedor, 3306, aunque lo hayas publicado en otro puerto del anfitrión.
Crear el usuario para el host remoto
Una vez resuelto el problema de red, es muy común pasar directamente a otro error: el ERROR 1045 Access denied. Ocurre porque MySQL identifica las cuentas por la pareja usuario y host, y una cuenta creada como 'app_tienda'@'localhost' no sirve para conectarse desde 192.168.1.20. Hay que crear el usuario para el host o la red de origen y concederle los permisos necesarios:
CREATE USER 'app_tienda'@'192.168.1.%' IDENTIFIED BY 'ClaveApp#2026';
GRANT SELECT, INSERT, UPDATE, DELETE ON tienda_mysql.* TO 'app_tienda'@'192.168.1.%';Seguridad: no expongas el puerto 3306 a internet
Cuando el objetivo es simplemente conectarse desde casa o desde el portátil a un servidor remoto, la tentación es poner bind-address = 0.0.0.0 y abrir el 3306 a todo el mundo. Conviene resistirse. Un MySQL expuesto a internet recibe intentos de acceso automatizados en cuestión de minutos, y cualquier contraseña débil o vulnerabilidad sin parchear se convierte en una puerta abierta.
La alternativa más sencilla y segura es un túnel SSH. El servidor sigue escuchando solo en 127.0.0.1 y tú rediriges un puerto local de tu máquina hacia él a través de la conexión SSH, que ya está cifrada y autenticada:
ssh -L 3307:127.0.0.1:3306 usuario@servidor.ejemplo.comMientras esa sesión esté abierta, te conectas a tu propio puerto 3307 y el tráfico viaja por el túnel hasta el MySQL del servidor:
mysql -h 127.0.0.1 -P 3307 -u app_tienda -pHerramientas gráficas como MySQL Workbench, DBeaver o TablePlus tienen una opción para configurar este túnel directamente, sin escribir el comando. Para conexiones entre servidores de una misma infraestructura, lo recomendable es una red privada y reglas de firewall limitadas a las IP concretas de las aplicaciones.
Cómo evitar el error
La mejor prevención es documentar en un solo lugar cómo se conecta cada aplicación: host, puerto y tipo de conexión. Muchos 2003 aparecen porque alguien cambia el servidor, la IP o el puerto y una de las aplicaciones sigue con la configuración antigua. Usar nombres de host internos en lugar de IP fijas también ayuda, porque sobreviven a las migraciones.
Al preparar un servidor nuevo, decide desde el principio si la base de datos va a aceptar conexiones remotas. Si no las necesita, deja bind-address = 127.0.0.1 y usa túneles SSH para administrarla. Si las necesita, configura a la vez las tres piezas: bind-address, el firewall del sistema y el del proveedor, y los usuarios para los hosts de origen. Configurar solo una de ellas es precisamente lo que produce este error. Para repasar las distintas formas de conectarte desde la consola, consulta el artículo sobre cómo conectar al servidor.
Resumen
El ERROR 2003 significa que el cliente no ha podido abrir una conexión TCP/IP con el servidor MySQL. El código entre paréntesis orienta el diagnóstico: 111 o 10061 indican que nada escucha en esa dirección y puerto (servidor parado, bind-address limitado a 127.0.0.1, otro puerto o skip-networking), mientras que 110, 10060 o 113 apuntan a un firewall o a una dirección inalcanzable. Comprueba el servicio con systemctl, la escucha con ss -ltnp y la conectividad con nc -zv, y corrige la capa que falle. Recuerda que, una vez abierta la red, el usuario debe existir para el host de origen, y que un túnel SSH suele ser mejor opción que exponer el puerto 3306 a internet.
Escrito por Eduardo Lázaro
