服务器如何配置防火墙?Linux nftables与Windows防火墙入门

防火墙是服务器上最基础也最容易出事的一层:配对了没人注意到它的存在,配错了立刻失联。尤其是远程配置云服务器时,一条 policy drop 就可能把自己挡在门外,然后只能靠控制台的 VNC 救场。

这篇文章从 nftables 的基础概念讲起,给出真实可用的命令和持久化方法,说明 nftables 与 firewalld、ufw 的关系,重点讲"放行 SSH 前先放行自己"的风险控制和回滚手段;然后讲 Windows 侧 Defender 防火墙的入站规则配置,包括按程序还是按端口、以及如何限制来源 IP。

nftables 取代 iptables 的背景

iptables 从 2001 年起就是 Linux 防火墙的事实标准,但它有几个结构性问题:IPv4、IPv6、ARP、桥接分别要用 iptables、ip6tables、arptables、ebtables 四套工具和四套规则,配置重复且容易不一致;规则语法不统一,添加、插入、删除的写法各不相同;规则集是线性的,规模大时匹配性能下降明显。

nftables 从 Linux 内核 3.13 开始合入主线,设计目标就是解决这些问题:

  • 用统一的 inet 地址族同时处理 IPv4 和 IPv6,一套规则覆盖双栈。
  • 语法统一,规则用表达式描述,可读性和可维护性更好。
  • 引入集合(set)与映射(map),几千个 IP 的黑名单用一条规则加一个集合搞定,匹配复杂度接近常数。
  • 内核态提供原子提交,规则集要么整体生效要么整体不生效,不会出现更新到一半的中间状态。
  • 规则集可以用文件描述,便于版本管理和批量下发。

目前主流发行版的现状是:RHEL 8 及以后、Debian 10 及以后、Ubuntu 20.04 及以后都默认使用 nftables 作为后端。需要注意的是,很多系统上 iptables 命令仍然存在,但它实际上是一个兼容层,真正生效的后端是 nftables。用 iptables -L 和 nft list ruleset 对比一下就清楚了:

sudo nft list ruleset | head -40
sudo iptables -L -n -v     # 可能显示为空或由 nft 转换而来

不要混用 iptables 和 nft 命令管理同一套规则,两套工具操作的是同一个内核子系统,混用会造成规则相互覆盖、行为难以预测。选定一套坚持用。

表、链、规则三个概念

nftables 的结构是三层嵌套,理解它就理解了一大半:

表(table)是规则的容器,同时指定地址族(family)。地址族决定这个表能处理什么流量:

地址族 处理的协议 常用场景
ip IPv4 只管 IPv4
ip6 IPv6 只管 IPv6
inet IPv4 + IPv6 推荐,一套规则管双栈
arp ARP 特殊场景
bridge 桥接二层 虚拟化、容器网络
netdev 网卡入口 早期过滤,高性能场景

链(chain)挂在表下面,分两类:

基础链(base chain)挂接到内核的某个钩子点(hook),真正接收流量。常用钩子有 input(发往本机)、forward(转发)、output(本机发出)、prerouting、postrouting。创建基础链时必须指定 type、hook、priority 和 policy。

常规链(regular chain)不挂接钩子,只能被 jump 调用,用于把规则分组,类似函数。

规则(rule)是链里的具体判断,由匹配表达式和动作组成。匹配部分如 tcp dport 22、ip saddr 192.168.1.0/24、ct state established;动作部分如 accept、drop、reject、log、counter。

数据流经的顺序是:包进入网卡 → prerouting → 路由判断 → input(发往本机)或 forward(转发)→ 本机进程 → output → postrouting → 出去。规则在链内按顺序匹配,命中即执行动作并停止,所以顺序很重要。

nft 基本命令

先看现状和地址族支持:

sudo nft list ruleset              # 查看全部规则集
sudo nft list tables               # 列出所有表
sudo nft list table inet filter    # 查看指定表

创建一个表、一条基础链和几条规则:

# 创建 inet 族的表(同时管 IPv4 和 IPv6)
sudo nft add table inet filter

# 创建 input 基础链,默认策略 accept
sudo nft add chain inet filter input '{ type filter hook input priority 0; policy accept; }'

# 允许已建立的连接和相关连接(这条通常要放在前面)
sudo nft add rule inet filter input ct state established,related accept

# 允许回环接口
sudo nft add rule inet filter input iif lo accept

# 允许 SSH(端口按实际修改)
sudo nft add rule inet filter input tcp dport 22 ct state new accept

# 允许 HTTP/HTTPS
sudo nft add rule inet filter input tcp dport { 80, 443 } ct state new accept

