Ubuntu 上的防火墙不难,难的是很多人一开始就把它想歪了:不是背几条 `ufw allow`,也不是把所有端口一关就叫安全。真正能长期维护的防火墙配置,应该回答三个问题:这台机器对外提供什么服务、谁能访问、出了问题怎么安全回滚。
Ubuntu 默认推荐的入口是 UFW,也就是 Uncomplicated Firewall。它的价值不是“功能比 nftables 强”,而是把日常主机防火墙里最常见的规则做成了人能看懂、敢修改、容易审计的命令。你要开 SSH、限制管理网段、只允许 Nginx 暴露 80/443、给内网服务加来源限制,用 UFW 就够了。等你要做路由转发、NAT、容器网络、透明代理、复杂链路,再往 nftables、iptables 或云安全组那层下探。
成熟的 Ubuntu 防火墙配置,不能只停在“命令大全”。先把防火墙层次讲清楚,再从一台新服务器的安全初始化开始,逐步处理 UFW 高级规则、Docker 场景、日志审计、故障回滚和生产检查。照着这个路径走,至少不会把自己锁在门外。
先搞清楚: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 numberedlimit:给 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 53HTTP / HTTPS:
sudo ufw allow out 80/tcp
sudo ufw allow out 443/tcpNTP:
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 reloadNAT、端口转发这类需求,通常需要改 `/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 sshdWeb 服务同理。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 || trueUbuntu 防火墙从入门到精通,不是把 UFW 命令背熟,而是知道每条规则在什么边界上生效。单机 Web 服务,用 UFW 做默认拒绝入站、明确放行 80/443/SSH;管理入口按来源限制;Docker 端口单独审计;复杂路由再下探到 nftables 和 UFW framework。这样配出来的防火墙不炫,但可读、可查、可回滚,生产环境就吃这一套。