mysql 仅通过 create user 'u'@'localhost' 实现本地 socket 连接限制,需配合 skip-networking=on 彻底禁用 tcp、确保 socket 文件存在且权限正确、客户端不显式指定 -h 127.0.0.1 或 --protocol=tcp,三者缺一不可。

MySQL 本身不提供“仅允许某一个特定 socket 路径”的用户级限制能力。所谓“通过指定 Unix Socket 连接”,本质是客户端行为 + 服务端账号匹配规则的组合结果,不是服务端对 socket 文件路径做白名单校验。
CREATE USER 'u'@'localhost' 是唯一可靠方式
MySQL 对 'localhost' 字面量有硬编码特殊处理:只要账号 host 是 'localhost',且客户端不显式指定 -h 127.0.0.1 或 --protocol=TCP,就会强制走 Unix socket(路径由服务端 socket 变量决定)。
- 必须一步创建:
CREATE USER 'app'@'localhost' IDENTIFIED BY 'pwd';,不能事后UPDATE mysql.user—— MySQL 8.0+ 禁止修改Host字段,强行改会导致账号失效 - 已有账号需先
DROP USER 'app'@'old_host';再重建,否则权限不生效 -
GRANT必须严格匹配 host:GRANT SELECT ON db.* TO 'app'@'localhost';,对'app'@'%'授权无效 - 验证连接类型:登录后执行
\s,看Connection:行是否为Localhost via UNIX socket
服务端 socket 路径必须存在且可访问
账号能走 socket 的前提是服务端 socket 文件真实存在、路径正确、权限合规。MySQL 不会自动创建缺失的 socket 文件。
- 查当前路径:
SHOW VARIABLES LIKE 'socket';,典型值如/var/run/mysqld/mysqld.sock - 确认文件存在:
ls -l /var/run/mysqld/mysqld.sock,应为srwxrwxrwx,属主mysql:mysql - 确认目录权限:
/var/run/mysqld/必须存在,属主mysql,权限至少drwxr-xr-x - 配置文件中显式设置:
[mysqld]段加socket = /var/run/mysqld/mysqld.sock,改完必须重启服务
客户端必须避免触发 TCP fallback
即使服务端和账号都配对了,客户端一个参数写错就退回 TCP,直接绕过 'localhost' 账号逻辑。
- 命令行测试时,不要加
-h 127.0.0.1;用mysql -u app -p或mysql -u app -p -h localhost才走 socket - 应用代码里别写死
host='127.0.0.1';Python/Node.js 驱动中更稳妥的是显式传unix_socket='/var/run/mysqld/mysqld.sock' - GUI 工具(如 DBeaver、Navicat)常默认把
localhost解析成127.0.0.1,此时必须手动填 socket 路径或留 host 为空(部分驱动支持) - 某些旧版客户端不支持
-S参数,只能靠 host 字符串触发 socket,所以-h localhost是最通用的兼容写法
彻底关闭 TCP 监听才能堵死所有逃逸路径
只建 'u'@'localhost' 账号还不够——如果服务端仍监听 TCP,攻击者可能用 'u'@'127.0.0.1' 或 'u'@'%' 账号连进来。必须双保险。
- 在
[mysqld]段添加:skip-networking=ON(关键!),并注释掉或删掉bind-address - MySQL 8.0+ 若启用了 X Plugin,还需加:
mysqlx=0,否则33060端口仍开放 - 重启后验证:
ss -tln | grep :3306和ss -tln | grep :33060均无输出 - 注意:
bind-address = 127.0.0.1≠ 关闭 TCP,它只是限制监听地址,仍启用 TCP 协议栈
真正难点不在配置本身,而在于「账号 host 字符串」、「客户端连接参数」、「服务端 socket 文件状态」三者必须严丝合缝。漏掉任意一环,比如目录权限不对、GUI 工具解析错、或者忘了删旧账号,都会让限制形同虚设。











