--- title: "Gestionar usuarios y permisos en MySQL y MariaDB" description: "Crea usuarios en MySQL 8 y MariaDB, concede solo los privilegios necesarios con GRANT, usa roles, limita el acceso remoto y prueba los permisos en Ubuntu 24.04." category: "Gestión de Bases de Datos" language: es source: https://cubepath.com/es/docs/gestión-de-bases-de-datos/gestion-de-usuarios-y-permisos-en-mysql-mariadb updated: 2026-09-25 --- # Gestionar usuarios y permisos en MySQL y MariaDB MySQL y MariaDB controlan el acceso con cuentas formadas por un usuario y un host (`'usuario'@'host'`) y con privilegios que se conceden a nivel global, de base de datos, de tabla o de columna. Usar `root` para todo es cómodo pero peligroso: un fallo en la aplicación expone el servidor entero. En este tutorial crearás usuarios con los privilegios mínimos que necesita cada uno, los agruparás con roles, permitirás el acceso desde otra máquina de forma controlada y comprobarás que los permisos funcionan como esperas, en Ubuntu 24.04. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con MySQL 8.0 (`sudo apt install mysql-server`) o MariaDB 10.11 (`sudo apt install mariadb-server`). - Un usuario no root con privilegios `sudo`. - Una base de datos de ejemplo. En la guía se usa `appdb`; puedes crearla en el paso 1. Los comandos son iguales en los dos motores salvo donde se indica. ## Paso 1: Conectarse como administrador y revisar las cuentas En Ubuntu, la cuenta `root` de MySQL y MariaDB se autentica por socket Unix: el usuario root del sistema entra sin contraseña y nadie más puede usarla. Abre la consola: ```bash sudo mysql ``` Si todavía no tienes una base de datos de ejemplo, créala con una tabla: ```sql CREATE DATABASE appdb; CREATE TABLE appdb.clientes (id INT PRIMARY KEY AUTO_INCREMENT, nombre VARCHAR(100), email VARCHAR(255), notas_internas TEXT); ``` Lista las cuentas existentes: ```sql SELECT user, host, plugin, account_locked FROM mysql.user; ``` ```output +------------------+-----------+-----------------------+----------------+ | user | host | plugin | account_locked | +------------------+-----------+-----------------------+----------------+ | debian-sys-maint | localhost | caching_sha2_password | N | | mysql.infoschema | localhost | caching_sha2_password | Y | | mysql.session | localhost | caching_sha2_password | Y | | mysql.sys | localhost | caching_sha2_password | Y | | root | localhost | auth_socket | N | +------------------+-----------+-----------------------+----------------+ ``` Las cuentas `mysql.*` son internas y están bloqueadas; `debian-sys-maint` la usan los scripts del paquete. No las modifiques. En MariaDB, la columna `account_locked` no existe en `mysql.user`; usa `SELECT user, host, plugin FROM mysql.user;`. ## Paso 2: Crear un usuario para la aplicación Una cuenta se identifica por usuario y host. `'app'@'localhost'` solo puede conectarse desde el propio servidor, lo más seguro cuando la aplicación está en la misma máquina. Sustituye `your_strong_password` por una contraseña larga y aleatoria: ```sql CREATE USER 'app'@'localhost' IDENTIFIED BY 'your_strong_password'; ``` Concede solo lo que la aplicación necesita para trabajar con los datos de `appdb`: ```sql GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'localhost'; ``` `appdb.*` significa todas las tablas de `appdb`. Este usuario puede leer y modificar filas, pero no crear ni borrar tablas. Si la aplicación ejecuta sus propias migraciones, crea una cuenta aparte para ellas con `CREATE, ALTER, INDEX, DROP, REFERENCES` además de los anteriores, y úsala solo durante los despliegues. No hace falta ejecutar `FLUSH PRIVILEGES`: `CREATE USER` y `GRANT` aplican los cambios de inmediato. Ese comando solo es necesario si modificas las tablas de `mysql` directamente, algo que no deberías hacer. Revisa los privilegios del usuario: ```sql SHOW GRANTS FOR 'app'@'localhost'; ``` ```output +-----------------------------------------------------------------------------+ | Grants for app@localhost | +-----------------------------------------------------------------------------+ | GRANT USAGE ON *.* TO `app`@`localhost` | | GRANT SELECT, INSERT, UPDATE, DELETE ON `appdb`.* TO `app`@`localhost` | +-----------------------------------------------------------------------------+ ``` `USAGE` significa "sin privilegios": solo permite conectarse. ## Paso 3: Conceder privilegios a nivel de tabla y columna Los privilegios pueden ser más finos que una base de datos completa. Un usuario de informes que solo debe leer el nombre y el correo de los clientes, pero no las notas internas, puede recibir permiso sobre columnas concretas: ```sql CREATE USER 'informes'@'localhost' IDENTIFIED BY 'your_strong_password'; GRANT SELECT (id, nombre, email) ON appdb.clientes TO 'informes'@'localhost'; ``` Con este permiso, `SELECT nombre FROM appdb.clientes` funciona, pero `SELECT *` o `SELECT notas_internas` fallan. Para retirar un privilegio usa `REVOKE` con la misma forma que el `GRANT`. Por ejemplo, para que `app` deje de poder borrar filas: ```sql REVOKE DELETE ON appdb.* FROM 'app'@'localhost'; ``` Vuelve a concederlo si lo necesitas para el resto de la guía: ```sql GRANT DELETE ON appdb.* TO 'app'@'localhost'; ``` ## Paso 4: Agrupar privilegios con roles Cuando varias personas o servicios necesitan el mismo acceso, es más fácil definir un rol con los privilegios y asignarlo, que repetir los `GRANT` en cada cuenta. Crea un rol de solo lectura y otro de lectura y escritura: ```sql CREATE ROLE 'appdb_lectura'; CREATE ROLE 'appdb_escritura'; GRANT SELECT ON appdb.* TO 'appdb_lectura'; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'appdb_escritura'; ``` Crea un usuario para una analista y asígnale el rol de lectura: ```sql CREATE USER 'ana'@'localhost' IDENTIFIED BY 'your_strong_password'; GRANT 'appdb_lectura' TO 'ana'@'localhost'; ``` Un rol asignado no está activo hasta que se activa en la sesión. Haz que se active automáticamente al conectar. En MySQL 8.0: ```sql SET DEFAULT ROLE ALL TO 'ana'@'localhost'; ``` En MariaDB la sintaxis es distinta y solo admite un rol por defecto: ```sql SET DEFAULT ROLE 'appdb_lectura' FOR 'ana'@'localhost'; ``` Comprueba el resultado: ```sql SHOW GRANTS FOR 'ana'@'localhost'; ``` ```output +----------------------------------------------------+ | Grants for ana@localhost | +----------------------------------------------------+ | GRANT USAGE ON *.* TO `ana`@`localhost` | | GRANT `appdb_lectura`@`%` TO `ana`@`localhost` | +----------------------------------------------------+ ``` Si más adelante cambias los privilegios del rol, todos los usuarios que lo tienen reciben el cambio. ## Paso 5: Permitir el acceso desde otro servidor Si la aplicación se ejecuta en otra máquina, crea la cuenta para la IP concreta de esa máquina (o su red privada) en lugar de usar `'%'`, que acepta conexiones desde cualquier dirección. En este ejemplo, el servidor de aplicación tiene la IP privada `10.0.0.5`: ```sql CREATE USER 'app'@'10.0.0.5' IDENTIFIED BY 'your_strong_password' REQUIRE SSL; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.0.0.5'; EXIT; ``` `'app'@'localhost'` y `'app'@'10.0.0.5'` son cuentas distintas, con su propia contraseña y privilegios. `REQUIRE SSL` obliga a cifrar la conexión; MySQL 8.0 genera certificados al instalarse. En MariaDB 10.11 tendrás que configurar `ssl_cert` y `ssl_key` antes de exigir SSL, o quitar `REQUIRE SSL` si la conexión va por una red privada de confianza. Por defecto, el servidor solo escucha en `127.0.0.1`. Cambia `bind-address` a la IP privada del servidor de base de datos (en el ejemplo, `10.0.0.2`). En MySQL el archivo es `/etc/mysql/mysql.conf.d/mysqld.cnf`; en MariaDB, `/etc/mysql/mariadb.conf.d/50-server.cnf`: ```bash sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf ``` ```ini [mysqld] bind-address = 10.0.0.2 ``` Reinicia el servicio (`mysql` o `mariadb`) y abre el puerto solo para el servidor de aplicación: ```bash sudo systemctl restart mysql sudo ufw allow from 10.0.0.5 to any port 3306 proto tcp ``` Desde `10.0.0.5`, comprueba la conexión: ```bash mysql -h 10.0.0.2 -u app -p appdb -e "SELECT CURRENT_USER();" ``` ```output +----------------+ | CURRENT_USER() | +----------------+ | app@10.0.0.5 | +----------------+ ``` ## Paso 6: Aplicar límites y bloquear cuentas Puedes limitar las conexiones simultáneas de una cuenta para que una aplicación con fugas de conexiones no agote las del servidor: ```sql ALTER USER 'app'@'localhost' WITH MAX_USER_CONNECTIONS 50; ``` Para cambiar una contraseña: ```sql ALTER USER 'ana'@'localhost' IDENTIFIED BY 'your_new_strong_password'; ``` Si una persona deja el equipo, bloquea su cuenta sin borrarla (útil si tiene objetos o quieres auditar) o elimínala directamente: ```sql ALTER USER 'ana'@'localhost' ACCOUNT LOCK; DROP USER 'informes'@'localhost'; ``` Un intento de conexión con una cuenta bloqueada devuelve `ERROR 3118 (HY000): Access denied for user 'ana'@'localhost'. Account is locked.` en MySQL. Para reactivarla, usa `ACCOUNT UNLOCK`. En MySQL 8.0 también puedes hacer que las contraseñas caduquen, lo que obliga al usuario a cambiarla al conectar: ```sql ALTER USER 'ana'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY; ``` No lo apliques a cuentas de aplicaciones: una contraseña caducada dejaría la aplicación sin acceso. ## Paso 7: Comprobar que los permisos funcionan No te fíes solo de `SHOW GRANTS`: conéctate como el usuario y prueba lo que debe y lo que no debe poder hacer. Como `'app'@'localhost'`: ```bash mysql -u app -p appdb ``` ```sql INSERT INTO clientes (nombre, email) VALUES ('Prueba', 'prueba@example.com'); SELECT id, nombre FROM clientes; DROP TABLE clientes; ``` ```output Query OK, 1 row affected (0.01 sec) ... ERROR 1142 (42000): DROP command denied to user 'app'@'localhost' for table 'clientes' ``` El `INSERT` y el `SELECT` funcionan; el `DROP TABLE` se rechaza, como debe. Si haces la prueba como `ana` (rol de solo lectura), el `INSERT` fallará con `INSERT command denied`. Para revisar de un vistazo quién tiene privilegios sobre `appdb`, consulta como administrador: ```sql SELECT grantee, privilege_type FROM information_schema.schema_privileges WHERE table_schema = 'appdb'; ``` ## Solución de problemas **`ERROR 1045 (28000): Access denied for user 'app'@'localhost' (using password: YES)`**: la contraseña no coincide o la cuenta existe con otro host. Comprueba las cuentas con `SELECT user, host FROM mysql.user WHERE user = 'app';` y restablece la contraseña con `ALTER USER`. **`ERROR 1130 (HY000): Host '10.0.0.5' is not allowed to connect to this MySQL server`**: no existe ninguna cuenta con ese usuario para esa IP. Créala con el host correcto, como en el paso 5. **`ERROR 2003 (HY000): Can't connect to MySQL server on '10.0.0.2:3306'`**: el servidor no escucha en esa IP o el cortafuegos bloquea el puerto. Revisa `bind-address`, ejecuta `sudo ss -tlnp | grep 3306` y la regla de UFW. **Un usuario con un rol asignado recibe `command denied`**: el rol no está activo en su sesión. Comprueba con `SELECT CURRENT_ROLE();` y configura el rol por defecto como en el paso 4. ## Conclusión Tus aplicaciones y usuarios usan ahora cuentas separadas con los privilegios mínimos, agrupados en roles, con acceso remoto limitado a IP concretas y comprobado con pruebas reales. Como siguientes pasos, puedes activar el componente `validate_password` de MySQL para exigir contraseñas robustas, revisar periódicamente las cuentas con `SHOW GRANTS` y retirar las que ya no se usan.