50

I would like to transfer files between two remote hosts using on local shell, but it seems rsync doesn't support synchronisation if two remotes are specified as follow:

$ rsync -vuar host1:/var/www host2:/var/www
The source and destination cannot both be remote.

What other workarounds/commands I could use to achieve similar results?

10 Answers 10

49

As you have discovered you cannot use rsync with a remote source and a remote destination. Assuming the two servers can't talk directly to each other, it is possible to use ssh to tunnel via your local machine.

Instead of

rsync -vuar host1:/var/www host2:/var/www

you can use this

ssh -R localhost:50000:host2:22 host1 'rsync -e "ssh -p 50000" -vuar /var/www localhost:/var/www'

In case you're wondering, the -R option sets up a reverse channel from port 50000 on host1 that maps (via your local machine) to port 22 on host2. There is no direct connection from host1 to host2.

  • 1
    I like @roaima's solution, but I couldn't get it to work for me due to various reasons. In the end I used sshfs to mount both remote directories locally, then used rsync across the two locally mounted directories. – aidan Oct 26 '15 at 2:07
  • Possible to see an example where a key is used? Having trouble figuring out how to use -i to specify the keys needed for the ssh commands. – onassar Mar 31 '16 at 14:27
  • @onassar add the -i key... parameter inside the quotes after the ssh command. If that doesn't help you please feel free to ask a New Question, referencing this answer for context – roaima Mar 31 '16 at 17:41
  • 1
    the reverse connect does not read the ~/.ssh/config on the local side - need to use something that can be resolved as if there were no SSH config file – Florenz Kley Apr 29 '16 at 10:40
  • 1
    'Assuming the two servers can't talk directly to each other'. This solution does work around a firewall or NAT issue that prevents a direct SSH connection. However, it doesn't address the case where for security reasons the source user (on host1) has no key or credentials or insufficient write permissions on the destination. For that see Kevin Cox's solution, or resort to an indirect connection using a script or scp -3. – Cedric Knight Aug 21 '17 at 8:15
19

You didn't say why you didn't want to log into one host and then copy to the other so I will share one of my reasons and solutions.

I couldn't log into one machine then rsync to the other because neither host had a SSH key that could log into the other. I solved this by using SSH agent forwarding to allow the first host to use my SSH key while I was logged in.

WARNING: SSH forwarding allows the host to use your SSH key for the duration of your login. While they can't copy your key they can log into other machines with it. Make sure you understand the risks and don't use agent forwarding for machines you don't trust.

The following command will use SSH agent forwarding to open a direct connection from host1 to host2. This has the advantage that the machine running the command isn't bottlenecking the transfer.

ssh -A host1 rsync -vuar /var/www host2:/var/www
  • 3
    +1 for explaining a valid use case (where the remote user on host1 has no permissions on the destination server); for the important security caveat (use port forwarding -D rather than -A to get around network rather than key restrictions); for explaining the advantage; for the command being short; and it actually working. Note that you may need to specify username@host1 if it's different from local username. Also that rsync performs host key verification when connecting to host2, so host1's key should already be in ~/.ssh/known_hosts on host2 or the command will fail. – Cedric Knight Aug 21 '17 at 8:21
  • Phenomenal answer, this helped me orchestrate some things in TeamCity that I wasn't able to do before (n.b. for other TeamCity users, you must add the "Build Feature" called "SSH agent" to your build config before using ssh -A, see confluence.jetbrains.com/display/TCD10/SSH+Agent). – John Zwinck Mar 7 '18 at 10:15
11

I like roaima's answer, but the paths are the same in both examples, obscuring which is which. We've established that the following doesn't work:

rsync -vuar host1:/host1/path host2:/host2/path

