Ubuntu 防火墙从入门到精通:UFW、nftables、Docker 和生产环境排错

作者:Administrator 发布时间: 2026-04-30 阅读量:4 评论数:0

Ubuntu 上的防火墙不难,难的是很多人一开始就把它想歪了:不是背几条 `ufw allow`,也不是把所有端口一关就叫安全。真正能长期维护的防火墙配置,应该回答三个问题:这台机器对外提供什么服务、谁能访问、出了问题怎么安全回滚。

Ubuntu 默认推荐的入口是 UFW,也就是 Uncomplicated Firewall。它的价值不是“功能比 nftables 强”,而是把日常主机防火墙里最常见的规则做成了人能看懂、敢修改、容易审计的命令。你要开 SSH、限制管理网段、只允许 Nginx 暴露 80/443、给内网服务加来源限制,用 UFW 就够了。等你要做路由转发、NAT、容器网络、透明代理、复杂链路,再往 nftables、iptables 或云安全组那层下探。

成熟的 Ubuntu 防火墙配置,不能只停在“命令大全”。先把防火墙层次讲清楚,再从一台新服务器的安全初始化开始,逐步处理 UFW 高级规则、Docker 场景、日志审计、故障回滚和生产检查。照着这个路径走,至少不会把自己锁在门外。

建议先在测试机上演练防火墙规则防火墙、SSH、Docker 端口转发这类配置,最好先拿一台干净 VPS 跑通再动生产机。如果你正好需要临时实验环境,可以看看雨云的云服务器方案,开一台小机器做安全演练很顺手。查看雨云服务器方案 →

先搞清楚:Ubuntu 防火墙到底有几层

在 Ubuntu 上谈防火墙,至少有四层。

  • 云厂商安全组:在服务器外面拦流量,比如云平台控制台里的入站规则。
  • Linux 内核 netfilter:真正执行包过滤的内核子系统。
  • nftables / iptables:管理员和程序配置 netfilter 的底层工具。
  • UFW:Ubuntu 默认推荐的简化前端,主要用于主机型防火墙。

这几层不是互相替代,而是叠在一起。请求从公网过来,可能先过云安全组,再进服务器内核,再被 UFW 写入的规则处理,最后才到 Nginx、SSH 或你的应用。

很多排错问题就出在这里。你在 UFW 里放行了 443,但云安全组没开,外面还是访问不了。你在云安全组开了 22,但 UFW 禁了 SSH,照样连不上。你用 Docker 发布了端口,UFW 状态看着没开,容器却能被访问,因为 Docker 可能改了转发链。

成熟做法是:

  • 云安全组负责粗粒度边界,尽量少开放公网入口。
  • UFW 负责服务器主机上的可读规则。
  • Docker / Kubernetes / 反代层单独审计,不假装它们不存在。
  • 真遇到复杂路由和 NAT,再进入 nftables 或 iptables 层。

UFW 适合什么,不适合什么

UFW 适合这些场景:

  • 单台 Ubuntu 服务器。
  • VPS 或轻量云主机。
  • SSH、HTTP、HTTPS、数据库端口的基础访问控制。
  • 按来源 IP 或网段限制管理入口。
  • 基础出站策略。
  • 日志审计和快速规则回滚。

UFW 不适合独自承担这些事情:

  • 大规模 Kubernetes 网络策略。
  • 复杂多网卡路由网关。
  • 高级 NAT、透明代理、策略路由。
  • DDoS 防护。
  • 应用层鉴权。
  • Docker 端口发布后的全部流量控制。

它是一个主机防火墙入口,不是全栈安全系统。把这个边界放对,后面就不容易上头。

安装和确认版本

多数 Ubuntu Server 默认已经装了 UFW。如果没有:

sudo apt update
sudo apt install -y ufw

查看版本:

ufw version

查看状态:

sudo ufw status

新机器常见输出是:

Status: inactive

这不代表服务器没有任何包过滤能力,只是 UFW 这套规则还没启用。

开启前最重要的一件事:别把 SSH 锁死

远程服务器上启用防火墙,第一条规则永远不是开 80,也不是开 443,而是先保证自己还能登录。

