适用场景
SSH 端口敲门(Port Knocking)是一套”默认关闭、按序敲门才开门”的服务隐藏方案。适用的典型场景有三类:
- 服务器 22 端口直接暴露在公网,日志里每天有成千上万条爆破尝试,希望在不引入 VPN 的前提下把 SSH 从扫描器的视野里”藏起来”。
- 运维入口机、跳板机、数据库跳板等只允许少数固定运维人员登录,来源 IP 不固定(家庭宽带、4G、漫游),无法用 IP 白名单解决。
- 已有堡垒机或 WireGuard 隧道,但希望再加一层无成本的前置过滤,降低被 0day 或口令爆破打穿的窗口。
需要明确的是:端口敲门是一种隐匿手段,不是认证手段。它降低了被扫描发现的概率,但真正守住 SSH 的仍然是密钥认证、2FA 与 fail2ban 这类强认证与限流机制。二者应叠加使用,而不是互相替代。
前置条件
- Linux 服务器(Debian/Ubuntu/CentOS/RHEL 均可),具备 root 权限。
- 已安装
knockd服务端与knock/nc客户端工具。 - 服务器使用 iptables 管理过滤规则(若使用 firewalld 或 nftables,需先确认后端是 iptables 还是 nftables,参见第 6 节常见问题)。
- 服务器上 SSH 服务当前可正常连接,且已准备好一个独立的备用登录通道(如云控制台 VNC),避免配置失误导致彻底锁死。
- 已明确三到四个用于”敲门”的 TCP 端口号,例如 7000、8000、9000,这些端口不需要任何服务监听。
原理说明
端口敲门的核心机制是”按顺序连接一组本不提供服务的端口”,服务端通过抓包识别出正确的端口序列,再动态向防火墙插入一条临时放行规则。
knockd 的工作方式与普通守护进程不同:它通过 libpcap 在网卡上直接抓取数据包,因此即便 iptables 的 INPUT 链已经 DROP 掉这些包,knockd 依然能看到敲门序列。这一点决定了它不会因为防火墙拦截而失效,也决定了它无法穿透上层 NAT 或负载均衡——如果服务器在云厂商 NAT 网关之后,敲门包必须先被转发到主机,否则 knockd 收不到。
敲门序列的匹配由三个条件共同约束:
- 顺序:必须严格按配置的端口顺序发起连接。
- 时间窗:
seq_timeout指定整个序列必须在多少秒内完成,防止慢速逐端口探测。 - 标志位:
tcpflags = syn只接受全新连接请求,避免被 ESTABLISHED 流量误触发,也避免某些扫描器用 FIN 扫描碰巧命中序列。
相对更安全的替代方案是 单包授权(SPA),代表实现是 fwknop。SPA 只发送一个加密的 UDP 包,不产生可被旁路监听的端口序列,也不暴露”哪些端口是暗号”这一信息。如果安全要求高、且能接受额外部署成本,建议优先考虑 SPA;如果只是想让自动化扫描器扫不到 SSH,knockd 的成本收益比更好。
操作步骤
1. 安装 knockd
# Debian / Ubuntu
apt update && apt install -y knockd iptables-persistent
# CentOS / RHEL / Rocky
dnf install -y epel-release
dnf install -y knockd iptables-services
安装 iptables-persistent 时会询问是否保存当前规则,选择 Yes。这一步很关键:后面的防火墙规则需要在重启后依然生效。
2. 编写 knockd 配置文件
编辑 /etc/knockd.conf。下面是一份带自动关门的完整配置,敲门成功后放行 SSH,30 秒后自动收回权限:
[options]
logfile = /var/log/knockd.log
interface = eth0
[openssh]
sequence = 7000,8000,9000
seq_timeout = 10
tcpflags = syn
start_command = /usr/sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
cmd_timeout = 30
stop_command = /usr/sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
参数含义:
- interface:必须填写服务器实际接收公网流量的网卡名,可用
ip -br addr确认,填错会导致敲门完全无响应。 - sequence:关门端口序列,三个端口均无需监听。
- seq_timeout:10 秒内必须敲完,过长会削弱防护效果。
- %IP%:knockd 自动替换为敲门来源 IP,注意它取的是 TCP 层源地址,若服务器在 CDN 或反向代理之后会失效。
- cmd_timeout:放行窗口时长。设为 30 秒意味着必须在 30 秒内完成 SSH 握手并登录,超时后已建立的连接不受影响,但新连接会被拒绝。
如果希望放行更持久(例如 300 秒),把 cmd_timeout 调大即可;如果希望手工关门,可另写一个反向序列 9000,8000,7000 的 [closessh] 段并在客户端主动敲门关闭。
3. 配置 SSH 默认拒绝
在 knockd 生效前,先把 22 端口设为默认拒绝,形成”白名单式”访问模型:
# 保证已建立连接不被中断
iptables -I INPUT 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# SSH 默认丢弃
iptables -A INPUT -p tcp --dport 22 -j DROP
# 保存规则
netfilter-persistent save
注意顺序:先放行 ESTABLISHED,再 DROP SSH。如果先 DROP 再放行,SSH 会话会在下次数据包到达时被切断。执行完可用 iptables -L INPUT -n --line-numbers 检查两条规则的先后位置。
4. 启动并设置开机自启
systemctl enable --now knockd
systemctl status knockd --no-pager
若启动失败,先看 journalctl -u knockd -n 50 --no-pager,最常见的原因是网卡名写错或配置文件中 [options] 段缩进使用了 Tab(knockd 要求使用空格)。
5. 客户端敲门脚本
把敲门动作封装成脚本,避免每次手数端口:
#!/bin/bash
# 保存为 ~/knock-ssh.sh 并 chmod +x
HOST="${1:-ssh.example.com}"
for port in 7000 8000 9000; do
nc -z -w1 "$HOST" "$port" >/dev/null 2>&1
sleep 0.2
done
echo "[*] 敲门完成,正在连接 SSH..."
ssh -o ConnectTimeout=5 "root@${HOST}"
参数 -z 表示只探测端口不发数据,-w1 限制单次超时 1 秒。两个端口之间 sleep 0.2 是为了避免本地并发导致顺序错乱。
配置验证
按以下四步验证链路是否打通:
# 1) 确认 knockd 已在抓包
systemctl is-active knockd
ss -lunp | grep knockd
# 2) 敲门前:SSH 应超时无响应
nc -z -w2 ssh.example.com 22; echo "exit=$?"
# 3) 执行敲门并立即查看日志
tail -f /var/log/knockd.log
# 正常输出示例:
# 2026-09-12 10:30:01 ssh.example.com: openSSH: Stage 1
# 2026-09-12 10:30:01 ssh.example.com: openSSH: Stage 2
# 2026-09-12 10:30:01 ssh.example.com: openSSH: Stage 3
# 2026-09-12 10:30:01 ssh.example.com: openSSH: OPEN SESAME
# 4) 确认放行规则已插入且带来源 IP
iptables -L INPUT -n --line-numbers | grep 22
预期结果是敲门前 22 端口探测超时,敲门后出现一条 ACCEPT tcp -- 203.0.113.9 0.0.0.0/0 tcp dpt:22 规则,30 秒后该规则自动消失。
常见问题
FAQ 1:敲门日志显示 OPEN SESAME,但 SSH 依然连不上
按以下顺序排查:
- 时间窗太短:从敲门完成到发起 SSH 握手超过
cmd_timeout,规则已被收回。先临时把cmd_timeout调到 120 秒验证。 - fail2ban 已封禁该 IP:knockd 放行的是 iptables INPUT,但 fail2ban 会往自己的链里插入 DROP 规则,二者的优先级取决于链的挂载顺序。执行
iptables-save | grep -i f2b确认,必要时先在 fail2ban 侧 unban。 - 前置 DROP 规则:云厂商安全组、主机上更靠前的 DROP 规则会先于 knockd 的 ACCEPT 生效。用
iptables -L INPUT -n --line-numbers确认新规则是否排在 DROP 之前——-I INPUT插入的是链首,通常是安全的。 - 本地网络设备干扰:部分家用路由器对未监听端口返回 RST 而非静默丢弃,会导致
nc只发出一个包。改用-w1并确认客户端发包,或在服务端用tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0 and port 7000'直接看是否收到。
FAQ 2:服务器使用 firewalld 或 nftables,knockd 的 iptables 命令不生效
knockd 本身不绑定任何防火墙后端,它只是执行你写在 start_command 里的命令。因此有两种处理方式:
- 改用 firewalld:把命令替换为
/usr/bin/firewall-cmd --add-rich-rule='rule family="ipv4" source address="%IP%" port port="22" protocol="tcp" accept',关门命令用--remove-rich-rule。注意 firewalld 不可与 iptables 规则混用,否则会出现规则互相覆盖。 - 改用 nftables:使用命名集合动态增删元素,例如
nft add element inet filter ssh_allow { %IP% }与nft delete element inet filter ssh_allow { %IP% },规则里用ip saddr @ssh_allow accept引用集合。这种方式比分次增删规则更清晰,也便于审计。
另一个容易踩的坑是:若系统同时装了 firewalld 和 iptables-services,两套规则会争夺 netfilter,建议只保留一套。
FAQ 3:敲门端口序列会不会被旁路嗅探出来
会。端口序列是明文 TCP SYN 包,处于同一广播域或同一链路上的人可以用 tcpdump 完整记录序列并重放。因此:
- 不要在公共 Wi-Fi、共享办公网等不可信网络中执行敲门。
- 把序列端口设置成看似随机、且与常用端口无关联的一组值,避免使用 1、2、3 或 111、222、333 这类易猜测组合。
- 服务端可开启
[options]里的one_time_sequences(需要配合knockd.conf的序列文件),让每次使用的序列不同,重放即失效。 - 安全要求更高时改用 fwknop 的 SPA 方案:单个加密 UDP 包,无序列可嗅探,且具备防重放计数。
还需注意:knockd 只针对 TCP SYN 做判断,对 UDP 敲门支持有限。如果从 UDP 开始敲门,必须保证中间链路不做 SYN 代理或连接跟踪优化,否则序列会被合并或丢失。
FAQ 4:敲门成功后 SSH 为何仍暴露在扫描结果里
若干在线端口扫描服务会在一段时间内复用历史探测结果,敲门后放行的 30 秒窗口有概率被扫描器命中。降低该风险的做法:
- 把
cmd_timeout控制在 30 秒以内,够登录即可。 - 让 SSH 只监听回环或内网地址,对外仅通过 knockd 放行的规则配合端口转发,实现”端口本身不存在”的效果。
- 登录成功后立刻执行反向序列主动关门,不依赖超时自动收回。
总结
端口敲门解决的是”服务可见性”问题,而不是”身份可信”问题。本文给出的配置组合了三个关键控制点:SSH 默认 DROP、顺序 + 时间窗 + SYN 标志三重校验、cmd_timeout 自动收权,落地成本低于一台跳板机,防护收益高于单纯更换 SSH 端口。
实施时优先级建议为:先确认备用登录通道可用,再改防火墙默认策略,最后启用并验证 knockd;任何时候都不要在只有一个登录入口的前提下把 SSH 改成默认拒绝。若环境对隐匿性要求更高,应直接评估 fwknop 等单包授权方案,并配合 SSH 密钥认证、2FA 与 fail2ban 形成分层防御。