# 允许 ICMP(便于排障,可按需限制类型)
sudo nft add rule inet filter input ip protocol icmp icmp type echo-request accept

# 记录并丢弃其余未匹配流量
sudo nft add rule inet filter input log prefix 'nft-drop: ' counter drop

几个容易踩的语法细节:

shell 转义。 规则里有大括号、分号、空格,在 bash 里会被解释。用单引号把整段规则包起来是最省事的做法。上面的 add chain 语句必须加引号,否则报语法错误。

ct state established,related 这一条非常重要。 没有它,本机主动发起的连接(比如 apt 更新、curl 外网)的返回包会被 input 链挡掉,表现为"能 ping 通但 apt 卡住"。加上它等于实现了有状态防火墙。

inet 表里的 ICMP 写法要用 ip protocol icmp 或 meta nfproto ipv4,因为 IPv6 的 ICMP 是另一个协议号。

查看规则带句柄(handle),删除和插入都要用到:

sudo nft -a list chain inet filter input     # -a 显示 handle 号
sudo nft delete rule inet filter input handle 12
sudo nft insert rule inet filter input index 0 ct state established,related accept
sudo nft add rule inet filter input index 2 tcp dport 8080 accept

add 是追加到末尾(或指定 index 之后),insert 是插入到开头(或指定 index 之前)。想让新规则先生效就用 insert。

使用集合管理 IP 列表,这是 nftables 相对 iptables 最大的优势:

# 命名集合,interval 支持网段
sudo nft add set inet filter ssh_allow '{ type ipv4_addr; flags interval; }'
sudo nft add element inet filter ssh_allow '{ 203.0.113.10, 198.51.100.0/24 }'
sudo nft add rule inet filter input tcp dport 22 ip saddr @ssh_allow accept
sudo nft list set inet filter ssh_allow

# 匿名集合,直接在规则里写
sudo nft add rule inet filter input tcp dport { 80, 443, 8080 } accept

集合里的元素可以动态增删,不需要重建规则,几千上万个 IP 的匹配性能远好于等长的线性规则链。

临时调试与清空:

sudo nft monitor              # 实时观察规则匹配情况,排障利器
sudo nft flush chain inet filter input    # 清空某条链的规则
sudo nft flush table inet filter          # 清空某张表
sudo nft flush ruleset        # 清空全部规则集(危险,等于全放行)

持久化配置

用 nft 命令加的规则在内存中,重启就没了,必须持久化。

主流发行版的做法是导出到 /etc/nftables.conf,然后启用 nftables 服务:

# 导出当前规则集(会覆盖已有文件,先备份)
sudo cp /etc/nftables.conf /etc/nftables.conf.bak 2>/dev/null
sudo nft list ruleset > /etc/nftables.conf

# RHEL、Rocky、CentOS
sudo systemctl enable --now nftables
sudo systemctl restart nftables

# Debian、Ubuntu
sudo systemctl enable --now nftables
sudo systemctl status nftables

