Granting privileges gets all the attention, but in my experience as a DBA, revoking them properly is where most security debt quietly accumulates. Accounts pick up permissions over time — a temporary grant during an incident that never gets rolled back, a developer given ALTER “just for this one migration” who still has it two years later. In this guide, I’ll walk through exactly how I revoke privileges in MySQL, verify the changes actually took effect, and build revocation into a routine part of database hygiene rather than something that only happens during a security incident.
Why Revocation Discipline Matters
Every privilege an account holds is part of your attack surface. If that account’s credentials are ever compromised — through a leaked .env file, a misconfigured CI pipeline, or a vulnerable dependency — the damage an attacker can do is bounded exactly by what that account can do. I treat REVOKE as just as important a tool as GRANT, not an afterthought.
graph TD
A[Account provisioned] --> B[Privileges granted for a specific need]
B --> C{Need still exists?}
C -->|Yes| D[Keep privilege, review again later]
C -->|No| E[REVOKE immediately]
E --> F[Verify with SHOW GRANTS]
Basic Revoke Syntax
REVOKE INSERT, UPDATE, DELETE ON shop_db.* FROM 'reporting_tool'@'10.0.1.%';
This removes those three privileges from the account while leaving any other grants (like SELECT) untouched. The syntax mirrors GRANT closely on purpose — I always think of REVOKE as the exact inverse operation.
Revoking at Different Scopes
Revoke Specific Privileges
REVOKE DROP, ALTER ON shop_db.* FROM 'shop_app'@'10.0.0.%';
I ran this exact command after finding an application account had picked up schema-altering privileges from an old manual grant nobody remembered adding — the app itself never needed to run ALTER or DROP statements.
Revoke All Privileges on a Specific Database
REVOKE ALL PRIVILEGES ON shop_db.* FROM 'old_service_account'@'10.0.4.%';
Revoke Absolutely Everything (Full Account Cleanup)
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'departing_employee'@'%';
This strips every privilege and the ability to grant privileges to others, but leaves the account itself intact (useful when you want to preserve the account for audit trail purposes before eventually dropping it).
Revoke Column-Level Privileges
REVOKE SELECT (payment_token) ON shop_db.users FROM 'support_agent'@'10.0.3.%';
I use this when a broader table-level SELECT grant needs to be narrowed after discovering a specific column shouldn’t have been included — a targeted fix rather than reworking the whole grant.
Revoke a Role From a User
REVOKE 'app_read_write' FROM 'shop_service'@'10.0.0.%';
This is one of the reasons I moved most permission management to roles — revoking a whole permission set is a single, auditable line instead of hunting down every individual GRANT that might have been layered on over time.
Verifying a Revoke Actually Worked
I never assume a REVOKE succeeded silently — I always check immediately after.
SHOW GRANTS FOR 'reporting_tool'@'10.0.1.%';
+--------------------------------------------------------------+
| Grants for reporting_tool@10.0.1.% |
+--------------------------------------------------------------+
| GRANT USAGE ON *.* TO `reporting_tool`@`10.0.1.%` |
| GRANT SELECT ON `shop_db`.* TO `reporting_tool`@`10.0.1.%` |
+--------------------------------------------------------------+
If the INSERT/UPDATE/DELETE privileges I revoked earlier no longer appear here, the change took effect as expected.
A Common Gotcha: Existing Sessions Don’t Update Instantly
This is something that trips up a lot of people I’ve mentored: revoking a privilege doesn’t immediately affect an already-open session’s cached privilege checks in every case — MySQL re-checks privileges per-statement for most operations, but some session-level state can persist until reconnect. I always recommend:
-- Check who's currently connected as that user
SELECT id, user, host, db, command, time, state
FROM information_schema.processlist
WHERE user = 'reporting_tool';
-- Kill the existing session if the revoke is security-critical and immediate
KILL 482;
For anything urgent — like responding to a compromised credential — I don’t just REVOKE and walk away. I also kill active sessions and rotate the password in the same action.
Emergency Revocation During a Security Incident
If I ever suspect an account has been compromised, my sequence is:
-- 1. Immediately revoke all privileges
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'compromised_user'@'%';
-- 2. Lock the account so it can't even authenticate
ALTER USER 'compromised_user'@'%' ACCOUNT LOCK;
-- 3. Kill any active sessions
SELECT id FROM information_schema.processlist WHERE user = 'compromised_user';
KILL <id>;
-- 4. Rotate the password before ever unlocking it again
ALTER USER 'compromised_user'@'%' IDENTIFIED BY 'NewComplexPass!789';
sequenceDiagram
participant DBA
participant MySQL
DBA->>MySQL: REVOKE ALL PRIVILEGES, GRANT OPTION
DBA->>MySQL: ACCOUNT LOCK
DBA->>MySQL: Query active sessions for this user
MySQL-->>DBA: List of connection IDs
DBA->>MySQL: KILL each session
DBA->>MySQL: Rotate password
Note over DBA,MySQL: Account is now fully contained
I do these in this exact order — revoking privileges first means even if killing the session lags slightly, the account can’t do further damage in the interim.
Auditing Grants Regularly (So You Know What to Revoke)
You can’t revoke what you don’t know exists. I run a quarterly review using information_schema:
SELECT grantee, privilege_type, table_schema
FROM information_schema.schema_privileges
ORDER BY grantee;
SELECT grantee, privilege_type
FROM information_schema.user_privileges
WHERE privilege_type NOT IN ('USAGE')
ORDER BY grantee;
Anything I can’t immediately justify — “why does the analytics account have DELETE?” — gets flagged for revocation or a conversation with whoever owns that service.
Security Best Practices Around Revocation
- Revoke immediately when a need ends — a temporary grant for a one-off migration should be revoked the same day the migration finishes, not “sometime later.”
- Build revocation into offboarding checklists for both human accounts and decommissioned services.
- Don’t just revoke — verify.
SHOW GRANTSafter every change, no exceptions. - Kill active sessions when a revoke is security-critical; don’t assume the next connection attempt is the only risk.
- Prefer revoking roles over untangling individual grants when the account was set up using role-based access — much less error-prone.
- Log and version-control your privilege changes where possible (I keep a changelog of
GRANT/REVOKEstatements run against production, separate from application migrations).
Real-World Scenario: Post-Incident Cleanup
After a contractor’s laptop was reported stolen, part of our incident response was auditing every database account tied to that person. We found they’d been granted SELECT, INSERT, UPDATE on the shop_db schema for a three-week project that had ended two months earlier. We revoked all privileges, locked the account, and rotated any shared credentials it might have touched — all within twenty minutes of the incident being reported, because we already had the information_schema audit query and the emergency revocation sequence documented ahead of time. Having that runbook ready, rather than improvising during the incident, made all the difference in response speed.
Troubleshooting Common Issues
| Problem | Likely Cause | Fix |
|---|---|---|
| Revoked privilege still seems active | Existing session cached state | Kill the session, force reconnect |
ERROR 1141: There is no such grant defined | Trying to revoke a privilege that was never granted at that exact scope | Check SHOW GRANTS for the exact scope the privilege exists at |
| Application breaks after a “cleanup” revoke | Removed a privilege the app actually needed | Review app query logs before revoking broadly; revoke incrementally and test |
| Role revocation doesn’t remove effective access | User has the same privilege granted directly, outside the role | Check both direct grants and role-based grants together |
Frequently Asked Questions
Does REVOKE ALL PRIVILEGES delete the user account? No — it strips privileges but the account still exists and can still authenticate (with USAGE only, meaning it can connect but do nothing). Use DROP USER separately to remove the account entirely.
Do I need to restart MySQL after running REVOKE? No, REVOKE takes effect immediately for new connections and for most privilege checks on existing ones; only certain session-cached states may require reconnect, as noted above.
Can I revoke a privilege that was granted through a role? You revoke it from the role itself (REVOKE ... FROM 'role_name'), which then affects every user assigned that role — or revoke the role from the specific user if only that user should lose access.
Is there a way to see privilege changes historically? MySQL doesn’t keep a built-in audit log of GRANT/REVOKE history by default — I enable the general query log or MySQL Enterprise Audit plugin (or a percona audit plugin) if this level of tracking is required for compliance.
Interview Questions on This Topic
- What’s the difference between
REVOKE ALL PRIVILEGESandDROP USER? - Why might a revoked privilege still appear active in an already-open session?
- What’s the correct order of operations when responding to a suspected compromised database account?
- How do you audit which accounts hold privileges they may no longer need?
- Why is revoking a role generally cleaner than untangling individually granted privileges?
Key Takeaways
- Treat
REVOKEas a routine operational tool, not just an incident-response measure. - Always verify a revoke with
SHOW GRANTS— never assume it applied as expected. - Existing sessions may need to be killed explicitly for a security-critical revoke to take full effect immediately.
- Build regular privilege audits into your operational routine so you know what needs revoking before it becomes a problem.
- Have an emergency revocation runbook ready before you need it — incident response is not the time to improvise the correct sequence of commands.
References
- MySQL 8.0 Reference Manual — REVOKE Statement: https://dev.mysql.com/doc/refman/8.0/en/revoke.html
- MySQL 8.0 Reference Manual — Privilege System: https://dev.mysql.com/doc/refman/8.0/en/privilege-system.html
- MySQL 8.0 Reference Manual — information_schema Privilege Tables: https://dev.mysql.com/doc/refman/8.0/en/information-schema-privilege-tables.html