Skip to content

Instantly share code, notes, and snippets.

@knowsuchagency
Last active December 30, 2025 00:27
Show Gist options
  • Select an option

  • Save knowsuchagency/60656087903cd56d3a9b5d1d5c803186 to your computer and use it in GitHub Desktop.

Select an option

Save knowsuchagency/60656087903cd56d3a9b5d1d5c803186 to your computer and use it in GitHub Desktop.
Resilient SSH port forwarding on macOS using launchd

Resilient SSH Port Forwarding on macOS with launchd

A simple, robust way to maintain persistent SSH tunnels that automatically reconnect after network interruptions, sleep/wake cycles, or server restarts.

Overview

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

Prerequisites

  1. SSH key-based authentication configured for your remote host
  2. An SSH config entry for your remote server (optional but recommended)

Example ~/.ssh/config entry:

Host myserver
    HostName your.server.ip
    User youruser

The Launch Agent

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 &amp;&amp; /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>

Key Configuration Explained

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)

Managing the Service

# 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.err

Forwarding Multiple Ports

Add multiple -L flags to forward additional ports:

-L 3000:localhost:3000 -L 8080:localhost:8080 -L 5432:localhost:5432

Why Not autossh?

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

Troubleshooting

Tunnel not starting? Check /tmp/port-forward-myserver.err for SSH errors.

Rapid reconnection loops? Increase ThrottleInterval to slow down retry attempts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment