--- title: "Cómo asegurar MySQL y MariaDB en Ubuntu 24.04" description: "Asegura MySQL 8.0 o MariaDB en Ubuntu 24.04: instalación segura, usuarios con privilegios mínimos, acceso remoto filtrado, TLS obligatorio y auditoría." category: "Gestión de Bases de Datos" language: es source: https://cubepath.com/es/docs/gestión-de-bases-de-datos/configuracion-de-seguridad-mysql-mariadb updated: 2026-09-25 --- # Cómo asegurar MySQL y MariaDB en Ubuntu 24.04 Una instalación recién hecha de MySQL o MariaDB funciona, pero no está pensada para exponerse a una red: puede tener usuarios anónimos, una base de datos de pruebas y cuentas con más permisos de los necesarios. En este tutorial asegurarás un servidor MySQL 8.0 en Ubuntu 24.04 paso a paso: instalación segura, usuarios con privilegios mínimos, acceso remoto limitado por IP, cifrado TLS obligatorio y una auditoría rápida de cuentas. Donde MariaDB se comporta distinto, se indica. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. - Un usuario no root con privilegios `sudo`. - MySQL 8.0 (`sudo apt install mysql-server`) o MariaDB 10.11 (`sudo apt install mariadb-server`) instalado desde los repositorios de Ubuntu. - UFW activo con el acceso SSH permitido. Las rutas cambian según el servidor: | | MySQL 8.0 | MariaDB 10.11 | |---|---|---| | Configuración del servidor | `/etc/mysql/mysql.conf.d/mysqld.cnf` | `/etc/mysql/mariadb.conf.d/50-server.cnf` | | Servicio systemd | `mysql` | `mariadb` | | Cliente | `mysql` | `mariadb` (también `mysql`) | ## Paso 1: Ejecutar la instalación segura El script `mysql_secure_installation` elimina los usuarios anónimos, la base de datos `test` y el acceso remoto de root. En Ubuntu, el usuario `root` de MySQL se autentica con el plugin `auth_socket`: solo puede entrar el usuario root del sistema (o con `sudo`), sin contraseña. Es un buen comportamiento y conviene mantenerlo. Lanza el script: ```bash sudo mysql_secure_installation ``` Responde así a las preguntas: - **VALIDATE PASSWORD component**: `y`, y elige el nivel `1` (MEDIUM) o `2` (STRONG). Rechazará contraseñas débiles al crear usuarios. - **Remove anonymous users**: `y`. - **Disallow root login remotely**: `y`. - **Remove test database and access to it**: `y`. - **Reload privilege tables now**: `y`. En MariaDB el script se llama `sudo mariadb-secure-installation`. Cuando pregunte si quieres cambiar a `unix_socket` o poner contraseña a root, responde `n` a ambas: root ya usa autenticación por socket en Ubuntu. Comprueba que no quedan usuarios anónimos ni root remoto: ```bash sudo mysql -e "SELECT user, host, plugin FROM mysql.user ORDER BY user;" ``` ```output +------------------+-----------+-----------------------+ | user | host | plugin | +------------------+-----------+-----------------------+ | debian-sys-maint | localhost | caching_sha2_password | | mysql.infoschema | localhost | caching_sha2_password | | mysql.session | localhost | caching_sha2_password | | mysql.sys | localhost | caching_sha2_password | | root | localhost | auth_socket | +------------------+-----------+-----------------------+ ``` Las cuentas `mysql.*` y `debian-sys-maint` son del sistema y están bloqueadas o las usa el paquete; no las borres. ## Paso 2: Crear usuarios con privilegios mínimos Cada aplicación debe tener su propio usuario, con permisos solo sobre su base de datos y limitado al host desde el que se conecta. Nunca uses `root` ni `GRANT ALL ON *.*` para una aplicación. Entra en la consola: ```bash sudo mysql ``` Crea la base de datos y un usuario que solo pueda conectarse desde el servidor de aplicación (`app_server_ip`). Si la aplicación corre en el mismo servidor, usa `'localhost'` como host: ```sql CREATE DATABASE miapp CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; CREATE USER 'miapp_user'@'app_server_ip' IDENTIFIED BY 'tu_contraseña_segura'; GRANT SELECT, INSERT, UPDATE, DELETE ON miapp.* TO 'miapp_user'@'app_server_ip'; ``` En MariaDB usa la collation `utf8mb4_unicode_ci`, porque `utf8mb4_0900_ai_ci` es exclusiva de MySQL 8. Estos cuatro privilegios bastan para una aplicación en funcionamiento. Si tu framework ejecuta migraciones de esquema, crea un segundo usuario para ellas con `CREATE, ALTER, DROP, INDEX, REFERENCES` además de los anteriores, y úsalo solo al desplegar. Revisa los permisos concedidos: ```sql SHOW GRANTS FOR 'miapp_user'@'app_server_ip'; ``` ```output +-----------------------------------------------------------------------------------+ | Grants for miapp_user@app_server_ip | +-----------------------------------------------------------------------------------+ | GRANT USAGE ON *.* TO `miapp_user`@`app_server_ip` | | GRANT SELECT, INSERT, UPDATE, DELETE ON `miapp`.* TO `miapp_user`@`app_server_ip` | +-----------------------------------------------------------------------------------+ ``` `USAGE` significa "sin privilegios globales", que es justo lo que quieres. Si necesitas un usuario de administración con contraseña para herramientas externas, restríngelo a `localhost` y accede a través de un túnel SSH en lugar de abrir el puerto. ## Paso 3: Controlar en qué interfaz escucha el servidor En Ubuntu, MySQL y MariaDB escuchan por defecto solo en `127.0.0.1`. Si todas las aplicaciones están en el mismo servidor, déjalo así y salta al paso 5. Si la aplicación está en otro servidor, haz que MySQL escuche en la IP privada de este servidor (preferible) o en la pública. Edita la configuración: ```bash sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf ``` En la sección `[mysqld]`, ajusta `bind-address` y añade las otras dos directivas: ```ini [mysqld] bind-address = 127.0.0.1,your_private_ip mysqlx-bind-address = 127.0.0.1 local_infile = 0 skip_name_resolve = ON ``` - `bind-address` admite varias direcciones separadas por comas desde MySQL 8.0.13. En MariaDB 10.11 solo acepta una dirección: usa directamente `your_private_ip`. - `mysqlx-bind-address` mantiene el protocolo X (puerto 33060) solo en local. No existe en MariaDB; omítelo. - `local_infile = 0` impide que los clientes lean archivos locales con `LOAD DATA LOCAL`. - `skip_name_resolve` evita las búsquedas DNS inversas en cada conexión. Con él activo, los usuarios deben definirse por IP, no por nombre de host. Reinicia y comprueba en qué direcciones escucha: ```bash sudo systemctl restart mysql sudo ss -tlnp | grep mysqld ``` ```output LISTEN 0 151 your_private_ip:3306 0.0.0.0:* users:(("mysqld",pid=6120,fd=24)) LISTEN 0 151 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",pid=6120,fd=23)) LISTEN 0 70 127.0.0.1:33060 0.0.0.0:* users:(("mysqld",pid=6120,fd=21)) ``` ## Paso 4: Filtrar el puerto 3306 con UFW Abre el puerto solo para los servidores que lo necesitan: ```bash sudo ufw allow from app_server_ip to any port 3306 proto tcp sudo ufw status numbered ``` ```output Status: active To Action From -- ------ ---- [ 1] OpenSSH ALLOW IN Anywhere [ 2] 3306/tcp ALLOW IN app_server_ip ``` > Advertencia: no uses `sudo ufw allow 3306`. Los escáneres automáticos prueban contraseñas contra cualquier MySQL accesible desde Internet en cuestión de minutos. ## Paso 5: Exigir TLS en las conexiones Si hay conexiones remotas, el tráfico (incluidas las consultas y los datos) debe ir cifrado. MySQL 8.0 genera en el primer arranque un certificado autofirmado en `/var/lib/mysql` y activa TLS sin configuración adicional. Compruébalo: ```bash sudo mysql -e "SHOW VARIABLES LIKE 'have_ssl'; SHOW VARIABLES LIKE 'tls_version';" ``` ```output +---------------+-------+ | Variable_name | Value | +---------------+-------+ | have_ssl | YES | +---------------+-------+ +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | tls_version | TLSv1.2,TLSv1.3 | +---------------+-----------------+ ``` Para obligar a que todas las conexiones TCP usen TLS, añade a la sección `[mysqld]` de `mysqld.cnf`: ```ini require_secure_transport = ON ``` Las conexiones locales por socket de Unix siguen permitidas, porque no salen del servidor. También puedes exigir TLS solo a un usuario concreto: ```sql ALTER USER 'miapp_user'@'app_server_ip' REQUIRE SSL; ``` Reinicia el servicio y, desde el servidor de aplicación, conéctate y confirma el cifrado: ```bash sudo systemctl restart mysql ``` ```bash mysql -h your_private_ip -u miapp_user -p --ssl-mode=REQUIRED -e "SHOW SESSION STATUS LIKE 'Ssl_cipher';" ``` ```output +---------------+------------------------+ | Variable_name | Value | +---------------+------------------------+ | Ssl_cipher | TLS_AES_128_GCM_SHA256 | +---------------+------------------------+ ``` Si la columna `Value` sale vacía, la conexión no está cifrada. El certificado autofirmado cifra el tráfico, pero el cliente no puede verificar la identidad del servidor. Si necesitas esa verificación (`--ssl-mode=VERIFY_IDENTITY`), sustituye `ssl_ca`, `ssl_cert` y `ssl_key` por certificados emitidos por tu propia CA. En MariaDB 10.11 no se genera ningún certificado. Tienes que crear uno o usar el de tu CA, indicar las rutas con `ssl_cert`, `ssl_key` y `ssl_ca` en `50-server.cnf`, y después activar `require_secure_transport = ON`. ## Paso 6: Auditar cuentas y privilegios Repite estas consultas de vez en cuando, y siempre después de dar acceso a alguien. Entra con `sudo mysql` y ejecuta: Usuarios que pueden conectarse desde cualquier host: ```sql SELECT user, host FROM mysql.user WHERE host = '%'; ``` Usuarios con privilegios globales peligrosos: ```sql SELECT user, host FROM mysql.user WHERE Super_priv = 'Y' OR File_priv = 'Y' OR Grant_priv = 'Y' OR Process_priv = 'Y'; ``` Cuentas sin contraseña que no usan autenticación por socket: ```sql SELECT user, host, plugin FROM mysql.user WHERE authentication_string = '' AND plugin NOT IN ('auth_socket', 'unix_socket'); ``` Lo esperado es que la primera y la tercera consulta no devuelvan filas, y que la segunda solo devuelva `root@localhost` y cuentas del sistema. Elimina lo que sobre con `DROP USER 'usuario'@'host';` o quita permisos con `REVOKE`. > Nota: en MariaDB, `mysql.user` es una vista y la tercera consulta puede no ser fiable. Usa `SELECT user, host, plugin, authentication_string FROM mysql.global_priv;` o revisa cada cuenta con `SHOW CREATE USER`. ## Paso 7: Revisar el log de errores y los accesos fallidos MySQL registra en `/var/log/mysql/error.log` los errores de arranque y, con el nivel de detalle por defecto, los avisos importantes. Para ver los intentos de acceso fallidos, sube el nivel de log a 3 en `[mysqld]`: ```ini log_error_verbosity = 3 ``` Tras reiniciar, los intentos fallidos aparecen así: ```bash sudo grep 'Access denied' /var/log/mysql/error.log | tail -n 5 ``` ```output 2026-09-25T10:14:03.512345Z 12 [Note] [MY-010926] [Server] Access denied for user 'root'@'203.0.113.45' (using password: YES) ``` Si ves intentos desde IPs que no conoces, revisa las reglas de UFW del paso 4: no deberían poder llegar al puerto. No actives el `general_log` en producción para esto: registra todas las consultas, llena el disco rápidamente y puede contener datos sensibles. ## Solución de problemas **`ERROR 1045 (28000): Access denied for user 'root'@'localhost'`.** Estás entrando sin `sudo`. Con `auth_socket`, root solo funciona como `sudo mysql`. **`ERROR 3159 (HY000): Connections using insecure transport are prohibited while --require_secure_transport=ON`.** El cliente intenta conectar sin TLS. Añade `--ssl-mode=REQUIRED` o la opción equivalente de tu driver. **La aplicación no conecta tras activar `skip_name_resolve`.** El usuario está definido con un nombre de host. Créalo de nuevo con la IP de origen. **`ERROR 1819 (HY000): Your password does not satisfy the current policy requirements`.** El componente de validación de contraseñas está actuando. Usa una contraseña más larga con mayúsculas, números y símbolos. ## Conclusión Tu servidor MySQL o MariaDB ya no tiene cuentas anónimas ni base de datos de pruebas, cada aplicación usa un usuario con permisos mínimos, el puerto solo es accesible desde IPs concretas y las conexiones remotas van cifradas. Como siguientes pasos, programa copias de seguridad con `mysqldump` y prueba la restauración, ajusta el rendimiento en `my.cnf` según tu carga de trabajo, y aplica las actualizaciones de seguridad de Ubuntu con `unattended-upgrades`.