云服务器SSH 端口变更与 rsync 密钥传输
rsync 自己不做身份认证,认证交给 SSH,所以「密钥对登录」就是把 ssh -i 私钥 塞进 rsync 的 -e。而 SSH 改端口则要看系统用的是传统 sshd.service 还是 systemd socket 激活——如果是后者,sshd_config 的 Port 是唯一真源,ssh.socket 由 sshd-socket-generator 自动派生。
本文记录一套完整可复现的流程,包括实测输出与排错表。
速查
| 场景 | 命令 |
|---|---|
| rsync 走密钥对 | rsync -avz -e "ssh -i ~/.ssh/key.pem" ./src/ root@IP:/dst/ |
| 写进 config 后 | rsync -avz ./src/ aliyun:/dst/ |
| 改 SSH 端口 | 改 sshd_config 的 Port → sshd -t → daemon-reload → restart ssh.socket ssh.service |
| 验证监听 | systemctl show ssh.socket -p Listen + ss -tlnp | grep 端口 |
| 外部验证 | 本地 Test-NetConnection -ComputerName IP -Port 2233 |
| 失联兜底 | 阿里云控制台 VNC 远程连接(不走 SSH) |
三条铁律
- 先开安全组,再改服务端,顺序反了就是自锁。
- 改完
sshd_config必须先sshd -t,配置写坏会导致 generator 不生成、静默回落 22。 - 永远保持至少一个非默认端口,否则可能生成空的
ListenStream=导致无人监听。
1. rsync 使用密钥对
1.1 原理
rsync 是纯传输工具,不做认证。它把连接交给「远程 shell」(默认 SSH)。所以任何 SSH 的参数都能通过 -e 传进去:
rsync -avz --progress \
-e "ssh -i ~/.ssh/aliyun.pem -o IdentitiesOnly=yes" \
./dist/ root@47.98.x.x:/var/www/html/
-o IdentitiesOnly=yes:防止 SSH 先去试 agent 里的其它 key,避免「公钥太多被拒」。- 改端口用 SSH 自己的
-p(小写),别和 rsync 的-p/--perms混淆:-e "ssh -p 2233 -i ~/.ssh/aliyun.pem"。
路径结尾的斜杠是 rsync 第一大坑
| 写法 | 含义 |
|---|---|
./dist/ |
把 dist 里面的内容同步到目标目录 |
./dist |
把 dist 这个目录本身放进目标目录,结果是 .../html/dist/... |
1.2 推荐:写进 ~/.ssh/config
Host aliyun
HostName 47.98.x.x
User root
Port 2233
IdentityFile ~/.ssh/aliyun.pem
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 6
之后命令干净到只剩业务参数,ssh / scp / git 全部复用同一份配置:
rsync -avz --delete ./dist/ aliyun:/var/www/html/
1.3 私钥权限
控制台下载的 .pem 在 Windows 上通常是「所有人可读」,OpenSSH 直接拒绝:
Permissions for 'aliyun.pem' are too open.
WSL / Linux / macOS:
chmod 600 ~/.ssh/aliyun.pem # 400 也可以
WSL 里的隐藏坑
私钥如果留在 /mnt/c/...,chmod 无效(DrvFs 固定 777)。必须拷进 Linux 文件系统:
mkdir -p ~/.ssh && cp /mnt/c/Users/you/Downloads/aliyun.pem ~/.ssh/ && chmod 600 ~/.ssh/aliyun.pem原生 Windows OpenSSH(PowerShell):
icacls aliyun.pem /inheritance:r /grant:r "$($env:USERNAME):(R)"
1.4 阿里云侧的 5 个检查点
- 登录名:Linux 默认
root;创建实例时选了非 root 登录名则是ecs-user。用ecs-user写/var/www这类目录要提权:rsync -avz --rsync-path="sudo rsync" ./dist/ aliyun:/var/www/html/ - 绑定密钥对需重启实例生效(控制台换绑/解绑同理)。
- 安全组入方向放行 SSH 端口,授权对象最好只填自己的 IP。
- 对端要装 rsync:
rsync --version,没有就sudo yum install -y rsync/sudo apt install -y rsync。 - 密钥类型:控制台导入支持
rsa/dsa/ssh-rsa/ssh-dss/ecdsa(不含 ed25519)。用ssh-keygen -y -f 私钥反查公钥内容。
1.5 常用参数
| 参数 | 作用 |
|---|---|
-a |
归档模式(递归 + 保留权限/时间/软链),基本必带 |
-v / -z |
详细输出 / 传输压缩(文本划算,已压缩文件无益) |
-P |
= --partial --progress,断点续传 + 进度,传大文件必带 |
-n |
dry-run,改 --delete 前一定先跑一次 |
--delete |
目标端删除源端已不存在的文件(镜像同步,危险) |
--exclude='.git' / --exclude-from=file |
排除 |
--bwlimit=2M |
限速 |
--rsync-path="sudo rsync" |
对端用 sudo 跑 rsync |
2. SSH 改端口
2.1 先判断服务模式
systemctl status ssh.socket ssh.service 2>/dev/null
systemctl cat ssh.socket
ss -tlnp | grep -E 'sshd|:22\b'
| 模式 | 特征 | 谁监听 | 改哪里 |
|---|---|---|---|
| 传统 | 只有 sshd.service / ssh.service 常驻 |
sshd 自己 | sshd_config 的 Port |
socket(Accept=yes) |
有 ssh@.service,每个连接一个进程 |
systemd | ssh.socket 的 ListenStream |
socket(Accept=no) |
ssh.service 常驻,TriggeredBy: ssh.socket |
systemd 建 socket,fd 传给 sshd | 看有没有 generator ↓ |
Accept=no 的指纹:ss -tlnp 里同一个 socket 同时挂在 systemd(pid 1) 和 sshd 上:
LISTEN 0 4096 0.0.0.0:2233 0.0.0.0:* users:(("sshd",pid=1269022,fd=3),("systemd",pid=1,fd=76))
↑ 共享 fd,不是各自监听
2.2 决定性诊断:有没有 sshd-socket-generator
ls -l /usr/lib/systemd/system-generators/ /lib/systemd/system-generators/ 2>/dev/null | grep -i ssh
ls -l /run/systemd/generator/ssh.socket.d/ 2>/dev/null
sudo sshd -T | grep -E '^(port|listenaddress)'
- 有
sshd-socket-generator→ 改sshd_config就够了(generator 自动翻译成ListenStream) - 没有 → 必须手改
ssh.socket
2.3 自动同步链路(有 generator)
flowchart LR
A["/etc/ssh/sshd_config<br/>Port 2233"] -->|daemon-reload| B["sshd-socket-generator<br/>(内部调用 sshd -T 解析)"]
B -->|写入| C["/run/systemd/generator/<br/>ssh.socket.d/addresses.conf"]
C --> D["ssh.socket<br/>持有监听 fd"]
D -->|SD_LISTEN_FDS 传递 fd| E["ssh.service<br/>sshd -D 常驻"]
E --> F["客户端连接"]
操作(三步)
# 1. 改配置(drop-in 优先于主文件,因为 Include 在顶部,sshd 是"先出现者生效")
sudo mkdir -p /etc/ssh/sshd_config.d
printf 'Port 2233\n' | sudo tee /etc/ssh/sshd_config.d/99-ssh-port.conf
# 2. 校验 + 触发 generator
sudo sshd -t
sudo systemctl daemon-reload
# 3. 看生成结果(关键验证)
cat /run/systemd/generator/ssh.socket.d/addresses.conf
# 4. 重启
sudo systemctl restart ssh.socket ssh.service
生成结果应形如:
# Automatically generated by sshd-socket-generator
[Socket]
ListenStream=
ListenStream=0.0.0.0:2233
ListenStream=[::]:2233
2.4 手改 ssh.socket(无 generator)
sudo systemctl edit ssh.socket
[Socket]
ListenStream=
ListenStream=0.0.0.0:2233
ListenStream=[::]:2233
必须写两条 ListenStream
若基础单元含 BindIPv6Only=ipv6-only,[::] 那条 socket 不接收 IPv4 连接,基础单元靠显式多写一条 0.0.0.0:22 补齐 IPv4。
只写一条 ListenStream=2233 → 变成「仅 [::]:2233 且 ipv6-only」→ IPv4 全部失联,且表象是「改了端口但连不上」,极难排查。
- 第一行空值
ListenStream=是清空继承的监听列表,不写则新旧端口同时监听。 BindIPv6Only/FreeBind不用重写,drop-in 只覆盖显式设置的指令。- 过渡期想双端口并存:别写清空行,直接写四条
ListenStream。
2.5 生效与验证
sudo systemctl restart ssh.socket ssh.service # reload 无效!见下
systemctl show ssh.socket -p Listen
ss -tlnp | grep 2233
sudo sshd -T | grep -E '^(port|listenaddress)'
systemctl reload ssh 永远不会改端口
reload 只是发 SIGHUP 让 sshd re-exec,而复用的是 systemd 给的那个 fd。必须 restart socket。
外部验证(ss 只证明服务端在听,证明不了安全组放行):
Test-NetConnection -ComputerName 47.98.x.x -Port 2233
ssh -p 2233 -i ~/.ssh/aliyun.pem root@47.98.x.x
在服务器上 ssh -p 2233 root@127.0.0.1 成功没有意义,那走 lo 接口,完全绕过安全组。
2.6 收尾:关掉旧端口
sudo grep -rn -E '^\s*Port\s' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/ # 先定位
sudo cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F) # 备份
# 注释掉 Port 22,只留 Port 2233
sudo sshd -t && sudo systemctl daemon-reload
cat /run/systemd/generator/ssh.socket.d/addresses.conf # 确认只剩 2233
sudo systemctl restart ssh.socket ssh.service
验证新连接稳定后,再回阿里云控制台删掉 22 的安全组规则。
2.7 安全性检查
systemctl cat ssh.service | grep -iE 'killmode|execstart' # KillMode=process → 重启不踢现有会话
sudo ufw status
ufw status: inactive 的含义
主机侧没有任何防火墙,访问控制 100% 依赖阿里云安全组。云上常见做法,但要知悉。
将来若启用 ufw,必须先放行新端口再 enable:
sudo ufw allow 2233/tcp
sudo ufw enable # 最后一步其他连带影响
- 阿里云控制台 Workbench 远程连接走 SSH 22,改端口后失效(部分版本可指定端口)。
- VNC 管理终端不走 SSH,永远可用——唯一的兜底通道。
- 别忘了:fail2ban 的
port、CI/CD 部署脚本、监控探针、Ansible 的ansible_port、宝塔面板、iptables 旧规则。
2.8 排错表
| 现象 | 原因 |
|---|---|
改了 sshd_config 的 Port 没反应 |
socket 激活下 Port 由 generator 翻译,需 daemon-reload;无 generator 则要手改 ssh.socket |
| 新旧端口同时监听 | drop-in 里漏写了清空行 ListenStream= |
| 改了端口但 IPv4 连不上 | BindIPv6Only=ipv6-only 下只写了 [::],漏写 0.0.0.0: |
| 改了配置后完全没监听 | sshd -t 失败 → generator 不生成 → 空 ListenStream= 清空了列表 |
reload 后端口没变 |
正常,必须 restart ssh.socket |
ssh.socket 起不来 Address already in use |
旧进程占用,或 drop-in 端口重复 |
| 安全组加了还是连不上 | ufw 没放行 / 授权对象 IP 不对(家宽 IP 会变) |
| Workbench 连不上 | 走 SSH 22,改用 VNC |
no mutual signature algorithm |
老服务端 + RSA/SHA-1:-o PubkeyAcceptedAlgorithms=+ssh-rsa -o HostKeyAlgorithms=+ssh-rsa |
Permission denied (publickey) |
用户名错 / 公钥没进 authorized_keys / 用错私钥 |
2.9 兜底与回滚
阿里云控制台 → VNC 远程连接登录:
sudo cp -a /etc/ssh/sshd_config.bak.$(date +%F) /etc/ssh/sshd_config
sudo sshd -t
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket ssh.service
或删掉手写的 drop-in:
sudo rm -f /etc/systemd/system/ssh.socket.d/*.conf /etc/ssh/sshd_config.d/99-ssh-port.conf
sudo systemctl daemon-reload && sudo systemctl restart ssh.socket ssh.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run:sudo touch /etc/ssh/sshd_not_to_be_run 可让 sshd 彻底不监听。
3. 本机实测记录
3.1 环境事实
/usr/lib/systemd/system/ssh.socket:
[Unit]
Description=OpenBSD Secure Shell server socket
Before=sockets.target ssh.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
[Socket]
ListenStream=0.0.0.0:22
ListenStream=[::]:22
BindIPv6Only=ipv6-only
Accept=no
FreeBind=yes
[Install]
WantedBy=sockets.target
RequiredBy=ssh.service
ssh.service 显示 disabled 但 active (running)、TriggeredBy: ● ssh.socket——这是正常的,不是配置坏了。
RequiredBy=ssh.service 是 [Install] 指令,enable 时会在 /etc/systemd/system/ssh.service.requires/ 建符号链接,于是 ssh.service 反向依赖 ssh.socket。
因此不推荐简单的 disable ssh.socket
顺序必须是:disable ssh.socket → 确认 .requires 链接消失 → enable/restart ssh.service。只 mask ssh.socket 而不清依赖链接,ssh.service 会因缺 Requires 直接启动失败。
systemctl disable ssh.socket
ls -l /etc/systemd/system/ssh.service.requires/ # 应无 ssh.socket
sudo systemctl enable --now ssh.service
systemctl show ssh.service -p Requires3.2 诊断输出
$ ls -l /usr/lib/systemd/system-generators/ | grep -i ssh
-rwxr-xr-x 1 root root 482376 Sep 1 02:38 sshd-socket-generator
$ ls -l /run/systemd/generator/ssh.socket.d/
-rw-r--r-- 1 root root 169 Oct 10 19:55 addresses.conf
$ sudo sshd -T | grep -E '^(port|listenaddress)'
port 22
port 2233
listenaddress [::]:22
listenaddress 0.0.0.0:22
listenaddress [::]:2233
listenaddress 0.0.0.0:2233
$ cat /run/systemd/generator/ssh.socket.d/addresses.conf
# Automatically generated by sshd-socket-generator
[Socket]
ListenStream=
ListenStream=0.0.0.0:2233
ListenStream=[::]:2233
ListenStream=0.0.0.0:22
ListenStream=[::]:22
$ systemctl show ssh.socket -p Listen
Listen=0.0.0.0:2233 (Stream)
Listen=[::]:2233 (Stream)
Listen=0.0.0.0:22 (Stream)
Listen=[::]:22 (Stream)
$ ss -tlnp | grep 2233
LISTEN 0 4096 0.0.0.0:2233 0.0.0.0:* users:(("sshd",pid=1269022,fd=3),("systemd",pid=1,fd=76))
LISTEN 0 4096 [::]:2233 [::]:* users:(("sshd",pid=1269022,fd=4),("systemd",pid=1,fd=77))
结论:sshd_config → generator → ssh.socket 自动同步链路验证通过。此后改端口只需改 sshd_config 一处。
3.3 待办清单
- 本地
Test-NetConnection -ComputerName <IP> -Port 2233通 - 阿里云安全组确认有
TCP 2233(授权对象限自己 IP) -
sshd_config注释掉Port 22 -
sshd -t→daemon-reload→ 确认addresses.conf只剩 2233 →restart - 本地
ssh -p 2233成功 - 删除安全组 22 规则
- 更新本地
~/.ssh/config的Port 2233 - 检查 fail2ban(若有)的
port设置 - 确认阿里云 Workbench 是否需要改用 VNC
4. 参考
- 实例登录凭证管理(登录名/密码/密钥对) - 阿里云 ECS
- openssh-server should ship a systemd generator… (Launchpad #1991592) ——
sshd-socket-generator的来源与设计讨论 - 理解 SSH 改端口不生效的问题及解决方案
man 5 sshd_config、man 5 systemd.socket、man 8 sshd(socket activation 章节)