如果 SSH 是默认 22 端口:

sudo ufw allow OpenSSH

或者显式写端口:

sudo ufw allow 22/tcp

如果 SSH 是 2222:

sudo ufw allow 2222/tcp

更安全一点,只允许你的固定公网 IP 登录:

sudo ufw allow from 203.0.113.10 to any port 22 proto tcp

如果你通过 VPN 管理服务器,可以只允许 VPN 网段:

sudo ufw allow from 10.8.0.0/24 to any port 22 proto tcp

确认规则:

sudo ufw status verbose

再启用:

sudo ufw enable

启用时 UFW 可能提示:

Command may disrupt existing ssh connections. Proceed with operation (y|n)?

这不是废话。确认已经放行 SSH,再输入 `y`。

保险做法:启用前保留一个已有 SSH 窗口,不要关;另开一个新窗口测试能否重新登录。新窗口能进来,再关旧窗口。

一台新 Ubuntu 服务器的基础策略

比较稳的默认策略是:

sudo ufw default deny incoming
sudo ufw default allow outgoing

意思是:

  • 默认拒绝入站连接。
  • 默认允许出站连接。

这是大多数 Web 服务器、API 服务器、工具站、个人 VPS 都适用的起点。

然后按需要开放服务:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

查看详细状态:

sudo ufw status verbose

你应该看到类似:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere
443/tcp                    ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)
80/tcp (v6)                ALLOW IN    Anywhere (v6)
443/tcp (v6)               ALLOW IN    Anywhere (v6)

如果你不使用 IPv6,别只在应用里忽略它。要么正确配置 IPv6 防火墙,要么在系统层明确关闭 IPv6。UFW 默认会同时处理 IPv4 和 IPv6,前提是 `/etc/default/ufw` 里:

IPV6=yes

修改后需要重载:

sudo ufw reload

常用端口怎么开

开放 HTTP:

sudo ufw allow 80/tcp

开放 HTTPS:

sudo ufw allow 443/tcp

同时开放 Web 服务:

sudo ufw allow 80,443/tcp

开放某个 UDP 服务,比如 WireGuard:

sudo ufw allow 51820/udp

开放一个端口范围:

sudo ufw allow 30000:32767/tcp

注意端口范围必须带协议,不能偷懒。

允许指定来源访问 Redis:

sudo ufw allow from 10.0.0.0/8 to any port 6379 proto tcp

拒绝某个 IP 访问任何服务:

sudo ufw deny from 198.51.100.23

拒绝某个网段访问 22:

sudo ufw deny from 198.51.100.0/24 to any port 22 proto tcp

生产里更常见的做法不是到处 `deny`,而是默认拒绝入站,只给可信来源 `allow`。

用应用配置文件管理服务

UFW 支持 application profile。很多软件会在:

/etc/ufw/applications.d/

放自己的配置。

查看可用应用:

sudo ufw app list

查看某个应用详情:

sudo ufw app info OpenSSH
sudo ufw app info "Nginx Full"

Nginx 常见配置:

sudo ufw allow "Nginx HTTP"
sudo ufw allow "Nginx HTTPS"
sudo ufw allow "Nginx Full"

`Nginx Full` 通常表示同时放行 80 和 443。

Apache 类似:

sudo ufw allow "Apache Full"

应用 profile 的好处是规则可读性更强。坏处是你得确认 profile 内容和真实服务一致。别看到名字像就直接放,先:

sudo ufw app info "Nginx Full"

精确到网卡:多网卡服务器要这么写

有些机器既有公网网卡,也有内网网卡。比如:

  • `eth0` 是公网。
  • `eth1` 是内网。

只允许内网访问 MySQL:

sudo ufw allow in on eth1 to any port 3306 proto tcp

拒绝公网网卡访问 MySQL:

sudo ufw deny in on eth0 to any port 3306 proto tcp

只允许公网访问 443:

sudo ufw allow in on eth0 to any port 443 proto tcp

出站也可以按网卡写:

sudo ufw allow out on eth0 to any port 443 proto tcp

多网卡机器不要只看端口,要看流量从哪块网卡进来、出去。否则规则看着没错,实际边界是糊的。

