SSH 免密配置零散写过几次,这次收成一篇:生成密钥、装公钥、关密码登录、改端口。最容易翻车的是改完端口连不上,排查放在倒数第二节,按命中率排序。
本地生成密钥对
arduino
ssh-keygen -t ed25519 -C "你的邮箱或备注"
ed25519 密钥短,安全性不比 RSA 差,现在默认选它。碰上很老的服务器不认,再换 -t rsa -b 4096。
它会问你两件事:
- 存哪里。直接回车,默认
~/.ssh/id_ed25519。 - 要不要设密码短语(passphrase)。留空,以后用密钥不再输任何密码;电脑可能被别人碰的话就设一个。嫌每次输麻烦可以配 ssh-agent,这里不展开。
生成完 ~/.ssh/ 下多两个文件:id_ed25519 是私钥,留在本机,谁都不给;id_ed25519.pub 是公钥,往服务器上装。
把公钥装到服务器
有 ssh-copy-id 就用它,一条命令:
javascript
ssh-copy-id -i ~/.ssh/id_ed25519.pub 用户名@服务器IP
端口不是 22 就加 -p:
css
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 用户名@服务器IP
Windows 上没有这个命令,手动来。先看公钥内容:
bash
cat ~/.ssh/id_ed25519.pub
复制整行输出,登录服务器:
bash
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "刚才复制的公钥" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
测试免密登录
javascript
ssh -i ~/.ssh/id_ed25519 用户名@服务器IP
没问密码直接进去,就成了。还在问密码,多半是权限问题,看后面的权限那节。
关掉密码登录
密钥确认能用了,再把密码登录关掉,暴力破解的路就断了。编辑 /etc/ssh/sshd_config:
bash
sudo nano /etc/ssh/sshd_config
找到或加上:
yaml
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
PermitEmptyPasswords no
新一点的 OpenSSH 把第三项改名为 KbdInteractiveAuthentication,旧名字作为别名仍然有效,不用改。
PermitRootLogin 建议设 prohibit-password,root 只认密钥。用不上 root 远程登录的话,直接写 no。
重启服务:
bash
# Ubuntu / Debian
sudo systemctl restart ssh
# CentOS / RHEL
sudo systemctl restart sshd
改之前先确认密钥能登录。重启前多开一个终端保持现有连接别退,新配置验证没问题再关。这是改 sshd 配置的保命习惯。
改端口
同一个文件,找到 #Port 22,改成:
yaml
Port 2222
过渡期可以新旧两行同时留着,新端口能连上之后再删 22。
重启服务,然后放行防火墙:
- Ubuntu:
sudo ufw allow 2222/tcp - CentOS:
sudo firewall-cmd --permanent --add-port=2222/tcp && sudo firewall-cmd --reload - 云服务器另外要去控制台安全组放行,系统防火墙管不到那一层。
改完端口连不上,按命中率排查
九成出在下面几处。
1. 服务没重启
改完 sshd_config 必须重启。Ubuntu/Debian 服务名是 ssh,CentOS/RHEL 是 sshd,别写反。
2. Ubuntu 22.10+ 的 socket 激活
这些版本默认由 ssh.socket 监听端口,改了 sshd_config 之后要走这一套才生效:
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
daemon-reload 不能省,systemd 靠它重新按配置生成监听参数。要是执行完新端口还是不监听,说明这版系统的 socket 不会自己读 sshd_config,退回传统方式最省事:
bash
sudo systemctl disable --now ssh.socket
sudo systemctl enable --now ssh.service
3. 防火墙没放行
系统防火墙要单独开新端口。Ubuntu 用 sudo ufw allow 2222/tcp;CentOS 用:
css
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
4. SELinux 没加标签
CentOS/RHEL 开着 SELinux 的话,sshd 不能随便绑新端口:
arduino
sudo semanage port -l | grep ssh_port_t
sudo semanage port -a -t ssh_port_t -p tcp 2222
提示端口已存在就改用 -m。最小安装的系统没有 semanage 命令,先装:CentOS 7 装 policycoreutils-python,8 代之后的系统装 policycoreutils-python-utils。
5. 云安全组没放行
安全组和系统防火墙是两层。控制台里实例的入方向规则放行新端口 TCP,注意规则优先级,一条优先级更高的拒绝规则会把你加的允许规则盖掉。
6. macOS 是另一套
macOS 的 sshd 由 launchd 托管,端口写在 /etc/services,改 sshd_config 里的 Port 不生效:
bash
sudo vim /etc/services
把 ssh 22/tcp 和 ssh 22/udp 两行的 22 改掉,然后重启服务:
bash
sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist
sudo launchctl load /System/Library/LaunchDaemons/ssh.plist
7. 还不行就看日志
sudo journalctl -u ssh -n 50
绑定失败、权限问题、配置语法错,日志里都会写。
权限问题
SSH 对权限很挑,太宽直接拒绝用你的密钥,标准权限是:
bash
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519 # 私钥
chmod 644 ~/.ssh/id_ed25519.pub # 公钥
chmod 600 ~/.ssh/authorized_keys # 服务器上的授权文件
在 WSL 里操作 Windows 分区(比如 /mnt/c/...)上的密钥会报权限错,那是文件系统不支持 Linux 权限位,把密钥挪进 WSL 自己的家目录再设权限就行。
常见报错速查
| 报错 | 大概率原因 | 处理 |
|---|---|---|
| 一直提示输密码 | ~/.ssh/config 指错了密钥,或 authorized_keys 内容不完整 |
ssh -v 用户名@服务器IP 看详细日志 |
| Permission denied (publickey) | 公钥没装上,或服务器端权限不对 | 检查 authorized_keys 存在、内容完整、权限 600 |
| UNPROTECTED PRIVATE KEY FILE | 私钥权限太宽 | chmod 600 私钥 |
| Connection refused | sshd 没跑,或端口不对 | systemctl status ssh 看状态 |
最后配个别名
以上都通了,在本地 ~/.ssh/config 里写一段:
javascript
Host myserver
HostName 服务器IP
User 用户名
Port 2222
IdentityFile ~/.ssh/id_ed25519
chmod 600 ~/.ssh/config。以后连服务器就是 ssh myserver 一条命令,scp、rsync、走 SSH 的 Git 操作也全部跟着免密。