iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Users & Privileges

MySQL user management = identity (user@host) + privileges (GRANT) + authentication plugin. Get it right and your applications run with the least privilege they need; get it wrong and a leaked password gives root access to everything.

CREATE USER, GRANT, REVOKE, roles

EXAMPLE
-- 1) Create a user
CREATE USER 'app_user'@'%' IDENTIFIED BY 'strong-password';
CREATE USER 'reporting'@'10.0.0.%' IDENTIFIED BY 'pw';
CREATE USER 'admin'@'localhost' IDENTIFIED BY 'pw';

-- Format: user@host
--   localhost    → Unix socket only
--   %            → any host (be careful)
--   10.0.0.%     → CIDR-like wildcard
--   db.app.example.com  → specific host

-- The 'host' part is part of the identity; 'mara@%' and 'mara@localhost' are different users.

-- 2) Grant privileges
GRANT SELECT, INSERT, UPDATE, DELETE ON app.* TO 'app_user'@'%';
GRANT SELECT ON reporting.* TO 'reporting'@'10.0.0.%';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' WITH GRANT OPTION;

-- Privilege levels:
--   GLOBAL          *.*
--   DATABASE        db.*
--   TABLE           db.table
--   COLUMN          db.table(col)
--   STORED PROC     PROCEDURE db.proc
--   ROUTINE         FUNCTION db.fn

-- 3) Privileges quick reference
--   SELECT, INSERT, UPDATE, DELETE                 — data
--   CREATE, DROP, ALTER, INDEX                      — schema
--   REFERENCES                                       — foreign keys
--   EXECUTE                                          — call stored procedures
--   FILE                                             — read/write server files (dangerous)
--   PROCESS, RELOAD, REPLICATION CLIENT/SLAVE        — admin
--   SUPER (legacy), SESSION_VARIABLES_ADMIN          — system tweaks
--   GRANT OPTION                                     — pass on privileges

-- 4) Apply changes
FLUSH PRIVILEGES;                                    -- usually unneeded after GRANT in MySQL 5.7+; needed after manual privilege table changes

-- 5) Inspect what users can do
SHOW GRANTS FOR 'app_user'@'%';
SHOW GRANTS;                                          -- for the current user
SELECT user, host, plugin FROM mysql.user;
SELECT host, user, db, select_priv FROM mysql.db;

-- 6) Revoke
REVOKE INSERT ON app.* FROM 'app_user'@'%';
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'app_user'@'%';

-- 7) Drop a user
DROP USER 'app_user'@'%';
-- Or rename
RENAME USER 'old'@'%' TO 'new'@'%';

-- 8) Change password
ALTER USER 'app_user'@'%' IDENTIFIED BY 'new-strong-password';
-- For older MySQL:
SET PASSWORD FOR 'app_user'@'%' = PASSWORD('new-pw');

-- 9) Force password rotation
ALTER USER 'app_user'@'%' PASSWORD EXPIRE;
ALTER USER 'app_user'@'%' PASSWORD EXPIRE INTERVAL 90 DAY;
ALTER USER 'app_user'@'%' PASSWORD HISTORY 3;       -- can't reuse last 3 passwords

-- 10) Lock / unlock accounts
ALTER USER 'app_user'@'%' ACCOUNT LOCK;
ALTER USER 'app_user'@'%' ACCOUNT UNLOCK;

-- 11) Authentication plugins (MySQL 8)
CREATE USER 'modern'@'%' IDENTIFIED WITH caching_sha2_password BY 'pw';
CREATE USER 'legacy'@'%' IDENTIFIED WITH mysql_native_password BY 'pw';
CREATE USER 'sock_admin'@'localhost' IDENTIFIED WITH auth_socket;

-- caching_sha2_password is MySQL 8 default — most secure; some old clients can't connect.
-- mysql_native_password — compatible with older drivers.
-- auth_socket — passwordless via Unix peer credentials; great for local services.