删除规则:先编号,再删除

不要凭感觉删规则。先看编号:

sudo ufw status numbered

输出类似:

[ 1] 22/tcp                     ALLOW IN    Anywhere
[ 2] 80/tcp                     ALLOW IN    Anywhere
[ 3] 443/tcp                    ALLOW IN    Anywhere

删除第 2 条:

sudo ufw delete 2

也可以按规则内容删除:

sudo ufw delete allow 80/tcp

编号删除更直观,但注意编号会变化。删多条时,最好每删一条重新 `status numbered`,别一口气按旧编号删,容易删错。

插入规则和规则顺序

防火墙规则有顺序。UFW 大部分简单规则不用你关心顺序,但 deny 和 allow 混用时顺序就重要了。

把规则插到最前:

sudo ufw insert 1 deny from 198.51.100.23

在最前面加一个管理网段白名单:

sudo ufw insert 1 allow from 203.0.113.0/24 to any port 22 proto tcp

如果你发现“明明 deny 了但还是能访问”,就查编号和顺序。

sudo ufw status numbered

limit:给 SSH 做简单限速

UFW 有 `limit`,常用于 SSH:

sudo ufw limit OpenSSH

或者:

sudo ufw limit 22/tcp

它不是完整的暴力破解防护,只是简单限速。更严肃的 SSH 爆破防护,建议配 Fail2ban。UFW 管端口边界,Fail2ban 根据日志动态封禁,两者分工不同。

如果你已经写了普通 SSH allow,可以先删除普通规则,再加 limit:

sudo ufw status numbered
sudo ufw delete 1
sudo ufw limit 22/tcp

注意别在远程机器上把唯一 SSH 入口删掉后忘了加回来。

logging:日志开到什么程度

开启日志:

sudo ufw logging on

设置日志级别:

sudo ufw logging low
sudo ufw logging medium
sudo ufw logging high
sudo ufw logging full

生产环境通常从 `low` 开始。`full` 很吵,流量稍微大点日志就膨胀。

查看 UFW 日志:

sudo journalctl -k | grep UFW

或:

sudo grep UFW /var/log/syslog
sudo grep UFW /var/log/kern.log

日志里常见字段包括:

SRC=198.51.100.23 DST=203.0.113.5 PROTO=TCP SPT=54231 DPT=22

重点看:

  • `SRC`:来源 IP。
  • `DST`:目标 IP。
  • `PROTO`:协议。
  • `SPT`:来源端口。
  • `DPT`:目标端口。
  • `IN`:入站网卡。
  • `OUT`:出站网卡。

日志不是为了好看,是为了能回答“谁在访问哪个端口、从哪块网卡进来、被什么规则挡住”。

dry-run:动手前先预演

UFW 支持 `--dry-run`。比如你想看开放 HTTP 会生成什么规则:

sudo ufw --dry-run allow 80/tcp

这不会真的修改防火墙,只会展示将要应用的规则。生产机上做复杂变更前,先 dry-run,一点不丢人。

show:看更底层的规则

UFW 状态不够看时,用 show:

sudo ufw show raw
sudo ufw show user-rules
sudo ufw show listening
sudo ufw show added

其中 `show listening` 很实用,它会列出当前系统监听端口和相关进程。你可以先看机器到底暴露了什么:

sudo ufw show listening

再决定防火墙该开哪些端口。

也可以配合 ss:

sudo ss -lntup
sudo ss -lnuap

防火墙配置前,先知道服务监听面。很多机器不是被防火墙“没配好”害的,而是自己都不知道哪些进程在监听公网。

出站策略:什么时候要 deny outgoing

默认允许出站是为了少踩坑。很多服务需要访问:

  • apt 软件源。
  • DNS。
  • NTP。
  • Docker registry。
  • 对象存储。
  • 第三方 API。
  • Webhook。

如果你把出站默认改成 deny:

sudo ufw default deny outgoing

就必须逐项放行。比如 DNS:

sudo ufw allow out 53

HTTP / HTTPS:

sudo ufw allow out 80/tcp
sudo ufw allow out 443/tcp

NTP:

sudo ufw allow out 123/udp

