SSD Nodes Learn
指南 Matt Connor作者: Matt Connor · 更新于 2026-07-19

UFW 与 IPv6:VPS 上敞开的那道门

您的 UFW 规则和云防火墙可能只覆盖 IPv4,导致服务在 IPv6 上完全敞开。本文讲解 VPS 上为何会这样,以及如何堵上这个缺口。

一句话说清 IPv6 防火墙陷阱

您的防火墙保护的是 IPv4。而您的 VPS 几乎肯定还有一个公网 IPv6 地址,许多服务默认就在它上面监听。如果您的防火墙只覆盖 IPv4,或者您依赖的是只过滤 IPv4 的云防火墙,那么在您的 IPv4 一侧看起来已经锁死的同时,这些服务中的每一个都能通过 IPv6 从整个互联网被访问。您用 curl 测试一个端口,看到连接被拒绝,于是觉得安全。而攻击者通过 IPv6 连到同一个端口,长驱直入。

本指南会说明这个缺口在一台普通的 Ubuntu 24.04 VPS 上来自哪里,如何准确看清您正在暴露什么,以及如何把它堵上。UFW 在这里并不是反派。在现代 Ubuntu 安装中,UFW 本身已经处理 IPv6。暴露来自它周围的各个层,也来自那些您并不知道正在监听的服务。

为什么您的 VPS 一开始就有 IPv6

如今几乎每一台 VPS 出厂时都在 IPv4 地址之外,还带有一个公网 IPv6 地址,往往是一整个 /64。查看您自己的:

ip -6 addr show scope global
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
    inet6 2001:db8:2a::1/64 scope global

那个 2001:db8:2a::1 和您的 IPv4 地址一样,可以从互联网上任何地方路由到。现在看看有什么在监听:

sudo ss -tlnp
State   Recv-Q  Local Address:Port   Process
LISTEN  0       0.0.0.0:22           sshd
LISTEN  0       [::]:22              sshd
LISTEN  0       127.0.0.1:5432       postgres
LISTEN  0       [::]:8080            docker-proxy

仔细看 Local Address 这一列。0.0.0.0:22 表示“在每一个 IPv4 地址上监听”。[::]:22 表示“在每一个 IPv6 地址上监听”。127.0.0.1:5432 绑定到回环地址,根本不对外公开,所以那条 Postgres 是安全的。两条 [::] 会通过 IPv6 向整个互联网响应,而那条 docker-proxy 正是您会忘记自己启动过的那种。

大多数守护进程开箱即绑定到 ::,因为在 Linux 上一个 :: 套接字通常也会接受 IPv4。所以一台全新服务器的默认姿态就是“在两种协议栈上、到处响应”。您的防火墙是挡在这一切前面的唯一屏障,这正是一个只看得到一种协议栈的防火墙会成为真正问题的原因。

IPv6 缺口究竟从何而来

常见来源有四个。在某一台机器上,您可能只碰到其中一个,也可能同时碰到几个。

1. 只过滤 IPv4 的云防火墙。 许多服务商的防火墙和安全组产品都是围绕 IPv4 成长起来的,它们要么忽略 IPv6,要么需要您手动添加单独的 IPv6 规则。如果您唯一的防火墙就是服务商控制台里的那一个,而它不覆盖 IPv6,那么无论它对 IPv4 上的 22 端口怎么说,您那些 [::] 服务都是敞开的。请阅读您服务商的防火墙文档,专门查找 IPv6 这个词。

2. 手写的 iptables,却没有 ip6tables。 iptables 命令只作用于 IPv4 表。IPv6 有一个完全独立的命令 ip6tables,带有它自己独立的规则。如果您写了一个满是 iptables -A INPUT ... 行的防火墙脚本,却从未写下对应的 ip6tables 规则,那么您的 IPv6 防火墙是空的,而一条默认策略为 ACCEPT 的空 INPUT 链会放行一切:

sudo ip6tables -L INPUT -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

那段输出把整个陷阱浓缩在一屏之内。IPv4 被过滤,IPv6 接受全世界。

3. Docker 直接越过您的防火墙发布端口。 当您运行 docker run -p 8080:80 时,Docker 会把自己的规则插到 UFW 的规则之前,所以即便 ufw status 说该端口被拒绝,已发布的端口仍然可以访问;在现代 Docker 上,IPv6 也是同样的情况。Docker 为何会绕过 UFW,以及如何正确过滤容器端口 解释了其机制和修复方法。关于这些已发布端口是如何声明的,请参见 VPS 上 Docker Compose 的基础

4. UFW 关闭了 IPv6。 UFW 确实处理 IPv6,但只在被告知要这么做时才会。检查这个开关:

grep IPV6 /etc/default/ufw

现代 Ubuntu 出厂即为 IPV6=yes,所以 UFW 会把每条规则同时应用到两种协议栈。如果您看到的是 IPV6=no(来自旧镜像或旧指南),那么您写过的每一条 UFW 规则都只针对 IPv4,IPv6 则处于无人管理的状态。

准确看清您正在暴露什么

不要猜。从外部测量它。先列出您的监听项,记下每一个绑定到 :: 的:

sudo ss -tlnp | grep '::'

然后,从另一台机器连到服务器的公网 IPv6 地址,尝试一个您认为已经关闭的端口:

curl -6 -v http://[2001:db8:2a::1]:8080/

如果它返回了一个页面或一段横幅信息,那么这个端口在 IPv6 上是开放的。一个关闭的端口会给您 Connection refused 或超时。要看到全貌,请在服务器之外用 nmap 扫描这个 IPv6 地址:

nmap -6 2001:db8:2a::1

