The whole idea in one sentence
The client private key and the public key in the remote authorized_keys must match — that is all. Debugging therefore revolves around two things: whether the pair matches, and whether the remote sshd is willing to trust that authorized_keys file (bad permissions make sshd silently refuse to read it).
ssh-keygen -t ed25519 -C "you@laptop" # 生成密钥对(一路回车即可)
ssh-copy-id user@server # 把公钥写入远端 authorized_keys
ssh user@server # 验证:不再要密码即成功The standard three steps and how to verify
① Generate a key: prefer ed25519 (shorter, faster, secure), defaulting to ~/.ssh/id_ed25519; ② Distribute: ssh-copy-id is the most reliable (it appends and fixes permissions); on Windows without it, manually append the key to the remote ~/.ssh/authorized_keys; ③ Verify: `ssh -o BatchMode=yes user@server echo ok` — BatchMode forbids interactive passwords, so a clean ok proves passwordless auth truly works, far more trustworthy than "no password prompt appeared".
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh user@server "cat >> ~/.ssh/authorized_keys"
# Windows 手动分发公钥的写法Gate 1: file permissions
sshd is extremely picky: if authorized_keys or .ssh is group-writable it silently ignores the whole file (no error, just a fallback to password auth). Correct values: ~/.ssh 700, authorized_keys 600, private key 600. Same on the Windows side — a too-open private key triggers UNPROTECTED PRIVATE KEY FILE and refuses to use it.
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519Gate 2: the home directory
The sneakier trap: the home directory itself being group/other-writable (common with shared mounts or legacy chmod 777 after manual useradd) — sshd refuses to trust authorized_keys all the same. Check with `ls -ld /home/user` and tighten to 750 or stricter. The checking order is always home → .ssh → file.
ls -ld /home/user # 家目录不能组/其他人可写
chmod 750 /home/user # 需要时收紧Gate 3: SELinux and the server log
On RHEL/CentOS with SELinux enforcing, a wrong security context on authorized_keys also fails silently (especially after manually mv/cp-ing the file). `restorecon -rv ~/.ssh` restores the standard context. For any stuck scenario there is one ultimate weapon: the server log `tail -f /var/log/secure` (Debian/Ubuntu: /var/log/auth.log) combined with a client-side -v reconnect — with both sides visible, the problem has nowhere to hide.
restorecon -rv ~/.ssh # SELinux 上下文修复
ssh -v user@server # 客户端详细握手过程
tail -f /var/log/secure # 服务端拒绝原因Level up: config aliases and multiple keys
Put frequent connections into ~/.ssh/config: one Host alias replaces a long command line and can bind a different key per server — much cleaner than typing -i every time, and git remotes and deploy scripts benefit equally.
Host prod
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/id_ed25519_prod
ServerAliveInterval 60
# 之后只需:ssh prod