Linux rsync 远程同步带密码:四种免交互方案与自动化实践

Linux rsync 远程同步带密码:四种免交互方案与自动化实践 在 Linux 上做远程同步rsync 几乎是绕不开的一个工具而一旦把它塞进定时任务或者自动化脚本里密码这两个字立马就成了第一道坎——手动敲一遍没问题交给 cron 跑就卡在交互提示上不动了。这篇文章就围绕Linux 下 rsync 远程同步带密码这件事把我这些年攒下来的配置、命令和踩过的坑一次性理清楚。核心会讲到两种完全不同的认证通道ssh 通道和 rsync daemon四种把密码喂进去的落地方式以及它们各自适合什么场景、有什么代价。不管你是刚接触 rsync 的新手还是已经在生产环境维护几十台机器同步任务的老手应该都能从这里找到能直接抄的配置。文章里的所有命令和配置都可以直接拿去改改就用我也会标注清楚哪些是通用实践、哪些是我自己环境里验证过的。1. 为什么 rsync 远程同步一到密码就卡壳1.1 rsync 在远程同步里的真实定位先把定位说清楚。rsync 不是简单的文件拷贝工具它的核心价值在于增量传输算法发送端和接收端各自对文件分块做滚动校验和只把两边不一致的块传过去。这意味着一个 10GB 的日志文件你只改了最后几十 KB同步时真正走网络的可能就那么点数据。这个特性和 scp、ftp 那种整个文件重传的思路完全不是一个量级尤其在跨机房、跨地域、带宽有限的场景下差距能到几十倍。它解决的典型问题有三类一是大批量文件的周期性备份比如每天凌晨把业务服务器上的数据目录同步到备份机二是多机内容分发一份物料要铺到十几台机器上靠 rsync 推一遍就行三是实时或准实时的镜像配合文件监控工具文件一变动就触发同步。这三类场景有一个共同点几乎都要交给自动化去跑而自动化最怕的就是等你输密码。这里必须先破除一个误区rsync 本身在 ssh 通道模式下根本没有自己的密码机制它完全依赖 ssh 来做身份认证。所以rsync 带密码这个说法实际包含两件完全不同的事——要么是让 ssh 免交互认证要么是启用 rsync 自己的 daemon 模式和 secrets file。很多人配置卡住根子就在于没分清这两条路拿 ssh 的密钥去套 daemon 配置或者反过来那肯定是不通的。1.2 ssh 通道与 daemon 模式认证走的是两条完全不同的路rsync 的远程用法分两大类写法上就能一眼区分ssh 通道远程 shell 模式地址里带冒号比如userhost:/path/to/dir或者用-e ssh显式指定。它在底层其实是通过 ssh 登录到对方机器然后在对面起一个 rsync 进程来干活。认证、加密、端口全部由 ssh 负责rsync 自己不管。daemon 模式地址里是双冒号host::module或者rsync://host/module。这时候对面跑的是一个常驻的 rsync 服务进程默认 873 端口认证由 rsync 自己的auth users和secrets file承担加密默认是没有的除非你再套一层 ssh 隧道。两种模式的差异我用一张表对比更清楚对比项ssh 通道模式daemon 模式地址写法userhost:/pathhost::module或rsync://host/module默认端口22873认证方式ssh 密钥或 ssh 密码rsync 自身 auth users secrets file传输加密默认加密默认明文需额外套隧道服务端依赖只要装了 ssh要配置并常驻 rsync 服务目录范围有 ssh 权限就能访问任意路径严格限制在模块定义的 path 内适合场景已有 ssh 体系的临时或跨网同步内网批量、固定目录、想隔离权限选型逻辑其实很直白如果目标机器你本来就能 ssh 上去且同步路径不固定走 ssh 通道最省事如果是内网里几台机器互相推数据想给同步单独开一个受限账号、只允许访问某个目录daemon 模式更干净。daemon 模式有个天然的安全优势——客户端只能看到模块暴露的路径拿不到整机权限这在多团队共用一台存储机的时候特别有用。反过来跨公网同步我一般不建议裸跑 daemon认证信息是明文传的得配合隧道或限制来源 IP 才行。理解了这两条路的本质差别后面配置时你就不会在该配哪个文件上犹豫了。2. 四种带密码方案的取舍逻辑2.1 SSH 密钥把密码问题从根上抹掉严格来说这不是带密码而是不要密码但它是实际生产里最推荐的方案必须先讲。原理很简单在客户端生成一对密钥把公钥追加到服务端目标账号的~/.ssh/authorized_keys里之后 ssh 登录就不再询问密码。rsync 走 ssh 通道时天然享受这个免密能力。生成和下发大致是这样几步ssh-keygen -t ed25519 -C rsync-backup -f ~/.ssh/rsync_key -N ssh-copy-id -i ~/.ssh/rsync_key.pub backup192.168.1.20-N 表示私钥不设口令这样脚本里才能在无人值守的情况下用如果给私钥设了口令那又回到需要交互的老问题得靠 ssh-agent 来托管。下发完之后先手动验证一次ssh -i ~/.ssh/rsync_key backup192.168.1.20 echo ok能直接打印 ok 就说明免密通了接下来 rsync 命令里加-e ssh -i ~/.ssh/rsync_key就能免交互跑。这套方案的好处是密码这个东西根本不存在于任何脚本和文件里泄露面最小。代价是要在服务端维护公钥机器多了得做批量分发和密钥轮换。我的建议是给同步任务单独建一个受限账号只授予目标目录的读写权限密钥也单独生成别复用管理员的密钥。这样即使密钥泄露影响面也可控。2.2 rsync daemon 的 secrets filersync 原生认证如果走 daemon 模式密码机制就回到了 rsync 自己手里服务端在rsyncd.conf里用auth users声明允许的用户名用secrets file指向一个存放用户名:密码的文件客户端则可以配一个只写密码的本地文件配合--password-file参数免交互。这是 rsync 体系里最正统的带密码方案。它的特点是认证和授权都在 rsync 层面完成跟系统账号没关系也就是说你可以造一个只存在于 rsync 配置里的虚拟用户完全不影响系统登录。这个设计在只读分发场景里特别顺手——给每台下游机器一个独立的 rsync 用户名想收回权限时改一行配置就行不用去动系统账号。要留意的点有两个一是 secrets file 的权限必须是 600 且属于运行 rsync 的用户否则服务端会直接拒绝启动二是 daemon 模式默认不加密凭据是明文过网的内网还凑合跨公网就得慎重。2.3 sshpass最省事也最需要小心的一种现实情况是很多环境不允许你随便改服务端的 ssh 配置加公钥要走流程或者对面是个网络设备、老系统压根不认你的密钥格式。这时候sshpass就成了最直接的选择——它通过伪终端把密码灌给 ssh让原本需要交互的流程变成非交互。用法本身很简单sshpass -p YourPassword rsync -avz -e ssh /data/ backup192.168.1.20:/backup/但它的代价也很明确密码以明文出现在命令历史、进程列表和脚本文件里。ps aux在同步运行期间能看到密码虽然时间窗口很短bash 的 history 也会记下来脚本文件本身更是明摆着的。所以用 sshpass 的时候密码管理必须格外上心。我的处理原则是密码绝不硬编码在脚本里而是从受限权限的配置文件或环境变量读取脚本本身做权限控制关掉相关命令的历史记录。下一节会讲具体怎么落地。sshpass 我认为是能用但不优雅的方案适合过渡期或无法改造的老环境。2.4 expect 兜底与它们之间的选择标准最后一种是比较土但极其通用的方案用 expect 脚本模拟人工交互捕获密码提示符然后发送密码。它几乎能应付任何交互式命令但可维护性差、对提示符文本敏感中文 locale、不同 ssh 版本的提示语都可能不一样我一般只在前面几种都不适用时才动用它。把四种方式放一起选择标准就清晰了方案是否免交互密码是否落盘加密推荐度SSH 密钥是否无密码是首选daemon secrets file是是服务端配置否需加隧道内网首选sshpass是是客户端脚本是过渡/受限环境expect是是客户端脚本是兜底实际决策时我通常问自己三个问题能不能改服务端能不能跨公网要不要精细化权限答案组合起来基本就决定了用哪种。3. 实操rsync daemon 带密码同步完整落地3.1 服务端 rsyncd.conf 逐项拆解daemon 模式的核心就是服务端那份配置文件。默认位置一般是/etc/rsyncd.conf内容长这样uid nobody gid nobody use chroot yes max connections 20 pid file /var/run/rsyncd.pid log file /var/log/rsyncd.log transfer logging yes [backup] path /data/backup comment backup storage read only no write only no auth users rsyncuser secrets file /etc/rsyncd.secrets hosts allow 192.168.1.0/24 hosts deny * list no逐项说下为什么这么配。uid/gid决定同步进程以什么身份写文件用 nobody 是出于安全但要注意目标目录得给 nobody 可写权限否则会报权限错误——这是新手最常踩的坑之一。use chroot yes把进程锁在模块 path 里防止路径穿越安全加固必开代价是需要 root 权限启动。max connections防止下游机器一拥而上把服务端压垮。[backup]这一段就是模块定义客户端地址里的host::backup对应的就是这个名字。read only no很关键——默认是只读不改成 no 客户端推文件会直接被拒绝。auth users和secrets file就是密码认证的两件套缺一不可。hosts allow用网段白名单做一层粗粒度过滤配合hosts deny *形成默认拒绝的策略。list no表示不响应客户端的模块列表查询对外少暴露一点信息。注意path指向的目录必须真实存在且权限正确rsync 不会帮你自动创建配错了启动时不报错客户端一连才失败排查起来容易绕远。3.2 secrets file 的权限与路径坑密码文件的内容很简单一行一个用户rsyncuser:Str0ngPassw0rd!格式就是用户名:密码冒号前后不能有多余空格。写完立刻改权限chmod 600 /etc/rsyncd.secrets chown root:root /etc/rsyncd.secrets权限不对的话服务端启动时会直接甩一句类似secrets file must not be other-accessible的错误然后退出。这个检查是硬性的因为在 rsync 看来能被别人读到的密码文件等于没设密码。这里有个容易忽略的细节secrets file 的属主应该是启动 rsync 的那个用户通常是 root而不是配置里uid指定的 nobody。经常有人看到uid nobody就把文件也改成 nobody结果启动失败绕半天才反应过来。另外如果同一台机器上跑多个模块、每个模块用不同密码文件路径一定要写绝对路径相对路径的解析基准会随启动方式变化非常不可靠。3.3 启动、放行与客户端验证服务端启动有两种常见方式。临时验证用前台命令rsync --daemon --config/etc/rsyncd.conf生产环境更推荐交给 systemd 托管写一个/etc/systemd/system/rsyncd.serviceExecStart填上面的命令Restarton-failure然后systemctl enable --now rsyncd即可机器重启后自动拉起。防火墙要放行 873 端口只对可信网段开firewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.168.1.0/24 port port873 protocoltcp accept firewall-cmd --reload验证顺序建议从简到繁先telnet host 873看端口通不通再rsync rsync://host/前提是list yes看模块列表最后带认证跑一次真同步。客户端这边有两种写法一种是交互式输密码rsync -avz /data/ rsyncuser192.168.1.20::backup/回车后会提示Password:输入即可。另一种是配密码文件免交互下一节展开。3.4 客户端密码文件与常用命令模板客户端准备一个只包含密码的文件注意只有密码没有用户名没有冒号这点和服务端不一样echo Str0ngPassw0rd! /etc/rsync.pass chmod 600 /etc/rsync.pass然后加上--password-file参数rsync -avz --password-file/etc/rsync.pass /data/ rsyncuser192.168.1.20::backup/这样就能完全免交互了。常用的组合参数我整理成一份模板推和拉各一份# 推本地目录同步到远端模块 rsync -avz --delete --password-file/etc/rsync.pass \ --exclude*.tmp --exclude.git/ \ /data/ rsyncuser192.168.1.20::backup/ # 拉远端模块同步到本地 rsync -avz --delete --password-file/etc/rsync.pass \ rsyncuser192.168.1.20::backup/ /data/restore/--delete表示让目标端和源端严格一致多出来的文件会被删掉用之前务必先用--dry-run演练这个后面单独讲。--password-file的路径在 cron 里必须写绝对路径因为 cron 的工作目录和登录 shell 完全不同。4. 实操ssh 通道下自动喂密码4.1 sshpass 的安装与最简命令sshpass 大多数发行版都能直接装# Debian/Ubuntu 系 apt-get install -y sshpass # RHEL/CentOS 系 yum install -y sshpass最简用法就是-p直接给密码或者-f从文件读或者-e从环境变量读。生产里我强烈建议用后两种不要用-p# 从环境变量读推荐 export RSYNC_PASSYourPassword sshpass -e rsync -avz -e ssh -o StrictHostKeyCheckingno /data/ backup192.168.1.20:/backup/ # 从文件读 sshpass -f /etc/rsync_pass.txt rsync -avz /data/ backup192.168.1.20:/backup/-e这种方式密码至少不会出现在命令行参数里ps aux抓不到。文件方式则要注意文件权限 600且脚本本身也要限制读取权限。4.2 引号、变量、-e 参数的配合细节sshpass 和 rsync 配合时几个细节特别容易翻车。第一是-e参数的引号rsync的-e后面接的是 ssh 的完整命令中间有空格所以必须整体引起来用双引号还是单引号取决于里面有没有变量展开需求。要传-p 2222这种非标准端口时写成sshpass -e rsync -avz -e ssh -p 2222 -o StrictHostKeyCheckingno /data/ userhost:/backup/第二是StrictHostKeyCheckingno。首次连接服务端时ssh 会问是否信任这个主机指纹这也是个交互同样会卡住脚本。加上这个参数就自动信任了但要清楚这意味着放弃了主机指纹校验安全上是有折损的事后应该把服务端指纹预先写进known_hosts再把它去掉。第三是密码里如果包含$、!、\这类特殊字符在变量赋值和引号处理上要格外小心最稳妥的办法是把密码写进文件用-f彻底绕开 shell 转义。4.3 密钥 ssh-agent 的稳妥路线如果只是不想每次输密码、又能接受在机器上存一个密钥那 ssh-agent 是最优雅的。它的思路是私钥设了口令但口令只在会话开始时输入一次之后由 agent 在内存里缓存解密后的私钥后续 ssh 调用直接问 agent 要不再提示。eval $(ssh-agent -s) ssh-add ~/.ssh/rsync_key ssh-add -l # 确认已加载这套东西在交互式会话里体验很好但放到 cron 里就麻烦了——cron 每次执行都是新的、干净的环境没有 agent 可用。所以自动化场景要么用无口令的专用密钥配合 ssh 的command和from限制来源和可执行命令要么在脚本启动时用SSH_ASKPASS加一个小助手程序来喂私钥口令。后者配置复杂我一般直接选前者用受限密钥把风险压到最低。4.4 密码来源的几种安全折中无论用 sshpass 还是 daemon密码总得有个地方存。常见的几种存放位置安全性排序大致是这样存放方式泄露风险适用场景硬编码在脚本里最高绝不推荐明文配置文件600 权限中单机自动化可控环境环境变量中低交互式或短期任务密钥管理服务下发低有成熟运维体系SSH 密钥免密最低条件允许时首选我的实际做法是能用密钥就用密钥必须用密码时把密码放在单独的、600 权限、属主为执行用户的文件里脚本里只引用路径绝不在脚本正文出现密码字符串。同时给这个文件所在目录加上审计任何读取行为都能查到。还有一点容易被忽视——日志里不能打印密码rsync 的-v输出不会带密码但如果你自己在脚本里echo了命令行那就泄了写脚本时留意别把整条命令打进日志。5. 参数与过滤规则同步内容对了才算成功5.1 尾部斜杠决定同步语义这是 rsync 最经典也最容易出错的地方必须单独讲。源路径末尾有没有斜杠含义完全不同rsync -av /data/ host:/backup/—— 把/data/目录里面的内容同步到/backup/结果是/backup/file1、/backup/sub/。rsync -av /data host:/backup/—— 把/data这个目录本身同步过去结果是/backup/data/file1、/backup/data/sub/。一个是内容对内容一个是目录套目录。我见过不止一次有人误加了斜杠结果备份目录结构悄悄变了层级发现的时候已经跑了好几天。目标端的斜杠一般无所谓但为了可读性建议统一带上。养成习惯写命令前先想清楚我要的是内容还是目录然后在脑子里过一遍源路径末尾那个斜杠该不该存在。5.2 --delete 与 --dry-run 的保命组合--delete让目标端成为源端的精确镜像多出来的文件会被删除。这个参数能让备份环境保持干净但也是删库事故的头号元凶——源端目录一旦被误删或者挂载点掉了下一次同步就会把目标端的内容全部清空。所以我的铁律是任何带--delete的命令第一次执行必须先加--dry-run。它会把将要做的每一步操作列出来但实际什么都不改rsync -avz --delete --dry-run --password-file/etc/rsync.pass /data/ rsyncuserhost::backup/仔细看完输出里的删除列表确认没有意外再去掉--dry-run正式执行。另外有两个参数值得记住--max-deleteN限制单次最多删多少个文件超过就中止--delete-excluded则会连被排除规则过滤掉的文件也一起删除非你明确知道自己在干什么否则别碰。还有个场景要特别注意如果源是一个挂载点同步前先确认挂载正常因为挂载失败时目录是空的带--delete同步上去就是把备份清空。5.3 排除规则与 include/exclude 顺序同步业务数据时总有一堆东西是不想传的缓存、临时文件、版本控制目录、日志。规则写法有讲究rsync 是按顺序逐条匹配的先匹配到的规则生效所以--include必须写在能匹配它的--exclude前面否则永远轮不到它。rsync -avz \ --include*/ \ --include*.conf \ --exclude* \ /etc/ rsyncuserhost::backup/上面这段的意思是先允许所有目录否则进不去子目录、允许所有 conf 文件、最后拒绝其他一切。这就是典型的白名单写法顺序颠倒一下结果就完全不对了。规则多了之后把它们写进一个文件用--exclude-from引用比堆一长串参数可读性好得多rsync -avz --exclude-from/etc/rsync-exclude.txt /data/ rsyncuserhost::backup/排除文件里一行一条#开头是注释。常见内容就是*.tmp、*.log、.cache/、node_modules/这些。有一点要提醒排除规则是对相对路径生效的/sub/tmp和sub/tmp含义不同前者锚定在同步根目录下后者匹配任意层级的同名路径写的时候留意开头那个斜杠。5.4 限速、断点、校验相关参数传输控制方面有几个参数是必会的。带宽限制用--bwlimit单位是 KB/srsync -avz --bwlimit5000 ... # 限制到约 5MB/s备份任务跑在业务时段时不限速很容易把带宽打满影响线上服务我一般会按业务高峰和低谷设两套值。断点续传用--partial中断时保留已传的部分文件和--partial-dir把部分文件放到指定目录下次续传配合--append-verify可以只追加差异部分特别适合传大文件。rsync -avz --partial --append-verify --bwlimit5000 /data/big.iso rsyncuserhost::backup/大文件同步还建议加--progress看实时进度脚本里则用--stats输出统计信息方便记日志。至于传输校验rsync 默认在结束时会对整个文件做校验和验证这点和只用-c的启动校验不同一般不需要额外加东西。如果实在不放心同步完再对关键文件跑一次md5sum比对代价是额外的读 IO看数据重要程度决定。6. 自动化落地cron、实时触发与日志6.1 cron 环境下密码与 PATH 的坑脚本在终端跑得好好的一进 cron 就失败十有八九是环境问题。cron 的执行环境极其干净PATH 通常只有/usr/bin:/bin你自定义的环境变量、alias、shell 配置全都不在。所以脚本里第一件事应该是显式补环境#!/bin/bash export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin export RSYNC_PASSWORDYourPassword # 仅 daemon 模式的另一种传参方式注意 daemon 模式如果需要用环境变量传密码变量名是固定的RSYNC_PASSWORDrsync 会自己读不需要--password-file。工作目录也要显式cd到目标位置因为 cron 默认在某处不同发行版不一样相对路径会全部错位。另外所有文件引用都写绝对路径包括密码文件、排除规则文件、日志文件。脚本的退出码一定要检查否则失败了你也无从知道rsync -avz --password-file/etc/rsync.pass /data/ rsyncuserhost::backup/ /var/log/rsync-backup.log 21 if [ $? -ne 0 ]; then echo $(date) rsync failed /var/log/rsync-error.log ficron 表达式本身也有讲究备份任务尽量错开业务高峰多个任务之间留足间隔别让它们互相抢带宽。6.2 inotifywait 触发式同步定时同步的粒度最细也就是分钟级如果要求文件一变动几秒内就到了对面就得上文件监控。inotify-tools里的inotifywait可以监听目录事件一旦有变动就触发一次 rsyncinotifywait -mrq --timefmt %Y-%m-%d %H:%M --format %T %w%f %e \ -e modify,create,delete,move /data/ | while read line; do rsync -avz --delete --password-file/etc/rsync.pass /data/ rsyncuserhost::backup/ done这段逻辑有个明显的坑高频写入会触发大量 rsync导致同步进程互相叠加。所以实际用的时候要加防抖比如事件触发后 sleep 几秒再同步或者记录上次同步时间间隔小于 N 秒就直接跳过。另外--delete在实时场景下要格外小心文件重命名、编辑器临时文件都会产生删除事件很容易把对面误删。我的做法是实时场景先不带--delete只做增量追加定期再跑一次带--delete的全量校准。6.3 日志记录与失败告警同步任务最怕的是静静地失败所以日志和告警必须配套。日志方面--log-file可以直接让 rsync 自己写日志文件加上transfer logging在服务端配置里打开能记录每次传输的文件、大小、耗时出问题时能追溯。脚本层面再记一层开始时间、结束时间、退出码两条线对照着看。告警的话最简单的做法是脚本里判断退出码非零就发邮件或写到一个统一监控采集的文件里。有条件的话把同步时长、传输字节数这些指标上报到监控系统设个阈值——比如平时每天同步 2GB、某天突然变成 200MB很可能就是源端目录异常这种负向异常光靠退出码是发现不了的得靠指标趋势。7. 报错排查速查表配置过程中大部分问题其实就那几类我把高频报错和对应原因整理成表遇到时对照着查会快很多报错/现象常见原因处理方式ERROR: auth failed on module用户名或密码不对客户端密码文件多了用户名或冒号检查两边密码一致性客户端文件只放密码secrets file must not be other-accessible服务端密码文件权限不是 600chmod 600属主改为 rootERROR: chdir failed模块 path 目录不存在或无权限创建目录并给运行用户写权限ERROR: module is read only服务端read only no没配在对应模块下加上该项Permission denied (13)运行用户对目标目录无写权限调整目录属主或 rsync 的 uid/gid连接超时防火墙未放行 873或hosts allow未包含来源检查防火墙和来源白名单rsync: command not found对端没装 rsyncssh 通道模式在对端安装 rsync卡在提示符不动密码交互没被处理用--password-file或 sshpassNo space left on device目标磁盘满了清理空间或加--max-size限制同步后目录层级不对源路径尾部斜杠用错按 5.1 节的规则核对除了表里这些还有两个隐蔽的问题值得单独说。一个是ssh 通道模式下两个 rsync 版本不一致老版本可能不支持新参数报错信息会指向协议不匹配处理方法是在两端都用较新版本或者去掉新参数。另一个是daemon 模式下的时区问题服务端日志时间和你本地对不上排查时间线时会误导判断配好 NTP 同步能避免这类困扰。排查的基本顺序建议固定下来先确认网络和端口通不通再确认认证过不过接着确认路径权限对不对最后才看参数逻辑。按这个顺序走能省掉大量来回试的时间。最后分享我自己在这件事上的几个体会。密码这条线能不用明文就不用明文SSH 密钥是我在所有环境里的第一选择只有在确实改不动服务端的时候才退到 sshpass而且密码一定放独立文件、600 权限、不进日志。daemon 模式我很喜欢它的权限隔离效果但只用在内网靠hosts allow限制来源绝不让 873 端口直接面对公网。还有一点不管用哪种方案--dry-run这个习惯一定要养成尤其是第一次上--delete的时候它救过我不止一次。至于同步任务本身日志和退出码检查是底线配置宁可多写两行脚本也别让它在你不知道的时候悄悄失败。