Always Port Forward with autossh and systemd/launchd on Mac and Ubuntu
- 10/20/2022
- Update: 10/20/2022
- Linux
Hello. This time we’ll combine autossh, which maintains SSH connections as much as possible, with systemd and launchd, which manage services on Linux and Unix, to create an environment that always performs SSH port forwarding.
This configuration eliminates the need to manually make SSH or VPN connections every time when viewing sites that can only be viewed or accessed under specific networks.
What is autossh
autossh is a CLI command that maintains SSH connections as much as possible. Install it with the following methods:
# Ubuntu
sudo apt install autossh
# macOS
brew install autossh
Command usage is almost the same as ssh:
autossh -M 0 -f -L 12022:remote:22
The -M
option is for monitor port. If there’s nothing special, you can disable it with 0. Details are described in man autossh
.
The -f
option runs autossh in the background.
Note that you cannot perform input like password requests, so when using this option, you need to be able to log into the remote without a password.
Combining autossh with systemd and launchd
systemd is Ubuntu’s service management tool, and launchd is macOS’s service management tool. By registering autossh with systemd and launchd:
- If SSH drops, autossh reconnects
- If autossh drops, systemd/launchd reconnects
- At system startup, systemd/launchd connects
This way, you can maintain SSH connections more robustly. This time, we’ll port forward using autossh based on settings written in ssh config.
Preparation
First, as preparation, set up passwordless SSH connection to the remote. It’s good to set SSH authentication to public key authentication.
Note that on Ubuntu, if you set a passphrase on the private key,
while on macOS you can set UseKeychain
in ~/.ssh/config
so that
the passphrase is saved in Keychain when using SSH and automatically loaded into ssh-agent,
Ubuntu doesn’t have Keychain so you need to do it yourself.
As mentioned earlier, autossh can’t handle input during SSH, so when using autossh on Ubuntu, you need to prepare a private key without a passphrase. On Mac, Keychain handles this nicely so it’s not necessary. You can set/remove private key passwords with the following command:
ssh-keygen -f ~/.ssh/id_rsa -p
After resolving the above, perform an initial normal SSH connection. This is because fingerprint confirmation occurs during initial SSH and requires a response, to prevent this from happening when using autossh. This time we’ll SSH with settings written in ssh config:
Host remote
HostName hoge
User hoge
LocalForward 6006 localhost:6006
ssh remote
The authenticity of host 'hoge (::1)' can't be established.
ED25519 key fingerprint is SHA256:hogehogehogehoge.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Success if initial login completes without password input other than yes. Let’s proceed.
Making autossh a Service
Next, we’ll write configs to make autossh a service.
Mac: Launchd Case
First, let’s check the full path location of autossh:
$ which autossh
/opt/homebrew/bin/autossh
This time it appears to be in /opt/homebrew/bin
. On Intel Mac it might be in /usr/local/bin
.
There are several locations for Launchd configuration files, but this time we’ll have it execute when the user logs in.
This way, the contents of ~/.ssh/config
written earlier can be used.
Place a file with the following contents in ~/Library/LaunchAgents/remote.autossh.plist
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>EnvironmentVariables</key>
<dict>
<key>AUTOSSH_GATETIME</key>
<integer>0</integer>
</dict>
<key>UserName</key>
<string>[username]</string>
<key>Label</key>
<string>remote.autossh</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin</string>
<string>-M</string>
<string>0</string>
<string>-N</string>
<string>remtote</string>
</array>
</dict>
</plist>
Once the file is written, register it with launchd. Please write the plist path as an absolute path:
launchctl load /Users/[username]/Library/LaunchAgents/remote.autossh.plist
This time we added RunAtLoad
to the plist, so execution should occur when loaded. Let’s check:
$ launchctl list | grep remote.autossh
57892 0 remote.autossh
From left: PID, exit code, and Label are displayed. This time there’s a PID and exitcode=0, so it’s successful. To remove from service, use unload:
launchctl unload /Users/[username]/Library/LaunchAgents/remote.autossh.plist
When ssh config is changed, restart with stop, start
:
launchctl stop iplab.autossh
launchctl start iplab.autossh
Ubuntu: Systemd Case
On Ubuntu, we’ll configure systemd. CentOS and Amazon Linux etc. (RedHat family) also use the same systemd, so with slight changes you should be able to configure similarly (unconfirmed).
Write configuration in /etc/systemd/system/remote.autossh.service
:
[Unit]
Description=keep ssh
After=network-online.target ssh.service
[Service]
User=[username]
Type=simple
RestartSec=3
Restart=always
TimeoutStopSec=10
Environment="AUTOSSH_GATETIME=0"
ExecStart=/usr/bin/autossh -M 0 -N remote
[Install]
WantedBy=multi-user.target
Finally, start the service, check status to see if it’s working normally, and set it for auto-start:
sudo systemctl start remote.autossh.service
sudo systemctl status remote.autossh.service
sudo systemctl enable remote.autossh.service
That completes the setup. Good work!