MySQL User Management
Every connection to MySQL is authenticated as a specific user account. User accounts control who can connect, from which host, and what they are allowed to do. Proper user management is fundamental to database security — you should never use the root account for application connections.
The user@host Format — Deep Dive
A MySQL user account is identified by two parts: the username and the host from which connections are allowed. Together they form the account identifier 'user'@'host'. These are three completely different accounts in MySQL:
'app_user'@'localhost' -- Unix socket or TCP to 127.0.0.1 'app_user'@'127.0.0.1' -- TCP only to loopback 'app_user'@'%' -- any host (wildcard) -- When a client connects, MySQL picks the MOST SPECIFIC matching account: -- exact hostname > IP address > subnet wildcard > '%'
Host value | Meaning | Use when |
|---|---|---|
'localhost' | Unix socket connections or TCP from 127.0.0.1 only | Application on the same server as MySQL |
'127.0.0.1' | TCP loopback only — does NOT match Unix socket | Explicit TCP-only local access |
'%' | Any host — TCP from anywhere including the internet | Cloud apps, Docker, remote access (always use with firewall) |
'192.168.1.%' | Any host in that subnet | Private network app servers |
'app.example.com' | Only this specific hostname | Single known remote server |
'10.0.0.5' | Only this exact IP address | Single known remote IP |
Authentication Plugins
MySQL 8.0 changed the default authentication plugin from mysql_native_password (SHA-1 based) to caching_sha2_password (SHA-256 based). Some older client libraries may not support the new plugin:
Plugin | Default in | Security | Notes |
|---|---|---|---|
caching_sha2_password | MySQL 8.0+ | Strong (SHA-256) | Requires TLS or RSA key exchange on first connection. Cached after first auth. |
mysql_native_password | MySQL 5.x | Weaker (SHA-1) | Widely supported by older clients. Deprecated in MySQL 8.4. |
sha256_password | 5.6+ | Strong (SHA-256) | No caching — slower than caching_sha2. Rarely used directly. |
auth_socket / unix_socket | Any | OS-level | Authenticates via OS username — used for root on Linux. |
-- Create user with caching_sha2_password (MySQL 8.0 default) CREATE USER 'modern_user'@'%' IDENTIFIED WITH caching_sha2_password BY 'SecurePass123!'; -- Create user with mysql_native_password (for legacy client compatibility) CREATE USER 'legacy_app'@'%' IDENTIFIED WITH mysql_native_password BY 'LegacyPass123!'; -- Check which plugin each user is using SELECT user, host, plugin FROM mysql.user WHERE user NOT LIKE 'mysql%' AND user NOT LIKE 'sys%'; -- Switch an existing user to mysql_native_password ALTER USER 'legacy_app'@'%' IDENTIFIED WITH mysql_native_password BY 'LegacyPass123!';
IDENTIFIED WITH mysql_native_password.Creating Users
-- Basic CREATE USER CREATE USER 'alice'@'localhost' IDENTIFIED BY 'SecurePass123!'; -- Allow connections from any host CREATE USER 'app_user'@'%' IDENTIFIED BY 'AppP@ssw0rd!'; -- Create only if the account does not exist (avoids error) CREATE USER IF NOT EXISTS 'bob'@'localhost' IDENTIFIED BY 'BobsP@ss!'; -- Full options example CREATE USER 'api_user'@'%' IDENTIFIED WITH caching_sha2_password BY 'ApiP@ssword!' PASSWORD EXPIRE INTERVAL 180 DAY -- force password change every 6 months FAILED_LOGIN_ATTEMPTS 5 -- lock after 5 consecutive failures PASSWORD_LOCK_TIME 1 -- lock for 1 day after max failures COMMENT 'API service account for inventory app';
Password Policies
The validate_password component enforces password strength rules. It is installed by default in MySQL 8.0:
-- Check current password validation settings
SHOW VARIABLES LIKE 'validate_password%';
-- Typical production settings in my.cnf:
-- [mysqld]
-- validate_password.policy = STRONG
-- validate_password.length = 12
-- validate_password.mixed_case_count = 1
-- validate_password.number_count = 1
-- validate_password.special_char_count = 1
-- Test a password against the current policy (returns 0-100)
SELECT VALIDATE_PASSWORD_STRENGTH('weakpassword'); -- low score
SELECT VALIDATE_PASSWORD_STRENGTH('Str0ng@Pass!99'); -- higher scoreAccount Locking and Password Expiration
-- Lock an account (disables login without deleting the account) ALTER USER 'migrator'@'localhost' ACCOUNT LOCK; -- Unlock a locked account ALTER USER 'migrator'@'localhost' ACCOUNT UNLOCK; -- Expire password immediately (forces change on next login) ALTER USER 'alice'@'localhost' PASSWORD EXPIRE; -- Set a password expiration interval ALTER USER 'alice'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY; -- Mark password as never expiring ALTER USER 'service_account'@'%' PASSWORD EXPIRE NEVER; -- Force password change after too many failed logins ALTER USER 'alice'@'localhost' FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 2; -- lock for 2 days after 3 failures
Setting and Changing Passwords
-- Change your own password (current user)
ALTER USER USER() IDENTIFIED BY 'NewP@ssword!';
-- Change another user's password (requires CREATE USER or SYSTEM_USER privilege)
ALTER USER 'alice'@'localhost' IDENTIFIED BY 'AliceNewPass!';
-- Check password expiry and lock status
SELECT user, host, account_locked, password_expired,
password_last_changed, password_lifetime
FROM mysql.user
WHERE user = 'alice';Connection Limits Per User
Protect the server from runaway application bugs by limiting connections per user account:
-- Set resource limits on an account
ALTER USER 'webapp'@'%'
WITH MAX_CONNECTIONS_PER_HOUR 10000 -- Max new connections per hour
MAX_QUERIES_PER_HOUR 200000 -- Max queries per hour
MAX_UPDATES_PER_HOUR 50000 -- Max updates per hour
MAX_USER_CONNECTIONS 50; -- Max simultaneous connections at any time
-- Reset all limits (set to 0 = unlimited)
ALTER USER 'webapp'@'%'
WITH MAX_CONNECTIONS_PER_HOUR 0
MAX_QUERIES_PER_HOUR 0
MAX_UPDATES_PER_HOUR 0
MAX_USER_CONNECTIONS 0;Proxy Users
A proxy user lets one account authenticate as another. This is useful for connection pools where the pool connects as a shared account and then proxies to per-user accounts for privilege control:
-- Create the real account with actual privileges CREATE USER 'real_user'@'%' IDENTIFIED BY 'real_pass'; GRANT SELECT ON mydb.* TO 'real_user'@'%'; -- Create the proxy account (no password needed — uses proxy auth) CREATE USER 'proxy_user'@'%' IDENTIFIED WITH mysql_no_login; -- Grant proxy privilege GRANT PROXY ON 'real_user'@'%' TO 'proxy_user'@'%';
Viewing Users and Privileges
-- List all user accounts with key attributes
SELECT user, host, account_locked, password_expired,
plugin, password_last_changed
FROM mysql.user
ORDER BY user, host;
-- Show grants for a specific user
SHOW GRANTS FOR 'alice'@'localhost';
-- Show grants for the current user
SHOW GRANTS;
-- Detailed grants for a user
SHOW GRANTS FOR 'app_user'@'%'GRenaming and Dropping Users
-- Rename a user (privileges are transferred) RENAME USER 'old_name'@'localhost' TO 'new_name'@'localhost'; -- Drop a user (also revokes all their privileges) DROP USER 'alice'@'localhost'; -- Drop if exists (avoids error if user does not exist) DROP USER IF EXISTS 'alice'@'localhost'; -- Drop multiple users at once DROP USER IF EXISTS 'dev_user'@'localhost', 'test_user'@'%', 'old_service'@'10.0.0.5';
KILL CONNECTION thread_id to force disconnect active sessions.Practical: 3-User Web Application Setup
A secure production setup separates responsibilities across dedicated accounts:
-- 1. Application account — least privilege needed to run the app CREATE USER 'app_user'@'10.0.0.%' IDENTIFIED WITH caching_sha2_password BY 'W3bAppP@ss!' COMMENT 'Main web application service account'; GRANT SELECT, INSERT, UPDATE, DELETE ON myapp_db.* TO 'app_user'@'10.0.0.%'; -- 2. Read-only reporting / BI account CREATE USER 'readonly_user'@'10.0.0.%' IDENTIFIED BY 'R3adOnlyP@ss!' COMMENT 'Read-only access for BI tools and analysts'; GRANT SELECT ON myapp_db.* TO 'readonly_user'@'10.0.0.%'; -- 3. Backup account — minimal privileges for mysqldump CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'Backup@gent!99' COMMENT 'Database backup via mysqldump'; GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER, RELOAD ON *.* TO 'backup_user'@'localhost'; -- 4. Migration account — schema changes only during deployments CREATE USER 'migrator'@'localhost' IDENTIFIED BY 'M1grateP@ss!9' COMMENT 'Schema migration account — lock when not deploying'; GRANT CREATE, ALTER, DROP, INDEX, REFERENCES, INSERT, SELECT, UPDATE, DELETE ON myapp_db.* TO 'migrator'@'localhost'; -- Lock migration account until a deployment is in progress ALTER USER 'migrator'@'localhost' ACCOUNT LOCK; -- Apply all privilege changes immediately FLUSH PRIVILEGES;
Monitoring User Connections
-- See all active connections SHOW PROCESSLIST; -- More detail from information_schema SELECT id, user, host, db, command, time, state, info FROM information_schema.PROCESSLIST ORDER BY time DESC; -- Count active connections per user SELECT user, COUNT(*) AS connections FROM information_schema.PROCESSLIST GROUP BY user ORDER BY connections DESC; -- Kill a specific connection (use with caution) KILL CONNECTION 42;
SSL/TLS Authentication Requirements
You can require that a user connects only over an encrypted connection:
-- Require SSL for a specific user account ALTER USER 'secure_app'@'%' REQUIRE SSL; -- Require a specific cipher ALTER USER 'secure_app'@'%' REQUIRE CIPHER 'ECDHE-RSA-AES256-GCM-SHA384'; -- Require a client certificate (mutual TLS) ALTER USER 'secure_app'@'%' REQUIRE SUBJECT '/CN=app-client/O=MyOrg/C=US' AND ISSUER '/CN=MyCA/O=MyOrg/C=US'; -- Remove SSL requirement ALTER USER 'secure_app'@'%' REQUIRE NONE; -- Check if SSL is required for a user SELECT user, host, ssl_type, ssl_cipher FROM mysql.user WHERE user = 'secure_app';
Failed Login Tracking
MySQL 8.0 can automatically lock accounts after too many failed login attempts:
-- Create user with failed-login protection
CREATE USER 'alice'@'%'
IDENTIFIED BY 'SecurePass!'
FAILED_LOGIN_ATTEMPTS 5 -- lock after 5 consecutive failures
PASSWORD_LOCK_TIME 1; -- lock for 1 day
-- Manually check lock status
SELECT user, host, account_locked,
Password_reuse_history,
Password_reuse_time
FROM mysql.user WHERE user = 'alice';
-- Manually unlock after a lockout
ALTER USER 'alice'@'%' ACCOUNT UNLOCK;
-- Check authentication history (MySQL 8.0+)
SELECT user, event_time, status, ip
FROM mysql.general_log
WHERE argument LIKE '%alice%'
ORDER BY event_time DESC
LIMIT 20;Roles (MySQL 8.0+)
Roles are named collections of privileges that can be granted to users. They simplify privilege management for teams with shared access patterns:
-- Create roles CREATE ROLE 'app_read_write', 'analytics_read', 'devops_admin'; -- Grant privileges to the roles GRANT SELECT, INSERT, UPDATE, DELETE ON myapp_db.* TO 'app_read_write'; GRANT SELECT ON myapp_db.* TO 'analytics_read'; GRANT ALL PRIVILEGES ON *.* TO 'devops_admin'; -- Assign roles to users GRANT 'app_read_write' TO 'webapp'@'%'; GRANT 'analytics_read' TO 'readonly_user'@'10.0.0.%'; GRANT 'devops_admin' TO 'alice'@'localhost'; -- Set default roles (activated automatically on login) SET DEFAULT ROLE 'app_read_write' TO 'webapp'@'%'; SET DEFAULT ROLE ALL TO 'alice'@'localhost'; -- Users can also activate roles in their session SET ROLE 'analytics_read'; -- View current active roles SELECT CURRENT_ROLE(); -- Show all role grants SHOW GRANTS FOR 'alice'@'localhost' USING 'devops_admin';
Granting and Revoking Privileges
-- Grant specific privileges on a specific database GRANT SELECT, INSERT, UPDATE, DELETE ON myapp_db.* TO 'webapp'@'%'; -- Grant on a specific table only GRANT SELECT ON myapp_db.products TO 'readonly_user'@'%'; -- Grant on a specific column only (column-level privileges) GRANT SELECT (id, name, price) ON myapp_db.products TO 'price_viewer'@'%'; -- Grant all privileges on a database GRANT ALL PRIVILEGES ON myapp_db.* TO 'dba_user'@'localhost'; -- Grant global privileges (use with caution) GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'monitor_user'@'%'; -- Revoke specific privileges REVOKE DELETE ON myapp_db.* FROM 'webapp'@'%'; -- Revoke all privileges REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'old_user'@'localhost'; -- Apply changes (usually automatic but ensures they take effect) FLUSH PRIVILEGES;
Best Practices
Never use root for application connections — create a dedicated account with minimal privileges
Use the most restrictive host specification possible (specific IP or subnet over %)
Rotate service account passwords regularly using ALTER USER
Lock accounts that are only needed periodically (migration accounts, manual access accounts)
Enable and configure the validate_password component to enforce strong passwords
Audit user accounts quarterly — drop accounts that are no longer needed
Store credentials in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault) — never hardcode in source
Use caching_sha2_password (MySQL 8.0 default) for new accounts; only use mysql_native_password if your client library requires it
Use roles (MySQL 8.0+) to manage privilege sets for teams — easier to audit and update than per-user grants