nmap 报告为在 IPv6 上开放的每一个端口,都是整个互联网可以访问的端口,无论您的 IPv4 扫描显示了什么。把 IPv4 和 IPv6 的扫描结果并排比较,是找出缺口最快的方式:任何在 -6 上开放而在 IPv4 上关闭的,都是您防火墙漏掉的服务。

堵上缺口

让 UFW 覆盖两种协议栈,并默认拒绝。 确认开关,然后设置默认拒绝入站的策略,只放行您需要的:

sudo sed -i 's/^IPV6=no/IPV6=yes/' /etc/default/ufw
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verbose

如果您翻转 IPV6=yes 时 UFW 已经处于启用状态,那么这项更改要等到您运行 sudo ufw reload 才会生效。

ufw status 会把每条规则列两次,一次是普通的,一次带 (v6) 后缀。当您看到那些 (v6) 行时,UFW 就在过滤 IPv6:

22/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)

如果您手动管理 iptables,请在 ip6tables 中镜像每一条规则,或者转用 nftables,它的 inet 表在同一个地方覆盖 IPv4 和 IPv6,从而消除这一整类错误。当您自己编写规则时,单个 nftables inet filter 表是最干净的修复方式。

把您不想公开的服务绑定到回环地址。 数据库、管理面板或指标端点几乎从不需要一个公网地址。把它绑定到 127.0.0.1::1,这样它从一开始就不会在可路由的地址上监听。对于 Postgres,设置 listen_addresses = 'localhost'。对于应用服务器,把它绑定到 127.0.0.1,并在前面放一个反向代理。关掉监听胜过给它加防火墙,因为那样就无处可及了。

不要指望 UFW 来守护 Docker 已发布的端口。 把容器端口发布到一个具体地址而不是每一个接口,例如 -p 127.0.0.1:8080:80,这样该端口只能从主机以及您有意代理到它的东西访问。当一个容器确实必须对外公开时,把它放到 一个 Traefik 反向代理 后面,只发布代理,而不是每个应用。

给您的服务商防火墙添加 IPv6 规则,或者接受它不是您的 IPv6 防火墙这一事实,转而让主机上的 UFW 或 nftables 来干这份活。

验证您确实已经关闭

在更改之后,再次运行同样的外部测试:

curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1

之前作出响应的那个端口现在应该拒绝或超时,nmap 也应该把它报告为被过滤或关闭。如果某个端口仍然开放,请沿着上面那四个来源逐一回溯:一个仍绑定到 :: 且前面没有任何规则的服务、一条位于 UFW 之前的 Docker 规则,或者一个从未见过 IPv6 的服务商防火墙。

把敏感服务彻底移出公共互联网则更为稳妥。把 SSH 和管理面板放到 WireGuard VPN 后面,并给它们的端口加防火墙,让它们只在隧道上响应,这样 IPv6 暴露的问题对它们就不再适用了。要拖慢那些冲着仍然公开的部分而来的暴力扫描,请在默认拒绝的防火墙之上叠加 把 Fail2ban 挡在 SSH 前面

如果端口本身对您来说还是新概念,端口是什么,以及服务如何监听 是应当先读的入门。

FAQ

UFW 默认会拦截 IPv6 吗?

在现代 Ubuntu 24.04 安装上,会。UFW 从 /etc/default/ufw 读取 IPV6=yes,把每条规则同时应用到 IPv4 和 IPv6,ufw status 会用 (v6) 后缀显示 IPv6 规则。陷阱出现在 IPV6=no(来自旧镜像或旧教程)时,出现在您依赖只过滤 IPv4 的服务商防火墙时,或者出现在 Docker 越过 UFW 发布端口时。用 grep IPV6 /etc/default/ufw 检查这个开关。

我怎样检查 VPS 在 IPv6 上暴露了什么?

运行 sudo ss -tlnp,记下每一个本地地址以 [::] 开头的监听项,这意味着它在每一个 IPv6 接口上响应。然后,从另一台机器用 curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/ 直接测试服务器的公网 IPv6 地址,或者用 nmap -6 YOUR:IPV6::ADDR 扫描它。任何在 IPv6 扫描中开放、却在 IPv4 上关闭的端口,就是您的缺口。

为什么 UFW 说端口被拦截,我却仍能访问 Docker 容器的端口?

当您用 -p 发布端口时,Docker 会把自己的防火墙规则插到 UFW 的规则之前,所以即便 ufw status 把该端口列为拒绝,已发布的端口仍然可以访问。这在 IPv4 上会发生,在 Docker 的 IPv6 支持开启时在 IPv6 上也会发生。请发布到一个具体地址,例如 -p 127.0.0.1:8080:80,或者把容器放到反向代理后面,只发布代理。

如果我的 IPv4 防火墙很牢固,我还需要 IPv6 防火墙吗?

需要。IPv4 和 IPv6 是两套独立的网络协议栈,各有独立的防火墙规则。一套完美的 IPv4 规则对 IPv6 流量毫无作用。如果您的 VPS 有一个公网 IPv6 地址(几乎所有都有),那么任何在 :: 上监听的服务,在一条 IPv6 防火墙规则或一次回环绑定阻止它之前,都会一直可以通过 IPv6 访问。

我怎样让一个服务只监听 IPv4,或者只监听 localhost?

在服务自己的配置中设置它的绑定地址。绑定到 127.0.0.1 表示只监听 IPv4 回环,或者绑定到 0.0.0.0 表示监听全部 IPv4 地址而没有 IPv6 监听器。Postgres 用 listen_addresses,SSH 用 ListenAddress,大多数应用服务器会提供一个 host 或 bind 参数。用 sudo ss -tlnp 确认结果,检查 Local Address 不再显示 [::]