Debian、Ubuntu 的 /etc/nftables.conf 通常自带一个基础模板,第一行是 #!/usr/sbin/nft -f,内容是完整的规则集声明格式(用 table ip filter { ... } 这样的块结构,而不是一条条 add)。推荐直接编辑这个文件的块结构写法,可读性更好:

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    set ssh_allow {
        type ipv4_addr
        flags interval
        elements = { 203.0.113.10, 198.51.100.0/24 }
    }

    chain input {
        type filter hook input priority 0; policy drop;

        ct state established,related accept
        iif lo accept
        tcp dport 22 ip saddr @ssh_allow accept
        tcp dport { 80, 443 } accept
        ip protocol icmp icmp type echo-request accept
        log prefix "nft-drop: " counter drop
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

文件开头的 flush ruleset 表示每次加载时清空旧规则再重建,好处是配置幂等,不会出现重复规则堆积;风险是如果文件有语法错误,nftables 服务启动失败,此时规则集可能是空的状态(具体行为取决于服务配置)。所以修改这个文件前先备份,改完先做语法检查:

sudo nft -c -f /etc/nftables.conf    # -c 只检查不加载

风险提示:永远不要直接 systemctl restart nftables 而不先 nft -c -f 检查。语法错误会导致规则加载失败,如果此时默认策略又没生效,你可能会在毫无察觉的情况下失去访问控制,也可能把自己关在门外。

与 firewalld、ufw 的关系

这一点很多人搞不清楚:firewalld 和 ufw 都不是独立的防火墙,它们是 nftables(或 iptables)的前端管理工具。关系类似于 apt 和 dpkg:底层是同一个内核子系统,上层是不同的操作界面。

工具 定位 默认发行版 适合场景
nft 直接操作 nftables 全部 需要精细控制、批量规则、高性能场景
firewalld 前端,zone 模型 RHEL、Rocky、CentOS RHEL 系日常管理
ufw 前端,简化语法 Ubuntu Ubuntu 简单场景、新手

firewalld 用 zone 的概念组织规则,比如 public、internal、trusted,网卡和来源可以归入不同 zone,各有不同的放行策略:

sudo firewall-cmd --state
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --permanent --remove-service=cockpit
sudo firewall-cmd --reload          # 必须 reload 才生效
sudo firewall-cmd --list-all

注意 firewalld 的 --permanent 和运行时规则是分开的,改了永久配置必须 --reload。

ufw 语法最简单,适合 Ubuntu 上手:

sudo ufw status verbose
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp
sudo ufw allow 80,443/tcp
sudo ufw delete allow 80/tcp
sudo ufw enable
sudo ufw status numbered      # 带序号,便于删除

重要原则:同一台机器上只用一个前端。 如果 firewalld 或 ufw 在运行,就不要用 nft 命令手工加规则,否则会被前端的规则重载覆盖掉,或者相互冲突。要么停掉前端用 nft 直接管理,要么用前端不管底层。

# 决定用 nft 直接管理时,先停掉前端
sudo systemctl disable --now firewalld     # RHEL 系
sudo systemctl disable --now ufw           # Ubuntu

远程配置防火墙的风险控制

这是本文最重要的部分。远程改防火墙的经典事故是:设了 policy drop 但忘了放行 SSH,或者放行 SSH 时写错了自己的 IP,导致再也连不上。

风险控制的四条规则:

第一,先放行再收紧。 顺序永远是:先加放行 SSH 的规则(或先确认已有规则),再设置默认策略为 drop。反着做等于先把自己锁外面。

第二,保留一个已登录的会话。 在整个操作过程中不关闭当前 SSH 连接,所有验证都在新窗口做。这是最可靠的保险。

第三,确认带外通道可用。 云控制台的 VNC 或串口终端不经过网络,是最后的救命通道。动手前先登录一次验证可用。

第四,用定时任务做自动回滚保险。 这是进阶技巧,非常实用:

# 先设一个 10 分钟后自动恢复的保险
echo 'nft flush ruleset' | sudo at now + 10 minutes 2>/dev/null
# 确认没问题后取消
sudo atq
sudo atrm 任务号

at 需要先安装并启动 atd 服务。如果不用 at,也可以用 sleep 600 && nft flush ruleset & 配合 disown,但不如 at 清晰。做完验证记得取消,否则 10 分钟后你的规则会被清空。

失联后的回滚方法:

通道 操作
还有会话 直接 nft flush ruleset 或改回配置
已断连但有 VNC 控制台登录,清空规则集或还原配置文件
firewalld 场景 firewall-cmd --panic-off 紧急解除
云安全组误配 控制台改回安全组规则,不依赖主机
完全无通道 联系云厂商技术支持,或重装系统保留数据盘

firewalld 提供了紧急模式,一键开启或关闭全部流量:

sudo firewall-cmd --panic-on      # 全部拒绝(紧急止血)
sudo firewall-cmd --panic-off     # 恢复正常
sudo firewall-cmd --query-panic

需要提醒的是 --panic-on 会连你自己的连接也断掉,所以只应该在已经确认有带外通道时使用,不要作为常规操作。

Windows Defender 防火墙

Windows Server 侧的防火墙是 Windows Defender 防火墙(带高级安全),它是有状态防火墙,默认阻止入站未请求的流量、允许出站流量。管理方式有图形界面和 PowerShell 两种,批量配置推荐 PowerShell。

查看当前状态与规则:

Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction
Get-NetFirewallRule -Direction Inbound -Enabled True |
    Select-Object DisplayName, Direction, Action, Profile |
    Sort-Object DisplayName | Format-Table -AutoSize

创建入站规则,推荐按程序而不是按端口:

# 按程序放行,只影响指定可执行文件
New-NetFirewallRule -DisplayName "允许 Nginx 入站" `
    -Direction Inbound -Action Allow -Protocol TCP `
    -Program "C:\nginx\nginx.exe" -Profile Any

# 按端口放行,并限制来源 IP(作用域)
New-NetFirewallRule -DisplayName "允许 RDP 仅办公网" `
    -Direction Inbound -Action Allow -Protocol TCP -LocalPort 3389 `
    -RemoteAddress 203.0.113.10,198.51.100.0/24 -Profile Any

# 允许 ping,便于排障
New-NetFirewallRule -DisplayName "允许 ICMPv4 Echo" `
    -Direction Inbound -Action Allow -Protocol ICMPv4 -IcmpType 8

按程序 vs 按端口的选择: 按程序(-Program)的好处是规则与具体可执行文件绑定,即使端口变化或程序被替换,规则不会误放行其他程序;缺点是程序路径变更时规则失效,且对多进程或动态端口的服务支持不友好。按端口(-LocalPort)配置简单稳定,但任何能在本机启动该端口监听的程序都会被放行。服务类应用建议按程序,标准服务(如 RDP、WinRM)按端口并严格限制来源。

作用域限制来源 IP(-RemoteAddress) 是 Windows 防火墙最实用也最容易被忽略的参数。把 3389 只对办公网出口放行,能挡掉绝大部分远程桌面爆破。修改已有规则的作用域:

# 收紧已有的 RDP 规则
Get-NetFirewallRule -DisplayGroup "远程桌面" | Where-Object { $_.Direction -eq 'Inbound' } |
    Set-NetFirewallRule -RemoteAddress 203.0.113.10,198.51.100.0/24

# 禁用不需要的默认规则(比删除安全,便于回滚)
Get-NetFirewallRule -DisplayName "*文件和打印机共享*" |
    Set-NetFirewallRule -Enabled False

风险提示:修改远程桌面相关规则前,先确认自己当前的来源 IP 在放行范围内,并确认云控制台有 VNC 或救援通道。改完立刻开一个新连接验证,不要关闭当前会话。

规则的管理与清理:

# 导出配置备份
netsh advfirewall export "C:\fw-backup.wfw"

# 恢复
netsh advfirewall import "C:\fw-backup.wfw"

# 重置为默认(危险,会清除全部自定义规则)
netsh advfirewall reset

# 删除规则
Remove-NetFirewallRule -DisplayName "允许 Nginx 入站"

# 查看某条规则的完整配置
Get-NetFirewallRule -DisplayName "允许 RDP 仅办公网" | Get-NetFirewallAddressFilter

netsh advfirewall export 导出的文件是重要备份手段,批量修改规则前先导出,出错可一键还原。

常见问题

nft 加了规则,重启后没了,为什么?

nft 命令只改内存中的规则集,持久化要导出到 /etc/nftables.conf 并启用 nftables 服务。执行 sudo nft list ruleset > /etc/nftables.conf 后确认服务已 enable。

为什么加了默认 drop 之后 apt 或 curl 就卡住了?

因为没有放行 ct state established,related,本机主动发起的连接其返回包被 input 链挡掉了。加上这条规则即可,它应该放在 input 链靠前的位置。

firewalld 和 nft 能一起用吗?

技术上能,但不建议。firewalld 是 nftables 的前端,它重载规则时会覆盖手工加的 nft 规则。选一个坚持用:要么用 firewalld 管理一切,要么停掉 firewalld 直接用 nft。

修改防火墙配置时怎么保证不失联?

四条措施:先加放行规则再收紧默认策略;保留一个已登录会话不关;提前确认云控制台 VNC 可用;用 at now + 10 minutes 设置一个自动清空规则的保险,验证通过后取消。

Windows 上配了入站规则还是连不上?

按三层排查:云安全组是否放行、Windows 防火墙是否放行、服务是否真的在监听。用 netstat -ano | findstr "3389" 确认监听,用 Test-NetConnection 目标IP -Port 3389 从外部测试区分是网络问题还是服务问题。

总结

  • nftables 用统一语法和 inet 地址族取代了 iptables 的四套工具,引入集合提升海量 IP 的匹配性能,主流发行版已默认切换;不要混用 iptables 与 nft 命令。
  • 三层结构是表(指定地址族)、链(基础链挂内核钩子、常规链用于分组)、规则(匹配加动作);规则在链内按顺序匹配,命中即停。
  • 有状态放行是必配项:ct state established,related accept 放在 input 链靠前位置,否则本机主动外连的返回包会被挡掉。
  • 持久化靠 /etc/nftables.conf 加 nftables 服务,改配置前先备份、nft -c -f 检查语法再重启;firewalld 与 ufw 都是 nftables 的前端,同机只用其一。
  • 远程改防火墙必须遵守"先放行后收紧、保留会话、确认 VNC、设定时回滚保险"四条,失联时按 VNC、--panic-off、安全组、厂商支持顺序抢救;Windows 侧用 New-NetFirewallRule 的 -RemoteAddress 严格限制来源,并按程序而非端口授权服务。