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: activedeny 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。防火墙的任务更朴素也更重要:让开放端口的清单与你所相信的相符。这份清单连同生效中的规则放在一个页面上是什么样子——见下面的演示。