Improving SSH (OpenSSH) connection speed with shared connections

Home » How-Tos » Improving SSH (OpenSSH) connection speed with shared connections

Introduction

This article briefly explains how to configure OpenSSH (version 4 or higher) to share connections to the same host (for faster connecting), as well as some of the problems (with workarounds) you may encounter using shared connections.

Contents:

Overview

Version 4 of OpenSSH introduced a great but little-known feature that enables you to share connections to a remote host, so that if you open multiple connections to that host, all connections after the first connecton reuse the first connection. Why would you want to do that? Because subsequent connections will connect much faster.

For example, connecting to a server of mine across the country takes 0.9 seconds or so the first time (and every time without connection sharing); connecting to that same host reusing a connection takes about .13 seconds — that‘s about 7 times faster! If you are using some tool that makes lots of connections to the same host one after the other, the time savings can be drastic. I use Darcs over SSH for source code revisioning, and it seems to be very slow for certain operations due to opening multiple connections. Reusing connections sped up certain operations I do many times a day from 7 or 8 seconds to less than 1 second.

Configuring OpenSSH to reuse connections is trivial to set up, but there are some gotchas that you might run in to.

Configuring Shared Connections

First, make sure you have a ~/.ssh directory, creating it if necessary, and ensure that it has the correct permissions. It should have 700 permissions (i.e., all permissions for you, none for anybody else):

calvin@turing ~ $ ls -ld ~/.ssh
drwx------ 2 calvin calvin 4096 2008-04-23 01:30 /home/calvin/.ssh/

If the permissions are not already set to 700, make it so:

calvin@turing ~ $ chmod 0700 ~/.ssh

Next, add the following configuration options to ~/.ssh/config, creating this file if it does not already exist:

Host *
  ControlPath ~/.ssh/master-%l-%r@%h:%p
  ControlMaster auto

This configures OpenSSH for opportunistic sharing. Host * specifies that the following block of configuration options should be used for connecting to any host. ControlMaster auto specifies that OpenSSH should reuse an existing connection to a given host if possible, or open a new connection if there is not an existing one. ControlPath ~/.ssh/master-%l-%r@%h:%p specifies where OpenSSH should create the socket file that represents the master connection, where %r is replaced by the login name, %h is replaced by the hostname, and %p is replaced by the port number. The %l option adds the local hostname to the name of the socket file, which is useful if the directory might be mounted on multiple hosts (e.g., if your $HOME is remote and accessed via NFS). If the directory is not shared across more than one machine, then the %l is not necessary, but it doesn’t hurt either, and it makes things more future-proof. (Thanks to Anthony M. for the suggestion of adding %l to the control path format string.) See the ssh_config man page for more details about what these options do.

After making this configuration change, verify that it’s working by connecting to some host, and verifying that the socket file is created. If you want to compare times, you might try running time ssh example.com exit with and without a master connection already open. You might also try connecting with the -v option, to see exactly what is happening. When you connect for the first time, you’ll see lots of debug messages (40 or 50 lines). When you connect reusing a connection, you’ll see less than 10 lines of output, and the last line before successfully logging in will be something like “auto-mux: Trying existing master”.

It is important that you configure the ControlPath to be somewhere that no other user has access to. If the ControlPath were set to a location that other users could write to, they could create a file there and prevent OpenSSH from being able to create a socket file there when you try to connect. (This wouldn’t be a fatal problem though, since you could still connect by specifying a non-default path: ssh -o "ControlMaster auto" -o "ControlPath ~/.ssh/foo-%l-%r@%h:%p" username@example.com.) But regardless of whatever attacks there are or are not, the ones you should worry about the most are the ones that haven’t been discovered yet, and there are far fewer of those for a socket file saved in a 700 directory than in a 777 directory.

The rest of this document deals with potential problems that you might run into.

Subsequent connections with different ssh options

Since all connections to the given host share the same TCP connection, they all reuse whatever SSH options the first connection used. This means that if you try to connect to a remote host using different options that you used initially, OpenSSH will quitely ignore the options you specify in subsequent connections. If you initially connect without enabling X11 forwarding (i.e., without using the -X flag) and want to later open a connection to that host with X11-forwarding enabled, doing ssh -X example.com will not work; it will quitely reuse the existing connection and won’t even warn you that X11 forwarding is not enabled. The same is true for the display setting and probably other options as well.

In order to use different configuration options for the connection, you must open a new connection and not reuse the existing master connection. This is accomplished by making a connection with ControlPath set to none. The -S flag is a convenience for setting the ControlPath on a per-process basis, so including -S none in your command results in a new connection being opened. In order to make a new connection with X11 forwarding enabled, for example, you would use:

ssh -S none -X example.com

If you do this sort of thing frequently, you might want to something like alias sshnew="ssh -S none" to your ~/.bashrc file, and then you can just do sshnew -X example.com.

Stale socket files

When the master connection successfully exits — i.e., when you logout from the master connection — OpenSSH will delete the appropriate socket file. However, if the process gets killed without having a chance to properly shut down, it won’t remove the socket file. The next time you try to connect to that host, you won’t be able to, and you’ll see an error message like:

Control socket connect(/home/calvin/.ssh/master-turing-calvin@example.net:1234): Connection refused
ControlSocket /home/calvin/.ssh/master-turing-calvin@example.net:1234 already exists

You’ll have to manually delete the socket file that it is complaining about, and then you’ll be able to connect again, and it will establish a new one if it’s a master connection.

Exiting master connection hangs if there are still slaves

If you mistakenly exit from the master connection you’ve opened while you still have slave connections open, the master process will just hang there until all the slave connections exit. This is arguably what should happen, since the not-so-desirable alternative would be for the master to forcibly kill all the slave connections and then exit. Still, it’s easy to keep mistakenly doing this if you open multiple connections in different terminals and forget which is which.

One simple solution is to create the master connection somewhere it won’t be used. You could use something like screen to open connections in the background somewhere where they won’t be seen or used when your system starts or when X starts, or you can just open a few terminals in a workspace that you don’t use or a virtual terminal.

If you want to really be sure you don’t use the connection, you can always start it with the -N option (and anywhere from 1 to 3 -v flags if you want to see output related to that master connection). SSH will make the connection and not invoke a shell or any other command on the other end. The net result is that you can’t do anything with that connection, except kill it when you’re finished. (Thanks for this suggestion, lacos.)

Conclusion

Feedback is welcome at initials-code@thisdomain, where the initials are cs and the domain is protempore.net.

2008-06-24: thanks to Reddit readers lacos and Anthony M. for their suggestions.

This page is valid HTML5 & CSS, and is licensed under a Creative Commons License.