But this does (I omitted the explicit bind_address of localhost from the -R option since that's the default):

ssh -R 50000:host2:22 host1 'rsync -e "ssh -p 50000" -vuar /host1/path localhost:/host2/path'

Note that you'll have to have ssh keys set up correctly between the two remote hosts, with the private key on host1 and the public key on host2.

To debug the connection, break this into two parts and add verbose status:

localhost$ ssh -v -R 50000:host2:22 host1

If this works, you'll have a shell on host1. Now try the rsync command from host1. I recommend doing this in a different window so the verbose ssh info doesn't get mixed together with the rsync status info:

host1$ rsync -e "ssh -p 50000" -vuar /host1/path localhost:/host2/path
  • In my example the paths are source, destination. The rsync is initiated on host1 with the target on host2. (You could have asked for clarification in a comment.) – roaima Jan 14 '16 at 0:12
  • 1
    I would have commented, but you can't comment on somebody else's post without a reputation of 50+. – jaybrau Jan 14 '16 at 15:20
6

Reformatting answer by roaima in bash script syntax (and adding line continuation characters '\' for clarity) I randomly picked port 22000...

SOURCE_USER=user1
SOURCE_HOST=hostname1
SOURCE_PATH=path1

TARGET_USER=user2
TARGET_HOST=host2
TARGET_PATH=path2

ssh -l $TARGET_USER -A -R localhost:22000:$TARGET_HOST:22 \
$SOURCE_USER@$SOURCE_HOST "rsync -e 'ssh -p 22000' -vuar $SOURCE_PATH \
$TARGET_USER@localhost:$TARGET_PATH"
  • 1
    it seems to me that all you've done is replace his arbitrary hostnames with variables? – Jeff Schaller Oct 20 '16 at 23:18
  • 2
    Yes, I did. It adds clarity for me on which is the source machine, which is the target, and where the source and target paths go. It took me awhile to work all that out and it wasn't obvious from simple placeholder hostnames. – David I. Oct 25 '16 at 15:23
  • Next time please feel free to improve someone else's answer directly by editing it. – roaima Apr 11 '17 at 7:30
  • This answer was the solution for me as it combines ssh-agent forwarding (-A) with the reverse tunnel (-R). – camelthemammel May 2 at 9:46
3

The Ideal way would be to run the rsync on one of those servers. But if you do not want to run a script on the remote server. You could run a script on your local system and do an ssh and execute the rsync there.

ssh user@$host1 <<ENDSSH >> /tmp/rsync.out 2>&1 rsync -vuar /var/www host2:/var/www ENDSSH

Also, as you maybe aware rysnc does one way synchronization . If you'd like two way synchronization , you can look at osync (https://github.com/deajan/osync). I use it and found it to be helpful.

0

Just as an additional info:

If you use a jump host to connect the other two machines but they cannot reach each other directly, you can use sshfs as medium between these two machines like so (on the jump host):

$ mkdir ~/sourcepath ~/destpath
$ sshfs sourcehost:/target/dir ~/sourcepath
$ sshfs desthost:/target/dir ~/destpath
$ rsync -vua ~/sourcepath ~/desthpath

SSHFS provides the two paths on the jump host and rsync manages the synchronisation of the files like always (just with the difference that it's virtually done locally).

  • 1
    Note that the performance will be terrible. This is because to detect changes rsync will read the file from the source server, transferring the whole thing over the network. That being said if you can't do a direct transfer you will have to eat this if you are using rsync. Also don't mount the destination, it is unnecessary and will cause rsync to change some defaults because it thinks it is talking to a local disk. – Kevin Cox Aug 22 '16 at 18:47
0

You could run an rsyncd (server) on one of the computers.

This is the approach I'm taking, since I do not want to use ssh to allow the 'source' (in rsync wording) access to the 'destination' as root without a password (as is required to use SSH tunneling with rsync in a script)

In my case, I simply set up an rsyncd server on the destination computer with a single user allowed from the source pc and used rsync from the source side.

Works great.

0

Try using this. It works for me.

ssh src_user@src_host 'rsync -av /src/dir/location/ dest_user@dest_host:/dest/dir/loc/'
0

An easy to use script

Over the years I've done this many times with more or less the same tricks as in every other answer here. However because it's very easy to get some detail wrong and spend a lot of time figuring out the issue I've come up with the script below that:

  1. Makes it easy to specify all the details (source, destination, options)
  2. Incrementally tests every single step and gives feedback if anything goes wrong, so that you know what to fix.
  3. Works around cases where ssh -A fails to propagate authentication data (don't know why this happens sometimes since the workaround was easier than finding the root cause)
  4. Finally does the job.

How to use the script

  1. Make sure you can ssh to both hosts from localhost without typing a password.
  2. Set the variables in the first few lines of the script
  3. Execute it.

How it works

As I said it uses the same tricks as in every other answer here:

  • ssh's -R option to ssh from localhost to host1 while at the same time setting up a port forwarding that then allows host1 to connect via localhost to host2 (-R localhost:$FREE_PORT:$TARGET_ADDR_PORT)
  • ssh's -A option to allow easy authenticaion of the second ssh chanel

My this IS complicated! Is there any easier way?

When copying all or most bytes from source to destination it's FAR easier to use tar:

ssh $SOURCE_HOST "tar czf - $SOURCE_PATH" \
    | ssh $TARGET_HOST "tar xzf - -C $TARGET_PATH/"

The script

#!/bin/bash
#-------------------SET EVERYTHING BELOW-------------------
# whatever you type after ssh to connect to SOURCE/TARGE host 
# (e.g. 1.2.3.4:22, user@host:22000, ssh_config_alias, etc)
# So if you use "ssh foo" to connect to SOURCE then 
# you must set SOURCE_HOST=foo
SOURCE_HOST=host1 
TARGET_HOST=host2 
# The IP address or hostname and ssh port of TARGET AS SEEN FROM LOCALHOST
# So if ssh -p 5678 someuser@1.2.3.4 will connect you to TARGET then
# you must set TARGET_ADDR_PORT=1.2.3.4:5678 and
# you must set TARGET_USER=someuser
TARGET_ADDR_PORT=1.2.3.4:5678
TARGET_USER=someuser

SOURCE_PATH=/mnt/foo  # Path to rsync FROM
TARGET_PATH=/mnt/bar  # Path to rsync TO

RSYNC_OPTS="-av --bwlimit=14M --progress" # rsync options
FREE_PORT=54321 # just a free TCP port on localhost
#---------------------------------------------------------

echo -n "Test: ssh to $TARGET_HOST: "
ssh $TARGET_HOST echo PASSED| grep PASSED || exit 2

echo -n "Test: ssh to $SOURCE_HOST: "
ssh $SOURCE_HOST echo PASSED| grep PASSED || exit 3

echo -n "Verifying path in $SOURCE_HOST "
ssh $SOURCE_HOST stat $SOURCE_PATH | grep "File:" || exit 5

echo -n "Verifying path in $TARGET_HOST "
ssh $TARGET_HOST stat $TARGET_PATH | grep "File:" || exit 5

echo "configuring ssh from $SOURCE_HOST to $TARGET_HOST via locahost"
ssh $SOURCE_HOST "echo \"Host tmpsshrs; ControlMaster auto; ControlPath /tmp/%u_%r@%h:%p; hostname localhost; port $FREE_PORT; user $TARGET_USER\" | tr ';' '\n'  > /tmp/tmpsshrs"

# The ssh options that will setup the tunnel
TUNNEL="-R localhost:$FREE_PORT:$TARGET_ADDR_PORT"

echo 
echo -n "Test: ssh to $SOURCE_HOST then to $TARGET_HOST: "
if ! ssh -A $TUNNEL $SOURCE_HOST "ssh -A -F /tmp/tmpsshrs tmpsshrs echo PASSED" | grep PASSED ; then
        echo
        echo "Direct authentication failed, will use plan #B:"
        echo "Please open another terminal, execute the following command"
        echo "and leave the session running until rsync finishes"
        echo "(if you're asked for password use the one for $TARGET_USER@$TARGET_HOST)"
        echo "   ssh -t -A $TUNNEL $SOURCE_HOST ssh -F /tmp/tmpsshrs tmpsshrs"
        read -p "Press [Enter] when done..."
fi

echo "Starting rsync"
ssh -A $TUNNEL $SOURCE_HOST "rsync -e 'ssh -F /tmp/tmpsshrs' $RSYNC_OPTS $SOURCE_PATH tmpsshrs:$TARGET_PATH"

echo
echo "Cleaning up"
ssh $SOURCE_HOST "rm /tmp/tmpsshrs"
  • tar is great when you've got a single (non-incremental) transfer and your transfer completes in a single pass. On the other hand, rsync with forwarding handles restarts and incremental transfers. – roaima Apr 4 at 12:34
  • Of course @roaima -- I don't consider tar equivalent. I left this passing reference for the day when I will be reading this to solve a problem where rsync will not 100% necessary. – ndemou Apr 4 at 13:29
-1

It's possible to use tar via ssh to transfer the files:

ssh -n user1@host1 'tar jcf - -C /var/www .' | ssh user2@host2 'tar jxvf - -C /var/www'

Change j parameter (for tar) to z in two places if you wish to compress the archive with gzip, instead of bzip2. Usually bzip2 has the higher compression than gzip, but it's slower, so change it depending on your needs (see: bzip2 vs gzip).

Related: How to copy between two remote hosts using tar piped into SSH from remote server when behind a firewall?


Alternatively (to safe the bandwidth, because of the transparent compression) it's possible to use sshfs to mount remote file-system as local and use rsync as usual, e.g.

$ sshfs user1@host1:/var/www /mnt
$ rsync -vuar /mnt user2@host2:/var/www
  • 1
    Mounting the filesystem locally wont save any bandwidth between the source and local machine. It will however save bandwidth between the local and destination (as it isn't mounted locally). Using rsync this way only makes sense if you are trying to save bandwidth on the destination. – Kevin Cox Dec 3 '15 at 14:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.