只允许访问某个 API:

sudo ufw allow out to 203.0.113.20 port 443 proto tcp

出站 deny 更适合高安全内网、受控业务机器、跳板机、合规环境。普通 VPS 和工具站别一开始就这么搞,排错成本很高。

路由和转发:UFW 不只管 INPUT

UFW 默认主打主机防火墙,也就是进到本机服务的流量。如果这台 Ubuntu 同时承担网关、VPN 路由、内网转发,就要看 routed 策略。

查看默认策略:

sudo ufw status verbose

你可能会看到:

Default: deny (incoming), allow (outgoing), disabled (routed)

开启路由流量规则时,常用:

sudo ufw route allow in on wg0 out on eth0

更精确一点:

sudo ufw route allow in on wg0 out on eth0 from 10.8.0.0/24 to any port 443 proto tcp

如果要让系统转发 IPv4,还要配置:

sudo nano /etc/ufw/sysctl.conf

确认:

net.ipv4.ip_forward=1

然后:

sudo ufw reload

NAT、端口转发这类需求,通常需要改 `/etc/ufw/before.rules`。这已经进入高级区,不建议边抄边上生产。先画清楚流量路径,再写规则。

端口转发和 NAT 的基本形态

假设你想把公网 `eth0` 的 80 转发到内网 Web 服务器 `10.0.0.2:80`。

先启用转发:

sudo nano /etc/ufw/sysctl.conf

写或确认:

net.ipv4.ip_forward=1

然后编辑:

sudo nano /etc/ufw/before.rules

在合适位置加入 nat 表,常见形态是:

*nat
:PREROUTING ACCEPT [0:0]
-A PREROUTING -p tcp -i eth0 --dport 80 -j DNAT --to-destination 10.0.0.2:80
COMMIT

再允许路由流量:

sudo ufw route allow in on eth0 to 10.0.0.2 port 80 proto tcp

重载:

sudo ufw reload

这种规则一定要做回滚预案。`before.rules` 写错可能导致 UFW 重载失败,甚至影响已有网络。

Docker 场景:别以为 UFW 一定能拦住容器端口

这是 Ubuntu 服务器上最常见的坑之一。

你以为只开了 22、80、443:

sudo ufw status

结果 Docker 里某个服务这样发布:

docker run -p 8080:8080 some-app

外网仍然可能访问到 8080。原因是 Docker 会管理自己的 iptables/nftables 规则,流量可能经过 `FORWARD`、`DOCKER`、`DOCKER-USER` 等链,而不是你想象里的 UFW 入站规则。

安全做法有几种。

第一,服务只绑定本地回环地址,再让 Nginx 反代:

docker run -p 127.0.0.1:8080:8080 some-app

这样公网不能直接打容器端口。

第二,用 compose 明确绑定:

services:
  app:
    image: some-app
    ports:
      - "127.0.0.1:8080:8080"

第三,重要环境里单独审计 Docker 防火墙链:

sudo iptables -S
sudo iptables -S DOCKER-USER
sudo nft list ruleset

第四,不要让数据库、Redis、管理后台直接 `0.0.0.0` 发布出来。很多事故不是防火墙不行,是容器发布端口太随意。

云安全组和 UFW 怎么配合

云安全组建议只开真正需要暴露给公网的入口:

  • 22 或你的 SSH 管理端口,最好限制来源 IP。
  • 80。
  • 443。
  • VPN 端口,比如 51820/udp。

数据库、Redis、内部管理面板,不要在安全组里对公网开放。即使 UFW 里做了限制,外层能不放就不放。

比较稳的组合是:

  • 云安全组做第一道粗边界。
  • UFW 做主机上的精细边界。
  • 应用自己做认证和授权。
  • Fail2ban 根据日志动态处理爆破。

这四层没有谁能完全替代谁。

和 Fail2ban 配合

UFW 管静态边界,Fail2ban 管动态封禁。

SSH 规则可以是:

sudo ufw allow from 203.0.113.10 to any port 22 proto tcp

如果 SSH 必须对公网开放,可以配:

sudo ufw limit 22/tcp

再装 Fail2ban 处理爆破:

sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

