Database audit fix

How to Disable Remote Root Login in MySQL and MariaDB

MySQL grants are per user and host, so a root row for any host other than localhost, 127.0.0.1 or ::1 lets root authenticate over the network. Drop those rows and keep administrative access on the local socket.

Hiren KalariyaLast reviewed: Aug 30, 2026Check mysql-remote-root
High
severity
Yes
needs root or sudo
3
results it can return
4 of 8
checks in this audit

Every threshold on this page is transcribed from the database-security-authentication audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 8 Authentication checks, or how the audit runs.

High severityNeeds sudo

What this check reads

Runs SELECT host FROM mysql.user WHERE user='root' AND host NOT IN ('localhost','127.0.0.1','::1') over the privileged socket connection and joins the matches with GROUP_CONCAT. Any other host value (including %, a subnet, or a specific IP) means root can authenticate over the network. Empty output, or the literal string NULL, is the PASS; a single non-local row is enough to FAIL, and every host found is named in the message.

When it applies

Runs only when MySQL/MariaDB is detected (HAS_MY=1) and reachable as root over the socket (MYOK=1); otherwise SKIP. It reads the account list, not the network configuration - a root@'%' row is reported even when a firewall or bind-address makes port 3306 unreachable, because the grant itself is the finding. Network exposure is covered separately by MySQL Network Binding and Exposed Database Ports.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
FAILroot has one or more non-local host entriesThe host values are listed, and root can authenticate from every one of them. Remote root is the first account brute-forcers try.
PASSroot is local-onlyRoot can only log in from the machine itself, whatever its password is.
SKIPNo root socket accessNothing was read. The grant table was never queried, so the host list for root is unknown.

Why it matters

OWASP: accounts should connect only from allowed hosts; administrative accounts should never be network-reachable. CIS MySQL "Ensure 'root' is restricted to localhost". Credential-stuffing bots try root first on 3306.

Why it fails, and when it is wrong

  • Docker mysql images create root@'%' when MYSQL_ROOT_HOST is not restricted. Managed installs (cPanel, Plesk) sometimes add root@<hostname>.
  • Even root@'10.0.0.5' is a finding under this check. If a remote admin host is truly required, create a dedicated admin account with a different name and TLS-required (REQUIRE SSL or REQUIRE X509) rather than exposing root.
  • Some setups rely on root@<hostname> for replication or monitoring tools. Replace with least-privilege accounts (REPLICATION SLAVE, PROCESS, etc.).

How to fix it

SELECT user,host FROM mysql.user WHERE user='root';
DROP USER 'root'@'%';
-- if remote admin is unavoidable:
CREATE USER 'dba'@'10.0.0.5' IDENTIFIED BY '...' REQUIRE SSL;
GRANT ALL ON *.* TO 'dba'@'10.0.0.5' WITH GRANT OPTION;

Verify the fix

sudo mysql -N -e "SELECT CONCAT(user,'@',host) FROM mysql.user WHERE user='root'"
# expected: only root@localhost, root@127.0.0.1 and/or root@::1

# From another host, remote root must be refused:
mysql -u root -h <server-ip> -e 'SELECT 1'
# expected: ERROR 1130 (host not allowed) or a connection failure

Debugging

MYOK=0; see MySQL Anonymous Users.

There is more than one non-local row. List every one with SELECT user,host FROM mysql.user WHERE user='root'; and drop each, then FLUSH PRIVILEGES.

This check has no allow-list, so a legitimate root@'10.0.0.5' still FAILs. The intended resolution is a differently named admin account with REQUIRE SSL, after which the finding clears; otherwise record it as an accepted exception.

sudo mysql -N -e "SELECT GROUP_CONCAT(host SEPARATOR ' ') FROM mysql.user WHERE user='root' AND host NOT IN ('localhost','127.0.0.1','::1')". Empty or NULL output is the PASS condition.

Sources

How the script reads this

Next

Re-run the Authentication audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 8 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 8 Authentication fixes
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals