防火墙与端口
这一课你将能做到
- 用
ss -tlnp 读懂监听状态,区分「对外监听 0.0.0.0」与「仅本机 127.0.0.1」,并找出是谁在监听哪个端口
- 用 ufw(Ubuntu)完成标准开门:放行 22/80/443、其余默认拒绝;会认 firewalld(RHEL)的等价命令
- 理解「双层门」:云安全组与系统防火墙的关系,并把 CodeRoadMap 配成「只暴露 80/443,Java/MySQL 绑 127.0.0.1」的标准拓扑
为什么要学
「服务器装了 MySQL,第二天就被人扫走勒索」——不是黑客厉害,是 3306 对全网敞开。同理,Spring Boot 直接监听 0.0.0.0:8080 且安全组放行,等于绕过 Nginx 把整个应用裸奔到公网:没有 HTTPS、没有限流、没有缓存、攻击面全开。
本课解决两件事:看清现状(ss:谁在监听、监听在哪个地址)和收紧入口(防火墙只开必需端口 + 应用只绑内网/回环)。这是 CodeRoadMap 上线前的最后一道工序,也是生产安全的第一课。
先建立图像
端口与监听:一扇扇门
IP + 端口 = 服务的门牌号。监听(LISTEN)= 门开着等人敲。
0.0.0.0:8080 ← 门开在「所有网卡」上:本机、内网、公网都能敲 ⚠️
127.0.0.1:3306 ← 门开在「回环」上:只有这台机器自己能敲 ✅
:::22 ← IPv6 的所有地址监听(配合 IPv4 则全网可连 SSH)
ss 输出解剖(看懂它 = 看懂全课)
ss -tlnp
# State Recv-Q Local Address:Port Peer Address:Process
# LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=123,fd=6))
# LISTEN 0 4096 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",pid=456,fd=30))
# LISTEN 0 511 [::]:22 [::]:* users:(("sshd",pid=789,fd=3))
# │ │ │ └ 谁在听(进程名+PID)→ 最有价值的列
# │ │ └ 远端 *(等任意来源连接)
# │ └ 本地监听地址:端口 ← 判断 0.0.0.0 还是 127.0.0.1 的地方
# └ t=TCP l=监听 n=数字端口 p=进程
CodeRoadMap 的标准端口拓扑(本课的目标态)
公网 → 安全组(只放 22/80/443) → ufw(同样只放 22/80/443)
├─ Nginx 0.0.0.0:80/443 ← 唯一的门面
├─ SSH :22 ← 你的管理通道
├─ Node 127.0.0.1:3000 ← 内部服务,外人不许见
├─ JAR 127.0.0.1:8080 ← 同上
└─ MySQL 127.0.0.1:3306 ← 绝不上公网
原则:对公网只开「门面」和「管理」,业务进程全部绑 127.0.0.1。即使防火墙配错,应用层还有一层兜底。
跟着做
步骤 1:摸清当前监听现状
ss -tlnp # 全部 TCP 监听 + 进程(本课第一命令)
ss -tlnp | grep -E ':(80|443|3000|8080|3306)\s' # 只看本站关心的端口
ss -tnp # 已建立的连接(t=tcp n=数字 p=进程)
ss -s # 汇总:连接总数,粗看健康度
sudo ss -tlnp # 不加 sudo 看不到别的用户进程的 p 列,习惯加
验收问题(现在就能答):你的机器上哪些端口对全网开放(0.0.0.0/::)?哪些只在本机(127.0.0.1)?每个端口是谁?
步骤 2:ufw 标准四连(Ubuntu)
# 先放行 SSH 再 enable —— 顺序错了等于把自己锁在门外!
sudo ufw allow OpenSSH # 或 sudo ufw allow 22/tcp
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable # 激活(此时问你 Command may disrupt... 输 y)
sudo ufw status verbose # 验收:default deny (incoming),22/80/443 ALLOW
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp # SSH 只给公司出口 IP
sudo ufw deny 3306/tcp # 明确拒绝数据库端口(双保险)
sudo ufw status numbered # 带编号规则表
sudo ufw delete 3 # 删第 3 号规则(按编号表来)
sudo ufw insert 1 allow 443/tcp # 插到最前(规则有顺序,命中即止)
sudo ufw disable # 全关(排障时临时用,用完记得 enable)
步骤 3:firewalld 对照(RHEL/Rocky/CentOS 会认)
# Rocky/Alma 上的等价操作:
sudo firewall-cmd --state
sudo firewall-cmd --permanent --add-service=ssh # ≈ ufw allow OpenSSH
sudo firewall-cmd --permanent --add-service=http # 80
sudo firewall-cmd --permanent --add-service=https # 443
sudo firewall-cmd --permanent --remove-service=mysql # 拒 3306
sudo firewall-cmd --reload # ≈ ufw enable 的生效动作
sudo firewall-cmd --list-all # ≈ ufw status verbose
| 动作 | Ubuntu ufw | RHEL firewalld |
|---|
| 看状态/规则 | ufw status verbose | firewall-cmd --list-all |
| 放行端口 | ufw allow 443/tcp | firewall-cmd --permanent --add-port=443/tcp |
| 拒绝端口 | ufw deny 3306/tcp | --remove-port / 默认拒绝 |
| 生效 | 规则即时生效 | --permanent 后必须 --reload |
两套底层都是内核的 netfilter(iptables/nftables),ufw/firewalld 只是「好用的外壳」。
步骤 4:双层门——云安全组与系统防火墙
互联网 → [云安全组] → [系统防火墙 ufw/firewalld] → [应用监听地址]
第一道:云控制台配置,实例外拦截 第二道:OS 内拦截
排障口诀:连接不通先问两遍——安全组放了吗(云控制台)?ufw 放了吗(status)?应用绑的是 0.0.0.0 吗(ss -tlnp)?三关都过才通。
动手验证「双层」的存在:在云控制台安全组临时放行 8080,但不放 ufw——curl 公网IP:8080 依然不通,因为第二道门没开;反之亦然。两层是「与」的关系。
步骤 5:把 Java/前端绑到 127.0.0.1(应用层收口)
防火墙之外,应用监听地址是最后一道兜底。Spring Boot:
# /www/wwwroot/coderoadmap/application-prod.yml 里:
# server:
# address: 127.0.0.1 ← 关键行,只听回环
# port: 8080
sudo systemctl restart coderoadmap-backend
sudo ss -tlnp | grep 8080 # 期望:127.0.0.1:8080(不再是 0.0.0.0 或 *:8080)
# package.json 的 start 脚本加 -H 0.0.0.0 反而错——本机只有 Nginx 反代访问它,绑回环即可:
# "start": "next start -H 127.0.0.1 -p 3000"
pm2 restart platform 2>/dev/null || sudo systemctl restart platform
sudo ss -tlnp | grep 3000 # 期望:127.0.0.1:3000
sudo grep -n "bind-address" /etc/mysql/mysql.conf.d/mysqld.cnf
# Ubuntu 默认已是 127.0.0.1;若是 0.0.0.0 改回并重启:
# sudo systemctl restart mysql
步骤 6:端到端验收(上线前检查单)
# ① 门面要通:
curl -I http://你的服务器IP # 期望 200/301(Nginx 应答)
# ② 内部端口公网不可达(关键!):
curl -m 3 http://你的服务器IP:8080 # 期望:连接超时/拒绝 —— 不通才对
curl -m 3 http://你的服务器IP:3306 # 同上
# ③ 本机回环可达(Nginx 反代的依赖):
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/actuator/health
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3000/
# ④ 规则表终审:
sudo ufw status verbose
讲清楚要点
- 监听地址是比防火墙更内层的防线:
0.0.0.0:8080 即使被防火墙挡住,也只是「门锁着窗没关」的运气;127.0.0.1:8080 是窗根本不存在。生产三件套:绑回环 + 防火墙默认拒 + 安全组白名单。
- 默认拒绝是防火墙的正确姿势:ufw enable 后默认 incoming deny,只 allow 白名单。「默认允许,见一个堵一个」永远堵不完(Redis 未授权、新开的调试端口都在赌你没堵)。
- 规则有顺序,命中即止:ufw 里 insert 进前面的规则先生效。典型的「先 deny 再 allow」逻辑(先拒绝网段、再放行某 IP)必须用编号确认顺序。
- SSH 规则必须先于 enable:ufw enable 的瞬间按现有规则接管流量,没放 22 你就把自己锁外面了。云控制台 VNC 是最后的救援通道,但别指望它。
- 安全组 ≠ 系统防火墙:两者独立生效、取交集。迁移服务器/换镜像时最容易漏的就是只配了一层。排障时按「外层到内层」顺序过。
- 对外暴露的每个端口都要有「存在理由」:443 因为要 HTTPS、22 因为要管理(最好限源 IP)、80 通常只为跳转 443。除此之外出现任何 0.0.0.0 监听,都该问一句「谁开的?为什么?能不能绑回环?」
你可能会懵的地方
- 127.0.0.1 和 0.0.0.0 到底是「谁的地址」?
它们是服务器自己的监听绑定地址,不是客户端的。0.0.0.0=「监听本机所有网卡」(接受任何来源);127.0.0.1=「只监听回环接口」(物理上只有本机进程能连)。浏览器里访问自己的公网 IP 能通不代表绑对了——从外部机器 curl 才算数。
- ufw enable 之后 SSH 断了怎么办?
立即走云控制台 VNC/救援登录,
sudo ufw disable 或补 ufw allow 22/tcp。预防:enable 前先 allow OpenSSH;改动防火墙时保持一条已连接的 SSH 会话不退出(本课与 SSH 课的双重保命规则)。
- 3306 绑了 127.0.0.1,远程开发机想连数据库怎么办?
三条路按优先级:① SSH 隧道(
ssh -L 13306:127.0.0.1:3306 prod,本机连 localhost:13306)——最安全;② 专用堡垒/跳板机;③ 实在要直连:云安全组+ufw 限源 IP + MySQL 专用账户限 host,三者齐备且经评审。永远不要 0.0.0.0 + 弱密码。
- TIME_WAIT 一大堆是故障吗?
不是。TIME_WAIT 是主动关闭方等待迟到的包,60 秒后消失,高并发短连接下量大属正常。要优化的是「让连接复用」(应用层连接池、Nginx upstream keepalive),而不是去「杀」TIME_WAIT。
- iptables 还要学吗?
ufw/firewalld 的底层。除非做容器网络(Docker 的端口映射就是 iptables 规则)或需要复杂转发,日常服务器管理用外壳即可。知道
sudo iptables -L -n 能看到底层规则、Docker 会绕过 ufw 直接插规则(暴露容器端口时安全组要单独确认)就够本阶段用。
错法对照
| 错法 | 问题 | 正解 |
|---|
ufw enable 前没放 SSH | 全机失联 | 先 allow OpenSSH 再 enable;改规则时留已连会话 |
| 「临时测试」放行 3306/6379/8080 到公网 | 忘了收回去 = 数据库裸奔 | 临时规则用完即删:ufw status numbered 定期审 |
| Java 监听 0.0.0.0 依赖安全组兜底 | 换机器/改安全组就裸奔,Nginx 被绕过 | server.address=127.0.0.1 应用层先收口 |
| 只配安全组不配 ufw(或反之) | 迁移/重装后防线归零 | 双层都配,验收用公网 curl 实测 |
| 防火墙问题第一反应 disable 排障 | 全网敞开,且忘 enable | 逐条 status 定位;临时 disable 用完立刻回滚 |
| 用 telnet 判断端口「通不通」就下结论 | 通只代表 TCP 层,不代表服务健康 | curl 带路径验业务(actuator/health),分层判断 |
卡点急救
- 公网 curl :80 全部超时:顺序过三关——云安全组 80 放行了吗?
ufw status 有 80 allow 吗?ss -tlnp | grep :80 Nginx 在监听且是 0.0.0.0 吗?哪关缺补哪关。
- 本机 curl 127.0.0.1:8080 通、公网 8080 不通,但希望它通:先反问自己「真的需要它通吗」——90% 情况正确做法是配 Nginx 反代而不是暴露 8080。确需直连:改 server.address + ufw allow + 安全组,三步一起动。
- 「连接被拒绝」和「连接超时」要区分:refused=包到了但没人听(端口没监听/服务没起);timeout=包在路上被丢(安全组/防火墙拦截、IP 错、网络不通)。refused 查服务,timeout 查防火墙——一句话定位大方向。
- ufw 规则改乱了想重来:
sudo ufw reset 清空全部规则回到默认,然后按「先 SSH → 80/443 → enable」顺序重来(reset 会自动 disable,正好是个干净的起点)。
- Docker 容器端口绕过了 ufw:
-p 6379:6379 直接插 iptables 规则,ufw 拦不住。容器对外暴露一律走安全组限制源 IP;或让容器只绑 127.0.0.1(-p 127.0.0.1:6379:6379)再由 Nginx 反代。
面试常问
-
如何查看服务器上哪些端口对外监听?
sudo ss -tlnp(现代工具)或老机器 netstat -tlnp。重点看 Local Address:Port 列:0.0.0.0/::=全网可达需审查,127.0.0.1=仅本机安全。p 列给出进程,配合 ps 确认业务归属。公网视角再用外部机器 curl 交叉验证防火墙与安全组。
-
0.0.0.0 和 127.0.0.1 监听的区别?Spring Boot 怎么只绑本机?
0.0.0.0 监听所有网卡、接受任何来源(含公网);127.0.0.1 只监听回环、仅本机进程可达。Spring Boot:server.address=127.0.0.1(yml)或启动参数 --server.address=127.0.0.1。生产模式:应用绑回环 + Nginx 443 反代。
-
云安全组和系统防火墙的关系?
两个独立生效的过滤器,流量需依次通过(取交集):安全组在虚拟化层(实例外,云控制台配),防火墙在 OS 内(ufw/firewalld 配)。任一层没放行就不通。正确配置是两层同时收敛到同一份白名单,避免「单层裸奔」。
-
数据库端口能不能对公网开放?为什么?
默认不能。MySQL/Redis 历史上大量未授权访问与勒索事件(Redis 尤甚)。必须开放时(跨机内网调用):走 VPC 内网、安全组限源 IP、数据库专用账号限 host、启用 TLS/强密码,并配审计。公网直连数据库属于架构 smell——正确方向是内网互通或 API 收口。
-
一台机器突然 CPU/内存异常,怀疑被入侵,怎么查?
联动本 track 多课:ss -tnp 看异常外连(对外挖矿端口);ps aux 按 CPU 找陌生进程,ls -l /proc/PID/exe 定位文件路径;last、grep Accepted /var/log/auth.log 查异常登录;crontab -l 与 /etc/cron* 查持久化后门。防再犯:密钥登录+禁密码、防火墙默认拒、最小权限(这些正是前面各课的拼图)。
自测三问
ss -tlnp 输出里 127.0.0.1:3306 与 0.0.0.0:8080 分别意味着什么?哪个需要整改?
- 服务器迁移后站点打不开,写出按「安全组 → ufw → 应用监听 → 服务状态」排查的命令序列。
- 开发机需要连服务器的 MySQL 调试数据,给出最安全的一条路径并写出命令。
小结一句话
防火墙的目标态一句话:公网只开 22/80/443,业务与数据端口全部绑 127.0.0.1,安全组与 ufw 双层同源白名单——ss -tlnp 是你的眼睛,curl 实测是你的验收,默认拒绝是你的信仰。
延伸