Web 服务同理。UFW 放 80/443,Nginx 记录真实 IP,Fail2ban 根据恶意请求日志封来源。别让 UFW 去理解业务登录失败,它不是干这个的。

生产常用脚本一:初始化安全规则

保存为:

/usr/local/sbin/ufw-init-web-server.sh

内容:

#!/usr/bin/env bash
set -euo pipefail

if [ "${EUID}" -ne 0 ]; then
  echo "请用 root 运行:sudo $0"
  exit 1
fi

SSH_PORT="${SSH_PORT:-22}"
ADMIN_CIDR="${ADMIN_CIDR:-}"

ufw default deny incoming
ufw default allow outgoing

if [ -n "${ADMIN_CIDR}" ]; then
  ufw allow from "${ADMIN_CIDR}" to any port "${SSH_PORT}" proto tcp comment "admin ssh"
else
  ufw allow "${SSH_PORT}/tcp" comment "ssh"
fi

ufw allow 80/tcp comment "http"
ufw allow 443/tcp comment "https"
ufw logging low
ufw --force enable
ufw status verbose

使用:

sudo chmod +x /usr/local/sbin/ufw-init-web-server.sh
sudo ADMIN_CIDR=203.0.113.10 SSH_PORT=22 /usr/local/sbin/ufw-init-web-server.sh

生产常用脚本二:备份 UFW 配置

保存为:

/usr/local/sbin/ufw-backup.sh

内容:

#!/usr/bin/env bash
set -euo pipefail

OUT_DIR="/root/ufw-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "${OUT_DIR}"

cp -a /etc/ufw "${OUT_DIR}/etc-ufw"
ufw status verbose > "${OUT_DIR}/status-verbose.txt" || true
ufw status numbered > "${OUT_DIR}/status-numbered.txt" || true
ufw show raw > "${OUT_DIR}/show-raw.txt" || true
ss -lntup > "${OUT_DIR}/listening-tcp.txt" || true
ss -lnuap > "${OUT_DIR}/listening-udp.txt" || true

tar -C "$(dirname "${OUT_DIR}")" -czf "${OUT_DIR}.tar.gz" "$(basename "${OUT_DIR}")"
echo "备份完成:${OUT_DIR}.tar.gz"

使用:

sudo chmod +x /usr/local/sbin/ufw-backup.sh
sudo ufw-backup.sh

生产常用脚本三:巡检开放端口和 UFW 状态

保存为:

/usr/local/sbin/ufw-audit.sh

内容:

#!/usr/bin/env bash
set -euo pipefail

REPORT="/tmp/ufw-audit-$(date +%Y%m%d-%H%M%S).txt"

{
  echo "Ubuntu 防火墙巡检报告"
  echo "时间:$(date)"
  echo

  echo "=== UFW 状态 ==="
  ufw status verbose || true
  echo

  echo "=== UFW 编号规则 ==="
  ufw status numbered || true
  echo

  echo "=== UFW 已添加规则 ==="
  ufw show added || true
  echo

  echo "=== 监听 TCP 端口 ==="
  ss -lntup || true
  echo

  echo "=== 监听 UDP 端口 ==="
  ss -lnuap || true
  echo

  echo "=== 最近 UFW 日志 ==="
  journalctl -k -n 200 --no-pager | grep UFW || true
} > "${REPORT}" 2>&1

echo "巡检报告:${REPORT}"

使用:

sudo chmod +x /usr/local/sbin/ufw-audit.sh
sudo ufw-audit.sh

生产常用脚本四:安全开放临时端口

临时调试端口最容易忘关。可以用脚本提醒自己。

保存为:

/usr/local/sbin/ufw-temp-allow.sh

内容:

#!/usr/bin/env bash
set -euo pipefail

PORT="${1:-}"
PROTO="${2:-tcp}"
FROM="${3:-}"

if [ -z "${PORT}" ]; then
  echo "用法:sudo $0 <port> [tcp|udp] [source_cidr]"
  echo "例子:sudo $0 8080 tcp 203.0.113.10"
  exit 1
fi

COMMENT="temporary-open-$(date +%Y%m%d-%H%M%S)"

