ssh-copy-id 命令详解与参数实测(Ubuntu 22.04)
以 Ubuntu 22.04(openssh-client 8.9p1)实测逐参数讲解 ssh-copy-id 命令:-i 指定公钥、-p 端口、-n dry-run、-f 强制、-s sftp、-o 传 SSH 选项与不带 -i 的默认行为,配合本机 sshd 口令认证完整演示公钥安装、authorized_keys 落盘验证、免密登录、重复安装幂等与 known_hosts 指纹冲突处置,全部命令与回显实测通过。
ssh-copy-id 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 ssh-copy-id 的常用参数——
-i指定公钥、-p指定端口、-ndry-run 预演、-f强制模式、-s走 sftp、-o传递 SSH 选项,以及不带-i时的默认选钥行为;完整走通「启动 sshd → 建演示用户 → 口令交互安装公钥 → authorized_keys 落盘 → 免密登录验证 → 重复安装幂等 → 指纹冲突与修复」全链路,全部命令与回显已在 Ubuntu 22.04(OpenSSH 8.9p1)实测通过。
一、环境信息
本文所有命令均在以下真实服务器环境逐条执行并记录回显,读者可按相同条件复现:
- 操作系统:Ubuntu 22.04.5 LTS,架构 x86_64,root 用户执行;
- 工具版本:ssh-copy-id 与 ssh 同属
openssh-client包(版本1:8.9p1-3ubuntu0.18);服务端openssh-server同版本已安装; - 服务端准备:本机执行
mkdir -p /run/sshd && /usr/sbin/sshd启动 SSH 服务(默认端口 22,另起-p 2222实例做端口实验),监听状态用ss -tlnp验证; - 演示用户:
sshu1(uid=1019,初始口令Test12345,仅用于本机回环实验); - 演示密钥:
/root/demo/ssh/下的各类型密钥对(由《ssh-keygen 命令详解与参数实测》一文 3.2~3.7 实测生成,全部无口令,注释形如demo-ed25519@test这类以 demo- 开头的标记); - 本机地址
172.17.0.2,sshd 监听 0.0.0.0:22 与 0.0.0.0:2222,所有安装与验证都走127.0.0.1回环。
💡 说明:
Test12345与演示密钥均为受控测试环境的实验凭据,不用于任何真实主机;文中出现的密钥指纹/公钥体都是本机回环实验的一手实测数据。
二、命令概览
2.1 语法与测试数据
ssh-copy-id 是 openssh-client 自带的辅助脚本:把本地公钥追加到远端主机的 ~/.ssh/authorized_keys(自动建目录、改权限、去重检查),实现后续免密登录。语法(取自实测 usage 原文):
Usage: /usr/bin/ssh-copy-id [-h|-?|-f|-n|-s] [-i [identity_file]] [-p port] [-F alternative ssh_config file] [[-o <ssh -o options>] ...] [user@]hostname
-f: force mode -- copy keys without trying to check if they are already installed
-n: dry run -- no keys are actually copied
-s: use sftp -- use sftp instead of executing remote-commands. Can be useful if the remote only allows sftp
-h|-?: print this help
[user@]hostname:必填,目标主机(本机实验用sshu1@127.0.0.1);-i [identity_file]:指定公钥文件(省略文件名时默认取~/.ssh/*.pub,实测 3.12);-o <ssh -o options>:原样传给底层的 ssh 命令(可多次出现),控制认证方式、超时等;- 工作流程(从回显反推):先打印
INFO: Source of key(s)列出候选公钥 →INFO: attempting to log in with the new key(s), to filter out any that are already installed(用新公钥试登一次做去重过滤)→ 提示输入口令 → 远端执行追加命令 → 报告Number of key(s) added。
本文测试数据约定:服务端为 127.0.0.1:22/2222 的本机 sshd,用户 sshu1,安装 /root/demo/ssh/ 下的各公钥;known_hosts 冲突实验通过轮换 /etc/ssh/ssh_host_ed25519_key 主机钥制造。
2.2 usage 全量输出(无参数 / -h 形态,逐行照抄)
无参数执行与 -h 都打印同一段 usage(stderr),退出码均为 1。实测:
ssh-copy-id
ssh-copy-id -h
实测回显(两形态输出逐行一致):
$ ssh-copy-id
Usage: /usr/bin/ssh-copy-id [-h|-?|-f|-n|-s] [-i [identity_file]] [-p port] [-F alternative ssh_config file] [[-o <ssh -o options>] ...] [user@]hostname
-f: force mode -- copy keys without trying to check if they are already installed
-n: dry run -- no keys are actually copied
-s: use sftp -- use sftp instead of executing remote-commands. Can be useful if the remote only allows sftp
-h|-?: print this help
退出码=1
$ ssh-copy-id -h
Usage: /usr/bin/ssh-copy-id [-h|-?|-f|-n|-s] [-i [identity_file]] [-p port] [-F alternative ssh_config file] [[-o <ssh -o options>] ...] [user@]hostname
-f: force mode -- copy keys without trying to check if they are already installed
-n: dry run -- no keys are actually copied
-s: use sftp -- use sftp instead of executing remote-commands. Can be useful if the remote only allows sftp
-h|-?: print this help
退出码=1
💡 要点:usage 短小(只列 -f/-n/-s/-h 四个自维护选项,其余选项直接透传给 ssh),完整行为要结合 ssh(1) 手册看;注意它没有 --help 长选项形态。
2.3 工具归属与版本
which ssh-copy-id
dpkg -S /usr/bin/ssh-copy-id
dpkg -l openssh-client | tail -1
实测回显:
$ which ssh-copy-id
/usr/bin/ssh-copy-id
$ dpkg -S /usr/bin/ssh-copy-id
openssh-client: /usr/bin/ssh-copy-id
$ dpkg -l openssh-client | tail -1
ii openssh-client 1:8.9p1-3ubuntu0.18 amd64 secure shell (SSH) client, for secure access to remote machines
💡 要点:ssh-copy-id 归属 openssh-client(客户端侧工具),装了客户端即有;它本身是脚本(可直接 cat /usr/bin/ssh-copy-id 读源码),无独立版本号,版本随 openssh-client。
三、参数逐项实测
3.1 服务端准备:启动 sshd 并验证监听
实测环境从零起服务:先确认 22 端口无监听,再启动(Debian/Ubuntu 手工启动 sshd 前需先建 /run/sshd 特权分离目录),最后 ss -tlnp 验证:
ss -tlnp | grep ':22 ' || echo '22 端口无监听'
mkdir -p /run/sshd && /usr/sbin/sshd
ss -tlnp | grep ':22 '
实测回显:
$ ss -tlnp | grep ':22 ' || echo '22 端口无监听'
22 端口无监听
$ mkdir -p /run/sshd && /usr/sbin/sshd
退出码=0
$ ss -tlnp | grep ':22 '
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=26429,fd=3))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=26429,fd=4))
💡 要点:mkdir -p /run/sshd 缺失时 sshd 会报 Missing privilege separation directory: /run/sshd 拒绝启动——手工起 sshd 的固定前置步骤;启动命令本身无输出,退出码 0,是否成功看 ss 监听(IPv4/IPv6 各一行)。
3.2 演示用户准备:useradd / chpasswd / 验证
useradd -m -s /bin/bash sshu1
echo 'sshu1:Test12345' | chpasswd
passwd -S sshu1
id sshu1
实测回显:
$ useradd -m -s /bin/bash sshu1
退出码=0
$ echo 'sshu1:Test12345' | chpasswd
$ passwd -S sshu1
sshu1 P 10/16/2026 0 99999 7 -1
$ id sshu1
uid=1019(sshu1) gid=3109(sshu1) groups=3109(sshu1)
💡 要点:-m 建家目录(安装公钥的目标位置 /home/sshu1/.ssh/);passwd -S 的 P 表示已设口令;chpasswd 从 stdin 读 用户:口令 是脚本设初始口令的标准写法。
3.3 主线安装:指定公钥 + 口令交互(-i 与安装全流程)
向 sshu1 安装 Ed25519 公钥。-i 指定公钥,-o StrictHostKeyChecking=no 免去首次连接的指纹确认(回环实验简化),口令通过 tty 喂入(见 5 章 Q1 的坑):
ssh-copy-id -i /root/demo/ssh/id_ed25519.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
实测回显(口令处喂入 Test12345):
$ ssh-copy-id -i /root/demo/ssh/id_ed25519.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/demo/ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
sshu1@127.0.0.1's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh -o 'StrictHostKeyChecking=no' 'sshu1@127.0.0.1'"
and check to make sure that only the key(s) you wanted were added.
退出码=0
流程解读:INFO: Source of key(s) 报出候选公钥 → 第二条 INFO 是过滤阶段(用这把钥试登一次,已装过的会被剔除)→ 1 key(s) remain to be installed 后索要口令 → 成功报 Number of key(s) added: 1 并给出验证命令提示。
💡 要点:-i 指定的 .pub 文件必须存在(且 3.11 的同名私钥检查);Now try logging... 提示里的命令是官方推荐的验证方式,实测见 3.5。
3.4 服务端落盘验证:authorized_keys 内容与权限
ls -la /home/sshu1/.ssh/
cat /home/sshu1/.ssh/authorized_keys
实测回显:
$ ls -la /home/sshu1/.ssh/
total 12
drwx------ 2 sshu1 sshu1 4096 Oct 16 14:22 .
drwxr-x--- 4 sshu1 sshu1 4096 Oct 16 14:22 ..
-rw------- 1 sshu1 sshu1 102 Oct 16 14:22 authorized_keys
$ cat /home/sshu1/.ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOLSfhHEnAE0hrdxkNl2eM4qI7jzU6PbMjvcW96aZF9f renamed-comment@test
💡 要点:ssh-copy-id 自动完成了目录创建(.ssh 700)与权限收敛(authorized_keys 600),属主切到目标用户——这三件事是手工配置免密最容易做错导致不生效的部分(服务端 StrictModes 检查权限,见 Q5);公钥行原样落盘(类型 + 公钥体 + 注释)。
3.5 免密验证:BatchMode 登录(必须用对应私钥)
装钥后验证免密,用 BatchMode 禁用一切交互提示,-i 指定刚安装公钥对应的私钥,IdentitiesOnly 锁定只用这一把:
ssh -i /root/demo/ssh/id_ed25519 -o IdentitiesOnly=yes -o BatchMode=yes sshu1@127.0.0.1 id
实测回显:
$ ssh -i /root/demo/ssh/id_ed25519 -o IdentitiesOnly=yes -o BatchMode=yes sshu1@127.0.0.1 id
uid=1019(sshu1) gid=3109(sshu1) groups=3109(sshu1)
退出码=0
对照:验证时不指定私钥(默认身份不在服务端 authorized_keys 里)会失败:
$ ssh -o BatchMode=yes sshu1@127.0.0.1 id
sshu1@127.0.0.1: Permission denied (publickey,password).
退出码=255
💡 要点:-i 公钥 安装、-i 私钥 验证是配套动作;BatchMode 下连接失败即报 Permission denied (publickey,password)——括号里是服务端放行的认证方式清单。免密验证三件套:-i 对应私钥 + IdentitiesOnly=yes(防止 agent/默认身份干扰)+ BatchMode=yes(绝不弹口令提示)。
3.6 重复安装的幂等:All keys were skipped
同一把钥再次安装(对照 3.3):
ssh-copy-id -i /root/demo/ssh/id_ed25519.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
实测回显:
$ ssh-copy-id -i /root/demo/ssh/id_ed25519.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/demo/ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: WARNING: All keys were skipped because they already exist on the remote system.
(if you think this is a mistake, you may want to use -f option)
退出码=0
💡 要点:过滤阶段发现公钥已存在,跳过全部安装(WARNING + 建议 -f),退出码仍是 0,且服务端 authorized_keys 不产生重复行(实测 grep 计数为 1)。幂等性使 ssh-copy-id 可以放心重复执行;要「强制再来一份」才用 -f(3.10)。
3.7 -n:dry-run 预演,不落盘
ssh-copy-id -n -i /root/demo/ssh/id_ecdsa.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
实测回显(口令已通过免密链路完成过滤阶段认证):
$ ssh-copy-id -n -i /root/demo/ssh/id_ecdsa.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/demo/ssh/id_ecdsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
=-=-=-=-=-=-=-=
Would have added the following key(s):
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBG5gRTqG3NNjTGqCPJNxjI2k3K8TOp39xKAGxIqm9wkvG71C22Tl0lohGMFOpOnZwI8QqW3+DIskQ9cBUcKWP3M= demo-ecdsa@test
=-=-=-=-=-=-=-=
退出码=0
服务端确认未落盘(grep -c 计数为 0,ecdsa 公钥未落盘(-n 生效)):
$ grep -c 'ecdsa-sha2' /home/sshu1/.ssh/authorized_keys || echo 'ecdsa 公钥未落盘(-n 生效)'
0
ecdsa 公钥未落盘(-n 生效)
💡 要点:-n 会真实登录远端执行「读取 authorized_keys」的过滤动作(所以仍需认证),但对「写入」环节 dry-run——输出 Would have added the following key(s) 预演块。变更审计前的标准动作:先 -n 看清单,确认无误再去掉 -n 实装。
3.8 -o:传递 SSH 选项(强制口令认证安装)
-o 可多次出现,逐项透传给底层 ssh。实测禁用公钥认证、强制口令认证(验证「走口令装钥」的最小依赖形态):
ssh-copy-id -i /root/demo/ssh/id_ed25519_quiet.pub -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -o PreferredAuthentications=password sshu1@127.0.0.1
实测回显(口令处喂入 Test12345):
$ ssh-copy-id -i /root/demo/ssh/id_ed25519_quiet.pub -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -o PreferredAuthentications=password sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/demo/ssh/id_ed25519_quiet.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
sshu1@127.0.0.1's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh -o 'StrictHostKeyChecking=no' -o 'PubkeyAuthentication=no' -o 'PreferredAuthentications=password' 'sshu1@127.0.0.1'"
and check to make sure that only the key(s) you wanted were added.
退出码=0
💡 要点:Now try logging 提示行会原样回显你传的全部 -o 选项(本例三个),可据此确认参数透传无误;常见用途还有 -o ConnectTimeout=5(慢链路探测)与 -o ProxyJump=跳板机(经跳板安装)。
3.9 -p:指定非标准端口
服务端另起 2222 端口实例,安装走 -p 2222:
/usr/sbin/sshd -p 2222
ss -tlnp | grep ':2222 '
ssh-copy-id -i /root/demo/ssh/id_rsa2048.pub -p 2222 -o StrictHostKeyChecking=no sshu1@127.0.0.1
实测回显:
$ /usr/sbin/sshd -p 2222
退出码=0
$ ss -tlnp | grep ':2222 '
LISTEN 0 128 0.0.0.0:2222 0.0.0.0:* users:(("sshd",pid=26784,fd=3))
LISTEN 0 128 [::]:2222 [::]:* users:(("sshd",pid=26784,fd=4))
$ ssh-copy-id -i /root/demo/ssh/id_rsa2048.pub -p 2222 -o StrictHostKeyChecking=no sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/demo/ssh/id_rsa2048.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
sshu1@127.0.0.1's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh -p '2222' -o 'StrictHostKeyChecking=no' 'sshu1@127.0.0.1'"
and check to make sure that only the key(s) you wanted were added.
退出码=0
2222 端口免密验证(-p 与 -i 配套):
$ ssh -i /root/demo/ssh/id_rsa2048 -o IdentitiesOnly=yes -o BatchMode=yes -p 2222 sshu1@127.0.0.1 hostname
f1034b9326b2
退出码=0
💡 要点:-p 2222 转化为 ssh 的 -p,提示行 ssh -p '2222' ... 可佐证;验证命令也必须带 -p 2222。注意 22 与 2222 是两个独立 sshd 实例(两个 pid),authorized_keys 是同一用户的同一份文件。
3.10 -f:强制模式(跳过已装检查直接复制)
-f 跳过「过滤阶段」——回显里没有 attempting to log in ... to filter out 与 remain to be installed 两行,直接进入安装。实测对 DSA 公钥强制安装:
ssh-copy-id -f -i /root/demo/ssh/id_dsa.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
实测回显(口令处喂入 Test12345):
$ ssh-copy-id -f -i /root/demo/ssh/id_dsa.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/demo/ssh/id_dsa.pub"
sshu1@127.0.0.1's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh -o 'StrictHostKeyChecking=no' 'sshu1@127.0.0.1'"
and check to make sure that only the key(s) you wanted were added.
退出码=0
💡 要点:-f 与 -i 组合还解除了「同名私钥存在性」检查(3.11 的报错在 -f 下不发生);对已存在的公钥 -f 会再装一份(authorized_keys 出现重复行,远端认证不受影响但文件变脏),所以日常优先靠幂等过滤,-f 只在明确需要时使用。
3.11 -i 的同名私钥检查与多公钥安装形态
-i 指向的 .pub 若没有同名私钥(或文件不存在),报错拒绝。实测三种形态:
cat /root/demo/ssh/id_ecdsa.pub /root/demo/ssh/id_rsa4096.pub > /root/demo/ssh/bundle.pub
ssh-copy-id -i /root/demo/ssh/bundle.pub sshu1@127.0.0.1
ssh-copy-id -i /root/demo/ssh/no-such-key.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
ssh-copy-id -f -i /root/demo/ssh/bundle.pub sshu1@127.0.0.1
实测回显:
$ ssh-copy-id -i /root/demo/ssh/bundle.pub sshu1@127.0.0.1
/usr/bin/ssh-copy-id: ERROR: failed to open ID file '/root/demo/ssh/bundle': No such file
(to install the contents of '/root/demo/ssh/bundle.pub' anyway, look at the -f option)
退出码=1
$ ssh-copy-id -i /root/demo/ssh/no-such-key.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
/usr/bin/ssh-copy-id: ERROR: failed to open ID file '/root/demo/ssh/no-such-key.pub': No such file
退出码=1
$ ssh-copy-id -f -i /root/demo/ssh/bundle.pub sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/demo/ssh/bundle.pub"
Number of key(s) added: 2
Now try logging into the machine, with: "ssh 'sshu1@127.0.0.1'"
and check to make sure that only the key(s) you wanted were added.
退出码=0
💡 要点:
- ssh-copy-id 的
-i实际是「identity file(私钥路径)」语义:它先检查同名私钥(.pub去掉扩展名)是否存在——过滤阶段要用私钥试登;只有.pub没有对应私钥时(合并多钥的 bundle 场景)报错并明确建议-f; .pub文件本身不存在时报错不带括号提示;- 多公钥的正确安装形态 =
cat 多个.pub > bundle.pub+-f -i bundle.pub,一次装进 2 把(Number of key(s) added: 2);服务端 authorized_keys 每把钥一行。
3.12 不带 -i:默认取 ~/.ssh/id_rsa.pub(实测只选一个)
ls -l /root/.ssh/*.pub
ssh-copy-id -o StrictHostKeyChecking=no sshu1@127.0.0.1
实测回显(~/.ssh 下有两个 .pub,但默认只装了其一):
$ ls -l /root/.ssh/*.pub
-rw-r--r-- 1 root root 571 Oct 16 14:18 /root/.ssh/id_rsa.pub
-rw-r--r-- 1 root root 99 Oct 15 19:58 /root/.ssh/perm_demo.pub
$ ssh-copy-id -o StrictHostKeyChecking=no sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
sshu1@127.0.0.1's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh -o 'StrictHostKeyChecking=no' 'sshu1@127.0.0.1'"
and check to make sure that only the key(s) you wanted were added.
退出码=0
💡 要点:省略 -i 时默认身份文件是 ~/.ssh/id_rsa.pub——实测只选了这一个(哪怕同目录还有其他 .pub),不是想象中的「全部公钥」。要把某个 .pub 装上去,明确写 -i 最稳妥。
3.13 -s:走 sftp 通道安装
远端只允许 sftp(shell 被禁)时用 -s,安装动作改由 sftp 完成。实测(本环境 shell 正常,展示可用性与幂等过滤的兼容):
ssh-copy-id -s -i /root/demo/ssh/id_ed25519_quiet.pub sshu1@127.0.0.1
实测回显:
$ ssh-copy-id -s -i /root/demo/ssh/id_ed25519_quiet.pub sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/demo/ssh/id_ed25519_quiet.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: WARNING: All keys were skipped because they already exist on the remote system.
(if you think this is a mistake, you may want to use -f option)
退出码=0
💡 要点:-s 模式同样先做过滤(该钥已在 3.8 装过,WARNING 跳过);远端若禁了 shell,普通模式会在安装阶段失败,-s 则可用。日常环境两种模式结果一致。
3.14 技术拓展:ssh-copy-id 与手工部署 authorized_keys 对比
| 维度 | ssh-copy-id(本文主角) | 手工 cat pub >> authorized_keys |
|---|---|---|
| 远端目录/文件自动创建 | ✔(.ssh 700 自动建) | 需自己 mkdir -p ~/.ssh && chmod 700 |
| 权限自动收敛 | ✔(authorized_keys 600,属主切换) | 易漏(漏了会被 sshd StrictModes 拒绝) |
| 已装去重 | ✔(过滤阶段跳过,幂等) | 无(重复追加) |
| dry-run 预演 | ✔(-n) | 无 |
| 依赖 | 需口令/已有免密通道可达远端 | 需先有其他登录通道(控制台/带外) |
| 适用场景 | 常规装钥、批量初始化 | 带外救援、authorized_keys 手术 |
💡 要点:两条路都要会——正常装钥用 ssh-copy-id(幂等 + 权限收尾齐全);服务端 sshd 配置坏了、带外控制台登录时才手工改 authorized_keys。
四、实战示例
4.1 场景一:新用户免密开通(建户→造钥→装钥→验证)
服务器初始化新开发账号的完整闭环(口令交互通过 tty 喂入):
useradd -m -s /bin/bash sshu1 && echo 'sshu1:Test12345' | chpasswd
ssh-copy-id -i /root/demo/ssh/id_ed25519.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1
ssh -i /root/demo/ssh/id_ed25519 -o IdentitiesOnly=yes -o BatchMode=yes sshu1@127.0.0.1 id
实测回显(第三步为 3.5 实测原文;前两步见 3.2/3.3):
$ ssh -i /root/demo/ssh/id_ed25519 -o IdentitiesOnly=yes -o BatchMode=yes sshu1@127.0.0.1 id
uid=1019(sshu1) gid=3109(sshu1) groups=3109(sshu1)
退出码=0
要点:四步闭环后把初始口令作废(passwd -l sshu1)即完成「只许钥、不许密」的交付;验证命令带 IdentitiesOnly=yes 是关键(避免误报成别的钥生效)。
4.2 场景二:非标准端口服务器的装钥与验证
对监听 2222 的实例安装(3.9 完整回显),验证同样带端口:
ssh-copy-id -i /root/demo/ssh/id_rsa2048.pub -p 2222 -o StrictHostKeyChecking=no sshu1@127.0.0.1
ssh -i /root/demo/ssh/id_rsa2048 -o IdentitiesOnly=yes -o BatchMode=yes -p 2222 sshu1@127.0.0.1 hostname
实测回显(验证步):
$ ssh -i /root/demo/ssh/id_rsa2048 -o IdentitiesOnly=yes -o BatchMode=yes -p 2222 sshu1@127.0.0.1 hostname
f1034b9326b2
退出码=0
要点:装钥与验证两处都要带 -p;提示行 ssh -p '2222' ... 是参数透传成功的标志。
4.3 场景三:变更前预演(-n 审计)
公钥治理动作前先 -n 预演要追加的清单(3.7 完整回显),再实装:
ssh-copy-id -n -i /root/demo/ssh/id_ecdsa.pub sshu1@127.0.0.1
实测回显(关键块):
=-=-=-=-=-=-=-=
Would have added the following key(s):
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBG5gRTqG3NNjTGqCPJNxjI2k3K8TOp39xKAGxIqm9wkvG71C22Tl0lohGMFOpOnZwI8QqW3+DIskQ9cBUcKWP3M= demo-ecdsa@test
=-=-=-=-=-=-=-=
要点:Would have added 块就是「变更单上的增量」;已存在的钥不会出现在该块(被过滤)。审计通过后去掉 -n 执行,完成后再 cat authorized_keys 留痕。
4.4 场景四:known_hosts 指纹冲突的完整处置链
服务器重装导致主机指纹变更后,连接与装钥双双失败(sshcopy3 [7][8] 完整回显,此处引其结构):
$ ssh -o BatchMode=yes sshu1@127.0.0.1 true
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:20nDWniZ75XC8e8tOw8YMyeoLLVJ0XXdzbHioUzoVrE.
Please contact your system administrator.
Add correct host key in /root/.ssh/known_hosts to get rid of this message.
Offending ED25519 key in /root/.ssh/known_hosts:1
remove with:
ssh-keygen -f "/root/.ssh/known_hosts" -R "127.0.0.1"
Host key for 127.0.0.1 has changed and you have requested strict checking.
Host key verification failed.
退出码=255
$ ssh-copy-id -i /root/demo/ssh/id_ecdsa.pub sshu1@127.0.0.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/demo/ssh/id_ecdsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: ERROR: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
ERROR: @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
(……中段与 ssh 报错逐行一致,每行以 ERROR: 为前缀,略……)
ERROR: Host key verification failed.
退出码=1
处置三步(实测):-F 确认旧指纹 → -R 清理(自动备份)→ accept-new 重连重新登记:
$ ssh-keygen -F 127.0.0.1
# Host 127.0.0.1 found: line 1
127.0.0.1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJO+yG1w+ThzGkfIBPDvghHn5Eyhd578pl/dQiiCUQy1
$ ssh-keygen -R 127.0.0.1
# Host 127.0.0.1 found: line 1
/root/.ssh/known_hosts updated.
Original contents retained as /root/.ssh/known_hosts.old
$ ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes sshu1@127.0.0.1 id
Warning: Permanently added '127.0.0.1' (ED25519) to the list of known hosts.
uid=1019(sshu1) gid=3109(sshu1) groups=3109(sshu1)
退出码=0
要点:报错里的 Offending ... key in /root/.ssh/known_hosts:N 直接给出冲突行号与清理命令;-R 后新指纹自动登记(Permanently added)。⚠️ -R 前务必核对报错里打印的指纹与服务器管理员给出的一致——工具只管清记录,判断真伪是人的责任(详见《ssh-keygen 命令详解与参数实测》3.19/3.20)。
五、注意事项与常见问题
Q1:echo 口令 | ssh-copy-id ... 为什么不行?怎么办?
ssh 的口令提示从 tty(终端) 读取,且 PAM 层会丢弃预灌的 stdin 输入,管道直接喂口令会被清空、卡在 password: 提示。实测可用方案是临时分配伪终端再定时喂入:
{ sleep 2; echo 'Test12345'; sleep 1; } | script -qec 'ssh-copy-id -i /root/demo/ssh/id_ed25519.pub -o StrictHostKeyChecking=no sshu1@127.0.0.1' /dev/null
(script -qec 命令 给命令套一个 pty,{ sleep 2; echo 口令; } 延时喂入;sleep 时长要覆盖 ssh 建连时间。)更规范的做法:装钥前先用 sshpass 或先经带外通道手工登录一次;自动化场景推荐提前分发生效的密钥而不是脚本里递口令。
Q2:装钥成功后登录还是 Permission denied?
按顺序排查:① 验证命令是否用了对应私钥(-i /path/私钥 -o IdentitiesOnly=yes,3.5 实测不指定私钥即失败);② 服务端目录权限(.ssh 必须 700、authorized_keys 必须 600 且属主正确,3.4 实测形态)——sshd StrictModes 对权限过宽直接拒认;③ 公钥类型是否被服务端接受(DSA 钥在 8.8+ 默认禁用,见《ssh-keygen》Q5);④ 首次连接是否被 known_hosts 拦截(Q6)。
Q3:提示 WARNING: All keys were skipped 是错了吗?
不是错,是幂等保护:过滤阶段发现公钥已在远端(3.6 实测),退出码 0、不做任何变更。确属误判(比如你更新了公钥内容但注释相同导致比对相同)才用 -f 强制重装——但 -f 会追加重复行,优先核实内容。
Q4:报 ERROR: failed to open ID file '公钥路径': No such file?
两种情况(3.11 实测):.pub 文件本身不存在;或 .pub 存在但同名私钥不存在(合并多钥的 bundle 场景必现,因为过滤阶段要用私钥试登)。前者补文件;后者按提示加 -f 跳过检查(-f -i bundle.pub,实测一次装 2 把)。
Q5:authorized_keys 为什么要 600 权限?不设会怎样?
sshd 的 StrictModes(默认开)要求:家目录、.ssh、authorized_keys 都不能「组/其他人可写」,且 .ssh/authorized_keys 属主必须与登录用户一致——不满足时公钥认证整体失效(服务端日志记 Authentication refused: bad ownership or modes),表现为装钥成功但永远 Permission denied。ssh-copy-id 已自动收敛(3.4 实测 700/600),手工维护时必须自己 chmod。
Q6:ssh-copy-id 报 REMOTE HOST IDENTIFICATION HAS CHANGED 怎么处理?
服务器主机钥变更(重装/换机)触发客户端指纹冲突:过滤阶段的 ssh 直接失败,错误块以 ERROR: 前缀透出(4.4 完整回显)。处置:确认指纹变更合法 → ssh-keygen -R 主机 清旧记录(自动备份 known_hosts.old)→ 重连重新登记。若服务器只是「部分 key 类型换了」(如仅换了 ED25519 主机钥、ECDSA/RSA 未动),ssh 可能因其余类型匹配仍能连上、仅打 WARNING——这种「半冲突」更要核对指纹,防止放过真实劫持。
Q7:多把公钥一起装怎么操作?
把多个 .pub 合并成一个文件再 -f -i(3.11 实测 Number of key(s) added: 2):cat key1.pub key2.pub > bundle.pub && ssh-copy-id -f -i bundle.pub 用户@主机。每把钥占 authorized_keys 一行;配合 -n 预演可先看将追加哪几行。
六、参考链接
- ssh-copy-id(1) 手册(Ubuntu 22.04 jammy):https://manpages.ubuntu.com/manpages/jammy/en/man1/ssh-copy-id.1.html
- ssh(1) 手册(-o/-p/-i 等透传选项的权威定义):https://manpages.ubuntu.com/manpages/jammy/en/man1/ssh.1.html
- sshd_config(5) 手册(StrictModes、PubkeyAcceptedAlgorithms 等服务端检查项):https://manpages.ubuntu.com/manpages/jammy/en/man5/sshd_config.5.html
- OpenSSH 官方文档:https://www.openssh.com/manual.html
- RFC 4252(SSH 认证协议,publickey 认证机制):https://datatracker.ietf.org/doc/html/rfc4252