必须显式指定'localhost':create user 'u'@'localhost'是唯一正确写法,因mysql对'localhost'字面量有硬编码处理,强制走unix socket并跳过tcp/ip,而'127.0.0.1'、'%'或省略host均不等效。

创建用户时必须显式指定 'localhost' 主机名
MySQL 的用户账户由 username@host 组成,'localhost' 和 '127.0.0.1' 在 MySQL 中是**完全不同**的主机名,且行为不一致。仅用 CREATE USER 'u1'@'localhost' IDENTIFIED BY 'pwd'; 才能确保该用户只能通过 Unix socket 或本地 TCP(绑定到 127.0.0.1 且客户端显式连 localhost)登录。如果写成 'u1'@'127.0.0.1',虽然也能限制为本地 IP,但部分客户端(如某些 PHP 配置、mysql 命令行未加 --protocol=tcp)会默认走 socket,反而连不上。
常见错误现象:Access denied for user 'u1'@'localhost' 却能用 root 登录 —— 很可能是已存在同名但 host 为 '%' 或 '127.0.0.1' 的用户,优先级更高;或者客户端实际连接的是 127.0.0.1,而你只建了 'localhost' 用户。
- 始终用
SELECT User, Host FROM mysql.user;检查是否已有冲突用户 - 建用户后务必执行
FLUSH PRIVILEGES;(8.0.16+ 非必需,但低版本或不确定时建议保留) - 避免使用
GRANT ... ON *.*直接建用户,它可能隐式创建'%'用户;优先用CREATE USER+ 独立GRANT
localhost 用户无法从远程 SSH 连接的 MySQL 客户端访问
这是正常且预期的行为。只要用户 host 是 'localhost',MySQL 服务端会拒绝任何来自非本机网络接口(包括 Docker 容器、WSL、另一台机器的 SSH 隧道)的连接请求,哪怕目标 IP 是 127.0.0.1。因为 MySQL 在接受连接时,会检查 TCP 包的源 IP,而远程 SSH 端口转发过来的流量源 IP 是远程主机,不是 localhost。
如果你需要“本地开发环境可用、但禁止公网访问”,又希望支持 WSL 或容器调试,不要强行改 host,而是:
- 在 MySQL 配置中确认
bind-address = 127.0.0.1(不是0.0.0.0),防止监听外网端口 - 用
'u1'@'127.0.0.1'创建用户,并让客户端明确用-h 127.0.0.1连接(强制走 TCP) - 防火墙(如 ufw)额外限制
3306端口仅允许127.0.0.1访问
验证用户是否真的只能本地登录
别只信 SELECT 结果,要实测连接行为。最直接的方式是:
- 在本机用
mysql -u u1 -p -h localhost(应成功) - 在同一台机器上用
mysql -u u1 -p -h 127.0.0.1(若用户是'localhost',可能失败;若用户是'127.0.0.1',应成功) - 从另一台机器执行
mysql -u u1 -p -h your_server_ip(应报错Host 'x.x.x.x' is not allowed to connect) - 检查当前连接来源:
SELECT USER(), CURRENT_USER();—— 前者显示客户端声明的身份,后者才是 MySQL 实际匹配的user@host,这才是权限判定依据
注意 skip-name-resolve 对 localhost 的影响
如果 MySQL 配置启用了 skip-name-resolve = ON,它会跳过 DNS 反向解析,此时所有以 IP 地址连接的请求都不会被映射为 localhost,即使连的是 127.0.0.1。也就是说:'u1'@'localhost' 用户依然不能通过 -h 127.0.0.1 登录,除非你额外创建 'u1'@'127.0.0.1'。
这个配置常被用来提升性能或规避 DNS 故障,但它会让 host 匹配更严格。线上环境启用前,务必确认所有应用连接字符串中的 -h 参数与已建用户的 host 完全一致。
真正容易被忽略的点:MySQL 的 localhost 特殊处理只发生在服务端初始化连接阶段,且依赖于底层 socket 类型和配置组合;没有“绝对安全”的 host 值,只有“按需精确控制”的 host 列表。