if [ -n "${FROM}" ]; then
  ufw allow from "${FROM}" to any port "${PORT}" proto "${PROTO}" comment "${COMMENT}"
else
  ufw allow "${PORT}/${PROTO}" comment "${COMMENT}"
fi

ufw status numbered

echo
 echo "临时规则已添加,记得调试后删除。可以用:sudo ufw status numbered && sudo ufw delete <编号>"

使用:

sudo chmod +x /usr/local/sbin/ufw-temp-allow.sh
sudo ufw-temp-allow.sh 8080 tcp 203.0.113.10

生产常用脚本五:紧急回滚到只允许 SSH

这个脚本适合控制台里救急,不适合随便跑。

保存为:

/usr/local/sbin/ufw-emergency-ssh-only.sh

内容:

#!/usr/bin/env bash
set -euo pipefail

if [ "${EUID}" -ne 0 ]; then
  echo "请用 root 运行:sudo $0"
  exit 1
fi

SSH_PORT="${SSH_PORT:-22}"
ADMIN_CIDR="${ADMIN_CIDR:-}"

ufw --force reset
ufw default deny incoming
ufw default allow outgoing

if [ -n "${ADMIN_CIDR}" ]; then
  ufw allow from "${ADMIN_CIDR}" to any port "${SSH_PORT}" proto tcp comment "emergency ssh"
else
  ufw allow "${SSH_PORT}/tcp" comment "emergency ssh"
fi

ufw --force enable
ufw status verbose

使用:

sudo SSH_PORT=22 ADMIN_CIDR=203.0.113.10 /usr/local/sbin/ufw-emergency-ssh-only.sh

这会重置 UFW 规则,只保留 SSH。生产环境执行前要知道后果。

常见故障排查

规则开了,外网访问不了

按顺序查:

sudo ufw status verbose
sudo ss -lntup
curl -I http://127.0.0.1:80

然后查云安全组、Nginx 配置、应用监听地址。

如果应用只监听 `127.0.0.1`,公网当然访问不到:

sudo ss -lntup | grep 8080

看到 `127.0.0.1:8080`,说明只本地监听。

UFW 开了,但 Docker 端口仍能访问

先看容器发布方式:

docker ps

如果看到:

0.0.0.0:8080->8080/tcp

说明它对外发布了。改成:

127.0.0.1:8080->8080/tcp

再让 Nginx 反代。

启用后 SSH 断了

从云控制台或 VNC 进入,执行:

sudo ufw status numbered
sudo ufw allow 22/tcp
sudo ufw reload

如果一时搞不清:

sudo ufw disable

恢复登录后再重新配置。别在唯一远程入口上赌运气。

规则太乱了,想重来

先备份:

sudo ufw-backup.sh

再重置:

sudo ufw --force reset

重新设置默认策略和必要端口:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

一套成熟的上线检查清单

上线前别只看“Status: active”。至少检查这些:

  • SSH 是否已按来源限制或至少已放行。
  • 云安全组和 UFW 是否一致。
  • `sudo ss -lntup` 里没有意外公网监听。
  • Docker 端口没有直接暴露敏感服务。
  • 数据库、Redis、后台面板没有对公网开放。
  • IPv6 策略和 IPv4 一致。
  • UFW 日志级别不是 full 这种容易刷爆磁盘的配置。
  • 复杂变更前做了 `/etc/ufw` 备份。
  • 有云控制台、VNC、救援模式或备用入口。
  • 改完后用外部网络实际测试 22、80、443 和管理入口。

可以用这组命令收尾:

sudo ufw status verbose
sudo ufw status numbered
sudo ufw show listening
sudo ss -lntup
sudo journalctl -k -n 100 --no-pager | grep UFW || true

Ubuntu 防火墙从入门到精通,不是把 UFW 命令背熟,而是知道每条规则在什么边界上生效。单机 Web 服务,用 UFW 做默认拒绝入站、明确放行 80/443/SSH;管理入口按来源限制;Docker 端口单独审计;复杂路由再下探到 nftables 和 UFW framework。这样配出来的防火墙不炫,但可读、可查、可回滚,生产环境就吃这一套。

评论