-- 12) Roles (MySQL 8+) — like Postgres roles
CREATE ROLE 'app_reader', 'app_writer';
GRANT SELECT ON app.* TO 'app_reader';
GRANT INSERT, UPDATE, DELETE ON app.* TO 'app_writer';

GRANT 'app_reader', 'app_writer' TO 'app_user'@'%';

-- After granting roles, activate them per session (or always):
ALTER USER 'app_user'@'%' DEFAULT ROLE ALL;

SHOW GRANTS FOR 'app_user'@'%' USING 'app_reader', 'app_writer';

-- 13) Resource limits
CREATE USER 'analyst'@'%' IDENTIFIED BY 'pw'
    WITH MAX_QUERIES_PER_HOUR 1000
         MAX_UPDATES_PER_HOUR 100
         MAX_CONNECTIONS_PER_HOUR 50
         MAX_USER_CONNECTIONS 5;

-- 14) SSL / TLS — require encrypted connections
CREATE USER 'app_user'@'%' IDENTIFIED BY 'pw'
    REQUIRE SSL;

-- More strict — client cert auth:
CREATE USER 'svc'@'%' REQUIRE SUBJECT '/CN=svc-app'
                                   ISSUER  '/CN=Internal CA';

-- 15) Multi-factor (MySQL 8.0.27+)
ALTER USER 'mara'@'%' ADD 2 FACTOR IDENTIFIED WITH authentication_fido;

-- 16) Least privilege for app accounts — example
CREATE USER 'app_prod'@'%' IDENTIFIED BY 'pw' REQUIRE SSL
    PASSWORD EXPIRE INTERVAL 90 DAY
    PASSWORD HISTORY 5
    WITH MAX_USER_CONNECTIONS 50;

GRANT SELECT, INSERT, UPDATE, DELETE ON app.* TO 'app_prod'@'%';
GRANT EXECUTE ON PROCEDURE app.charge_payment TO 'app_prod'@'%';

REVOKE DROP, ALTER, CREATE ON app.* FROM 'app_prod'@'%';

-- App can't create or drop tables, only operate on data.

-- 17) Audit + monitoring
-- General log captures every statement (heavy; use sparingly)
SET GLOBAL general_log = 'ON';
SET GLOBAL general_log_file = '/var/log/mysql/general.log';

-- MySQL Enterprise Audit / Percona Audit Log for production-grade
-- audit_log.so plugin records who did what; ship to SIEM

-- Query analysis: performance_schema.events_statements_summary_by_user_by_event_name

-- 18) Common bugs
-- • 'app_user'@'%' + 'app_user'@'localhost' both exist → confusing precedence; remove duplicates
-- • GRANT ALL on *.* to app account → root-equivalent; use scoped GRANTS only
-- • FLUSH PRIVILEGES omitted after raw INSERT into mysql.user → not active until restart or flush
-- • caching_sha2_password unsupported by old driver → 'Authentication plugin' error; install caching_sha2 client or switch to native
-- • Passwords in DSN connection strings logged → use secret stores; mask in logs
-- • REQUIRE SSL but app connects without TLS → 'access denied'; configure TLS in driver
-- • SHOW GRANTS doesn't include role-derived; pass USING to see effective
-- • Dropping a user that's actively connected → existing sessions continue until they reconnect
-- • Wildcard host '%' on admin accounts → world-routable if listening on 0.0.0.0; firewall first
-- • Reused passwords across environments → one breach = total compromise; rotate per env

Why it matters

MySQL user identity is user@host — design hosts narrowly and grant the minimum privileges per database/table. Use roles to keep permission sets reusable, prefer caching_sha2_password, require SSL on production accounts, and rotate passwords automatically. Never GRANT ALL ON *.* to an application account.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
CREATE USER 'app'@'%' IDENTIFIED BY 's3cret';
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.* TO 'app'@'%';
FLUSH PRIVILEGES;
Try it Yourself »

Discussion

Loading…