UFW 被发明出来,是为了让防火墙用三条命令就能配好,而在这一点上它做到了。问题在别处:ufw status 显示的是意图,而不是结果。一条规则可以在列表里躺着却什么也没关上——出于三种不同的原因,而这三种在真实服务器上都经常出现。
不会把自己关在门外的开头
命令的顺序很重要。先放行 SSH,然后再启用:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable
在放行 SSH 之前执行 ufw enable,会切断你自己的会话,此后就只剩服务商的控制台了。如果服务器已经配置过而你没把握,就留一个第二连接开着——它能挺过一次失败的改动。
请看详细状态;简短的那个会把默认策略藏起来:
sudo ufw status verbose
第一个错误:规则有了,端口对全世界开着
别人配置里最常见的一行:
sudo ufw allow 3306
MySQL 就是这样为了「好让我从家里连上」而被打开的——而且是向整个互联网打开。有两种正确的写法,而且两种都更好:
sudo ufw allow from 203.0.113.25 to any port 3306
或者更可靠地,干脆别让服务出去:MySQL 配置里写 bind-address = 127.0.0.1,外部访问走 SSH 隧道。防火墙是第二道防线;第一道是服务根本不在公网地址上监听。UFW 里的规则可能被误删,而 bind-address 不会自己变。
第二个错误:IPv6
像 ufw allow from 203.0.113.25 这样的规则只作用于 IPv4。如果服务器有 IPv6 地址——而多数 VPS 都有并且是启用的——那么服务通过它依然够得着。服务商给了地址,你不记得它,而扫描器记得。
请检查 v6 过滤是否根本就开着(/etc/default/ufw 里的 IPV6=yes),以及服务是否在没有必要的情况下监听在 :: 上:
ss -tulpn | grep ':::'
明确写出地址的规则,必须为协议的每个版本分别书写。
第三个错误:Docker
最让人恼火的一个。Docker 发布端口时,会把自己的规则加到 iptables 链里 UFW 所写规则的前面。结果是:用 -p 5432:5432 启动的容器从互联网就能够到,尽管 UFW 报告 Status: active 和 deny incoming 策略。防火墙没坏——它只是根本轮不到。
解药不在 UFW 里,而在端口是怎么发布的:
ports:
- "127.0.0.1:5432:5432"
绑到本地地址是最简单也最可靠的解决办法。朝外的应当只有真正服务访客的东西:通常是反向代理的 80 和 443 端口。
规则顺序
UFW 应用第一条匹配的规则然后就停下。所以加在放行之后的拒绝不会起作用:根本轮不到它。请看编号,然后插到需要的位置:
sudo ufw status numbered
sudo ufw insert 1 deny from 198.51.100.0/24
sudo ufw delete 7
规则按编号删除,但每删一条编号就会挪位——请一条一条地删,并重新读一遍列表。
从外部检查
本地命令显示的是配置成了什么。实际结果只有从另一台机器上才看得到:
nmap -Pn -p- 203.0.113.25
nc -zv 203.0.113.25 3306
任何另一台服务器或者你家里的电脑都行。这是唯一一项不会撒谎的检查,而且每次有明显的重新配置之后都值得跑一遍——尤其是在装了什么「自己会配网络」的东西之后:Docker、控制面板、VPN。
日志
默认情况下 UFW 几乎什么也不写。用这条打开它:
sudo ufw logging low
记录会进到 /var/log/ufw.log——但前提是系统里有 rsyslog。在 Debian 12 和 Ubuntu 24.04 上它可能没有,那时一切都进 systemd 的日志:
sudo journalctl -k | grep -i '\[UFW'
空的 /var/log/ufw.log 本身说明不了什么——请先看日志。
该对防火墙期待什么
UFW 关上的是本不该够得着的东西。它不检查发往已开放端口的请求内容:443 对所有人开着,而到达站点的任何东西都畅通无阻地到达。那是别的工具的活儿——应用层的 WAF、流量层的 IDS。防火墙的任务更朴素也更重要:让开放端口的清单与你所相信的相符。这份清单连同生效中的规则放在一个页面上是什么样子——见下面的演示。