mysql仅内网访问需bind-address与user@host权限协同配置:bind-address限定监听网卡(如192.168.1.100),user@host精确匹配来源ip(如'api'@'192.168.1.42'),并删除'%'等宽泛账号,执行flush privileges;生效。

MySQL 不能靠单设 bind-address 或单配 host 就实现“仅内网访问”,必须两者配合,且权限规则优先级比网络层更细——漏掉任一环,都会导致限制失效。
为什么 bind-address = 192.168.1.100 还是能从外网连上?
因为 bind-address 只控制 MySQL 监听哪张网卡,不控制谁有权限连。它只是第一道门,而 user@host 才是第二道锁。如果账号是 'app'@'%',哪怕你只监听内网 IP,只要外网能通到那个端口(比如防火墙开了 3306),照样能连进来。
常见错误配置:
-
bind-address = 192.168.1.100,但没删掉'app'@'%'—— 外网仍可连 -
bind-address = 127.0.0.1,却以为“绑了内网 IP”就能让其他机器连——实际只接受本机回环连接 - 云数据库(如阿里云 RDS)里改了
bind-address没用,因为底层不给你操作系统权限,得走控制台白名单
CREATE USER 时 host 写成 '192.168.1.%' 真的只限内网吗?
不是。这个写法匹配所有以 192.168.1. 开头的 IPv4 地址,但不防伪造、不防 NAT 后的地址漂移,更关键的是:它不阻止其他同名账号干扰。例如同时存在:
CREATE USER 'api'@'192.168.1.%' IDENTIFIED BY 'pwd'; CREATE USER 'api'@'%' IDENTIFIED BY 'pwd';
MySQL 会按 Host 字符串长度排序,'%' 长度短,反而排在后面;但如果你用 GRANT ... TO 'api' 漏写 @'host',MySQL 默认补成 'api'@'%',这就直接覆盖了你的精细策略。
安全做法:
- 显式写出完整 IP:
'api'@'192.168.1.42',而不是网段通配 - 确认没有残留的
'api'@'%'或'api'@'localhost'(后者可能被本地脚本误用) - 执行
SELECT User, Host FROM mysql.user WHERE User = 'api';,逐条核对
FLUSH PRIVILEGES; 不执行会怎样?
权限变更不会生效。MySQL 启动时把 mysql.user 表加载进内存缓存,后续所有权限检查都查缓存,不实时读表。所以无论你是 CREATE USER、GRANT、还是直接 UPDATE mysql.user,都必须跟一句:
FLUSH PRIVILEGES;
否则会出现“明明建了用户,客户端连提示 Access denied”的情况。特别注意:
- DROP USER 后也要
FLUSH PRIVILEGES;,否则旧缓存还在 - 某些 MySQL 版本(如 8.0.16+)对
RENAME USER自动刷缓存,但UPDATE mysql.user永远不会自动刷 - 容器环境里,如果 MySQL 是通过挂载配置文件启动的,改完 my.cnf 里的
bind-address必须重启 mysqld,不是只刷权限就行
怎么确认客户端真实 IP 被 MySQL 看到了?
别猜,直接看 SHOW PROCESSLIST; 的 Host 列。它显示的是 MySQL 实际收到连接请求时的源 IP,受以下因素影响:
- K8s Pod 经 Service 访问:看到的是 ClusterIP 或 kube-proxy 的 IP,不是 Pod IP
- SSH 隧道或 socat 转发:看到的是隧道终点 IP,不是开发机 IP
- 云厂商代理(如腾讯云 CDB):默认看不到真实客户端 IP,需开启 PROXY protocol 并在应用层解析
- 使用
localhost连接:Linux 下走 Unix socket,Host显示为localhost;用127.0.0.1连则显示为127.0.0.1,两者权限记录必须分开授权
最稳妥的验证方式:从目标机器上执行 mysql -h -u -p,再立刻在 MySQL 里跑 SHOW PROCESSLIST;,对照 Host 值是否和你授权的 host 完全一致。











