为什么不该把 phpMyAdmin 直接暴露在公网
个人站长管数据库,十有八九用过 phpMyAdmin:点点鼠标就能看表、改字段、导出 SQL,比命令行友好太多。于是很多人图省事,直接把 phpMyAdmin 装到网站根目录,挂个 /pma 路径,公网随便访问。这件事的风险远超大多数人想象——phpMyAdmin 是历史上被扫描和爆破最狠的 Web 应用之一,几乎每一轮 CVE 出来,全网自动化扫描器几小时内就会覆盖一遍。一个装在公网、密码不强的 phpMyAdmin,等于把数据库的钥匙挂在门上。
正确做法不是不用它,而是让它只对你自己可见。这篇文章讲三件事:怎么用 SSH 隧道把数据库管理工具「藏」在 localhost 后面;phpMyAdmin 与更轻量的 Adminer 怎么选、怎么装;以及一套不暴露端口仍然能在任意地点管理的连接方案。
核心思路:管理入口永远只听 127.0.0.1
先建立一条心智模型。数据库管理工具(phpMyAdmin / Adminer / 桌面客户端)本质上是一个「客户端」,它需要能访问 MySQL 的 3306 端口。
如果直接在 Nginx 里给它配个公网域名,那就是「全网都能访问这个客户端」;而如果我们让它只绑在 127.0.0.1,那就只有「已经能登录这台服务器的人」才能访问它。而「已经能登录服务器」本身就是一道强得多的认证——SSH 有密钥、有 fail2ban、有二次验证。所以把管理入口退回到 localhost,再用 SSH 隧道按需打通,是安全性与便利性兼得的标准答案。
同样的逻辑适用于 MySQL 本身:bind-address = 127.0.0.1 应该永远保持,绝不要在防火墙里给 3306 开一个「临时」的公网白名单——那个白名单最后通常会变成永久。
方案一:Adminer 单文件方案(个人站首选)
Adminer 是一个 PHP 单文件数据库管理工具,只有几百 KB,把文件扔进目录就能用,不需要 Composer、不需要一堆 PHP 扩展。相比 phpMyAdmin,它暴露面小、更新简单(就是覆盖一个文件),非常适合个人站。
mkdir -p /srv/adminer
cd /srv/adminer
wget -O index.php https://github.com/vrana/adminer/releases/latest/download/adminer-4.8.1.php
# 也可以直接用官方英文版:https://www.adminer.org/latest.php
# 关键:目录权限收紧,禁止被 web 用户之外的人读
chown -R www-data:www-data /srv/adminer
chmod 750 /srv/adminer
Nginx 里配置一个只监听本机的 server 块:
server {
# 只监听 127.0.0.1,公网完全访问不到
listen 127.0.0.1:8081;
server_name _;
root /srv/adminer;
index index.php;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
include fastcgi_params;
# Adminer 若连本机 MySQL,这里必须传对 socket 或 127.0.0.1
}
}
注意 listen 127.0.0.1:8081; 这一行——只要绑定的是 127.0.0.1,即使服务器防火墙全开,外部也连不上这个端口。这是整个方案安全性的根基,比任何密码都可靠。
方案二:phpMyAdmin 如果一定要用
如果团队协作或功能需求必须用 phpMyAdmin,也可以装,但务必按下面的顺序收口。安装用系统包最省心:
apt install phpmyadmin
# 安装时会问是否用 dbconfig-common 配置数据库,个人站选 No 手动配置更可控
它默认会把配置放到 /etc/phpmyadmin/,Apache 会自动挂载。如果你用的是 Nginx,需要手动加一个只监听本机的 server 块,并把 PHP 转发到 php-fpm:
server {
listen 127.0.0.1:8082;
server_name _;
root /usr/share/phpmyadmin;
index index.php;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# 禁止访问配置目录
location ~ ^/(libraries|setup)/ { deny all; }
}
三板斧收口:一是 listen 127.0.0.1;二是删掉或禁用 setup/ 向导(跑完初始配置后直接移除,否则是个未授权写配置的入口);三是把 /usr/share/phpmyadmin 的属主设为 root,web 用户只读,防止被上传木马改写。
方案三:ssh -L 端口转发(本文重点)
服务已经只监听 127.0.0.1,接下来怎么从自己电脑访问?答案是 SSH 本地端口转发。原理很简单:SSH 在你本机和服务器之间建一条加密隧道,把「你本机某个端口」映射到「服务器上的某个地址端口」。
最基本的命令:
# 把本机 8081 映射到服务器的 127.0.0.1:8081
ssh -N -L 8081:127.0.0.1:8081 alice@your-server.com
参数说明:-N 表示只建隧道不执行远程命令;-L 本地端口:目标地址:目标端口,注意目标地址是从服务器视角解析的——写 127.0.0.1 指的是服务器自己的本机。执行后挂住不退出,然后浏览器打开 http://127.0.0.1:8081,看到的其实是服务器上的 Adminer。
更常见的场景是直接连数据库端口,配合桌面客户端(DBeaver、HeidiSQL、Navicat)使用:
# 把本机 13306 映射到服务器 127.0.0.1:3306
ssh -N -L 13306:127.0.0.1:3306 alice@your-server.com
# 客户端连 127.0.0.1:13306,用户名密码还是数据库自己的账号
# 好处:MySQL 从不暴露公网,中间还有一层 SSH 加密
这样配置的好处是:你在 DBeaver 里保存的连接信息永远是 127.0.0.1:13306,不用随服务器 IP 变动而改;而且3306 从始至终没有对公网开放,扫描器扫不到任何东西。DBeaver 还内置了 SSH 隧道选项,可以在连接设置里直接填 SSH 主机和密钥,效果等价,省去手动敲命令。
把隧道写进 SSH Config,一条别名搞定
每次都敲一堆参数太累,写进 ~/.ssh/config:
Host db-tunnel
HostName your-server.com
User alice
IdentityFile ~/.ssh/id_ed25519
LocalForward 13306 127.0.0.1:3306
LocalForward 8081 127.0.0.1:8081
# 断线自动保活,防止隧道静默死掉
ServerAliveInterval 30
ServerAliveCountMax 3
ExitOnForwardFailure yes
之后只需要 ssh db-tunnel 就同时打通了数据库和管理面板两个端口。ExitOnForwardFailure yes 很有用——如果本地端口被占用导致转发失败,SSH 会直接退出报错,而不是悄悄连上却不转发,让你对着一个连不上的端口排查半天。
还有一个稳定性的坑:长连接隧道经常会在网络切换(比如笔记本从 Wi-Fi 换到 4G)后静默失效。ServerAliveInterval 30 会定期发心跳,超时自动断开重连(配合 autossh 可自动重连)。如果只是临时用,手动重敲一次也行;如果要长期挂,用 autossh -M 0 -f -N db-tunnel。
反向场景:从服务器连你本机数据库
远程转发 -R 有时也有用,比如临时把服务器上跑的测试脚本连到你家本机的开发数据库,或者把内网服务的端口暴露出来做联调:
# 把服务器上的 9000 端口反向映射到你本机的 3306
ssh -N -R 9000:127.0.0.1:3306 alice@your-server.com
注意反向转发的目标地址同样是「从建立隧道的那一端(你的本机)视角」解析的。出于安全考虑,默认情况下服务器上的 9000 只绑定在服务器的 127.0.0.1,外部访问不到;如果需要让外部可达,要在服务端 sshd_config 里设 GatewayPorts yes——除非明确需要,不要开这个选项。
进阶:动态转发当一个轻量代理用
除了 -L 与 -R,SSH 还有第三种转发模式 -D,它会在本机开一个 SOCKS5 代理端口,把浏览器所有流量按需转发到服务器出口:
# 本机 1080 端口成为一个 SOCKS5 代理
ssh -N -D 1080 alice@your-server.com
浏览器设置 SOCKS5 代理为 127.0.0.1:1080 后,所有请求都会从服务器的 IP 出去。这个技巧在运维里非常实用:比如某个后台面板做了 IP 白名单,只允许服务器自己的 IP 访问,你就可以用动态转发把请求「借道」服务器;又或者要验证某个地区能否正常访问你的站点时,用它做出口对比。它比装一套代理软件轻量得多,用完关掉进程即可,不留任何持久配置。
三种转发模式可以简单记忆:-L 是「我连别人」——本机端口映射到远端;-R 是「别人连我」——远端端口映射到本机;-D 是「动态按需」——本机开 SOCKS 代理,目标由请求决定。三者都建立在那一条已经认证加密的 SSH 连接之上,这也是为什么说 SSH 是运维里「最被低估的瑞士军刀」。
SSH 隧道排错四个常见问题
- 转发失败
bind: Address already in use:本地端口被占用,换一个端口或先查lsof -i:13306。 - 连上了但浏览器打不开:多半是转发的目标地址写错。记住
-L的目标是从服务器视角解析的,写localhost可能解析成 IPv6 的::1而服务只监听 IPv4,改成显式127.0.0.1。 - 隧道时通时断:加
ServerAliveInterval;如果是云厂商的跳板,可能中间 NAT 超时,心跳间隔要小于 NAT 超时时间。 - 能连上但提示 Access denied:那是数据库自身的账号权限问题,不是隧道问题。检查该 MySQL 账号的 host 是否为
localhost,从隧道转过来的连接对 MySQL 而言源地址就是本机。
把管理面板彻底锁死的收尾清单
- MySQL 的
bind-address保持127.0.0.1,永远不要为了图方便开公网 3306。 - Adminer / phpMyAdmin 的 server 块一律
listen 127.0.0.1,公网域名解析不指向它。 - 删除
setup/目录、限制libraries/访问、web 目录属主设为 root。 - 数据库账号按最小权限建:只给需要的库、只给需要的操作,不要用 root 登录面板。
- SSH 侧用密钥登录、关掉密码认证、fail2ban 防爆破。
- 面板目录加一条 IP 白名单或 HTTP Basic 二重认证(虽然本机监听时已非必需,但多一层不亏)。
- 定期升级 Adminer/phpMyAdmin 版本,单文件方案升级成本极低,没有理由拖着。
小结
数据库管理这件事,安全性不来自「密码设得多复杂」,而来自「入口有没有暴露在公网」。把 Adminer 或 phpMyAdmin 绑到 127.0.0.1,再用 ssh -L 按需打通隧道,你就同时拿到了两种好处:操作体验和装在公网上完全一样,攻击面却缩小到接近零。个人站长的运维哲学其实就这么简单——能不暴露的端口就不要暴露,能藏在本机后面的入口就藏起来,剩下的交给 SSH 的加密与认证。