rsync Error: 'connection unexpectedly closed (code 12)' — Cause, Fix, and Troubleshooting Guide
Fix rsync: connection unexpectedly closed (0 bytes received so far) (code 12) — remote rsync missing, contaminated shell output, and SSH transport failures.
- #automation
- #troubleshooting
- #rsync
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
Exit code 12 is a protocol error: the local rsync expected to speak to a remote rsync over the SSH channel, but the channel closed or returned data that was not a valid rsync protocol stream. The tell-tale (0 bytes received so far) means the handshake never even started — the remote side died, was never there, or the transport carried garbage instead of protocol.
$ rsync -a /opt/jobs/src/ myuser@example.com:/opt/jobs/dst/
rsync: connection unexpectedly closed (0 bytes received so far) [Receiver]
rsync error: error in rsync protocol data stream (code 12) at io.c(232) [Receiver=3.2.7]
This is almost always a transport or remote-environment problem, not a file problem. The two dominant causes are (1) the remote host has no rsync binary to launch, and (2) the remote login shell prints text — a banner, an MOTD, a stray echo in .bashrc — that contaminates the stream rsync tries to parse. 0 bytes received is the fingerprint of a broken channel; a non-zero byte count points instead to a version or data-stream mismatch.
Symptoms
- rsync exits with status
12andconnection unexpectedly closed (0 bytes received so far). - Plain
ssh myuser@example.comworks fine, but rsync over that same SSH fails instantly. - The failure is immediate — no files transfer, no progress.
- The remote host was recently rebuilt, reimaged, or is a minimal container.
- The remote user’s shell prints a login banner or a
.bashrcechoes text on non-interactive logins.
echo "rsync exit: $?"
rsync exit: 12
Common Root Causes
1. rsync is not installed on the remote host
rsync launches rsync --server on the far end over SSH. If the binary is missing, the remote command fails and the channel closes with zero protocol bytes.
bash: rsync: command not found
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
2. The remote login shell prints text into the stream
An MOTD, a Last login: banner, or an echo/fortune in ~/.bashrc/~/.bash_profile that runs on non-interactive logins injects bytes rsync then fails to parse as protocol.
Welcome to prod-app-02 — authorized use only
rsync error: error in rsync protocol data stream (code 12)
3. SSH authentication or connection failure
If SSH itself cannot authenticate (expired key, wrong user, host-key change), the command never runs and rsync sees a closed channel.
4. Wrong remote path or permissions
A destination path the remote user cannot access can cause the remote rsync to exit early before establishing the stream.
5. Protocol / version mismatch
A very old rsync on one side talking to a much newer one can fail to negotiate, though this usually shows a non-zero received-bytes count.
6. Remote rsync killed (OOM) mid-handshake
On a memory-starved host, the kernel OOM-killer terminates rsync --server as it starts, closing the stream.
How to Diagnose
Step 1: Confirm SSH itself works, then run rsync’s SSH verbosely
ssh myuser@example.com 'echo ok'
rsync -e 'ssh -v' -a /opt/jobs/src/ myuser@example.com:/opt/jobs/dst/ 2>&1 | tail -20
If ssh ... echo ok works but rsync fails, the problem is on the remote rsync side or the shell environment, not connectivity.
Step 2: Check that rsync exists on the remote
ssh myuser@example.com 'command -v rsync || echo MISSING'
MISSING
MISSING is the direct cause — install it (see Fixes).
Step 3: Detect a contaminated non-interactive shell
This is the single most common subtle cause. A clean non-interactive login must print nothing:
ssh myuser@example.com 'true' 2>/dev/null | xxd | head
00000000: 5765 6c63 6f6d 6520 746f 2070 726f 6420 Welcome to prod
Any output at all here means the shell is injecting bytes into rsync’s channel.
Step 4: Find the offending echo
ssh myuser@example.com 'grep -nE "echo|fortune|cat .*motd" ~/.bashrc ~/.bash_profile 2>/dev/null'
/home/myuser/.bashrc:14:echo "Welcome to $(hostname)"
Step 5: Compare rsync versions on both ends
rsync --version | head -1
ssh myuser@example.com 'rsync --version | head -1'
rsync version 3.2.7 protocol version 31
rsync version 3.1.2 protocol version 31
Fixes
Install rsync on the remote host:
ssh myuser@example.com 'sudo apt-get update && sudo apt-get install -y rsync'
# or: sudo yum install -y rsync
Stop the login shell from printing on non-interactive sessions. Guard any output in .bashrc so it only runs for interactive shells:
# Near the top of ~/.bashrc
case $- in
*i*) ;; # interactive — banners OK
*) return;; # non-interactive (rsync/scp) — print nothing
esac
Suppress the MOTD/last-login banner for automation by touching a hushlogin or using a quiet SSH invocation:
ssh myuser@example.com 'touch ~/.hushlogin'
Point rsync at an explicit remote binary path if it is installed somewhere non-standard:
rsync -a --rsync-path=/usr/local/bin/rsync /opt/jobs/src/ myuser@example.com:/opt/jobs/dst/
After the fix, a clean run transfers files and exits 0:
rsync -a /opt/jobs/src/ myuser@example.com:/opt/jobs/dst/ && echo "exit: $?"
exit: 0
What to Watch Out For
0 bytes received so faralmost always means the channel broke before protocol negotiation — look at the remote environment, not your files..bashrcoutput that seems harmless in an interactive terminal silently breaks rsync (and scp/sftp) on non-interactive logins.- Test non-interactive cleanliness with
ssh host 'true' | xxd— any bytes are a problem. - Code 12 is a protocol error, distinct from code 23 (partial transfer); do not apply attribute fixes here.
- Minimal container images and freshly reimaged hosts frequently ship without rsync — bake it into the image.
Related Guides
- rsync error: some files/attrs were not transferred (code 23)
- curl (7) Failed to connect after N ms: Connection refused
- connect ECONNREFUSED — webhook target connection refused
Fixed it? Get 500 Automation & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.