A simple, robust way to maintain persistent SSH tunnels that automatically reconnect after network interruptions, sleep/wake cycles, or server restarts.
Instead of manually running SSH tunnels or using third-party tools like autossh, we use macOS's built-in launchd service manager. It handles:
- Automatic startup on login
- Automatic restart when the connection drops
- Reconnection after sleep/wake cycles
- Throttling to prevent rapid reconnection loops
- SSH key-based authentication configured for your remote host
- An SSH config entry for your remote server (optional but recommended)
Example ~/.ssh/config entry:
Host myserver
HostName your.server.ip
User youruser
Create ~/Library/LaunchAgents/com.user.port-forward-myserver.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>Label</key>
<string>com.user.port-forward-myserver</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>sleep 5 && /usr/bin/ssh -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -L 3000:localhost:3000 -L 5173:localhost:5173 -L 7777:localhost:7777 myserver</string>
</array>
<key>KeepAlive</key>
<true/>
<key>ThrottleInterval</key>
<integer>3</integer>
<key>StandardErrorPath</key>
<string>/tmp/port-forward-myserver.err</string>
</dict>
</plist>| Option | Purpose |
|---|---|
KeepAlive: true |
launchd restarts the process whenever it exits |
ThrottleInterval: 3 |
Wait at least 3 seconds between restart attempts |
sleep 5 |
Initial delay to let networking come up after login/wake |
-N |
No remote command—just forward ports |
-o ExitOnForwardFailure=yes |
Exit if port forwarding fails (triggers launchd restart) |
-o ServerAliveInterval=30 |
Send keepalive every 30 seconds |
-o ServerAliveCountMax=3 |
Disconnect after 3 missed keepalives (~90 seconds) |
# Load and start the service
launchctl load ~/Library/LaunchAgents/com.user.port-forward-myserver.plist
# Stop and unload the service
launchctl unload ~/Library/LaunchAgents/com.user.port-forward-myserver.plist
# Check if it's running
launchctl list | grep port-forward
# View errors
cat /tmp/port-forward-myserver.errAdd multiple -L flags to forward additional ports:
-L 3000:localhost:3000 -L 8080:localhost:8080 -L 5432:localhost:5432
autossh is great, but this approach:
- Uses only built-in macOS tools
- No Homebrew dependency
- launchd handles process supervision better than a wrapper script
- Native integration with macOS login/logout lifecycle
Tunnel not starting? Check /tmp/port-forward-myserver.err for SSH errors.
Rapid reconnection loops? Increase ThrottleInterval to slow down retry attempts.