Launchd (macOS)

Ghostunnel can run as a macOS daemon managed by launchd. Launchd socket activation is supported for the --listen and --status flags by passing an address of the form launchd:<name>, where <name> matches the socket key defined in your plist.

Ghostunnel can also load TLS identities from the system keychain via --keychain-identity. See Keychain.

Example Plist

A launchd plist to run Ghostunnel in server mode, listening on :8081, with a status port on :8082, forwarding connections to :8083:

<?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>ghostunnel</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/bin/ghostunnel</string>
      <string>server</string>
      <string>--keystore</string>
      <string>/etc/ghostunnel/server-keystore.p12</string>
      <string>--cacert</string>
      <string>/etc/ghostunnel/cacert.pem</string>
      <string>--target</string>
      <string>localhost:8083</string>
      <string>--listen</string>
      <string>launchd:Listener</string>
      <string>--status</string>
      <string>launchd:Status</string>
      <string>--allow-cn</string>
      <string>client</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/var/log/ghostunnel.out.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/ghostunnel.err.log</string>
    <key>Sockets</key>
    <dict>
      <key>Listener</key>
      <dict>
        <key>SockServiceName</key>
        <string>8081</string>
        <key>SockType</key>
        <string>stream</string>
        <key>SockFamily</key>
        <string>IPv4</string>
      </dict>
      <key>Status</key>
      <dict>
        <key>SockServiceName</key>
        <string>8082</string>
        <key>SockType</key>
        <string>stream</string>
        <key>SockFamily</key>
        <string>IPv4</string>
      </dict>
    </dict>
  </dict>
</plist>

RunAtLoad starts the service when the plist is bootstrapped (or at boot for system daemons). KeepAlive restarts the process if it exits unexpectedly, equivalent to systemd’s Restart=always.

Both SockType and SockFamily must be defined for each socket. If the family is omitted, launchd opens two sockets (IPv4 and IPv6) for each key, which Ghostunnel does not currently support.

Installing

# Copy the plist into place
sudo cp ghostunnel.plist /Library/LaunchDaemons/

# Load and start (modern macOS)
sudo launchctl bootstrap system/ /Library/LaunchDaemons/ghostunnel.plist

# Stop and unload
sudo launchctl bootout system/ghostunnel

On older macOS versions (before 10.11), use launchctl load and launchctl unload instead.

Use ~/Library/LaunchAgents/ (with gui/<uid>/ instead of system/) if running as a user agent rather than a system daemon.

Reloading Certificates

To reload certificates without restarting the service, send SIGHUP:

sudo launchctl kill SIGHUP system/ghostunnel

For automatic periodic reloads (e.g. with short-lived certificates), pass --timed-reload DURATION in the plist’s ProgramArguments. Ghostunnel re-reads the keystore at that interval and refreshes the listener if the certificate changed.

Graceful Shutdown

By default, launchd waits 20 seconds between SIGTERM and SIGKILL. If Ghostunnel’s --shutdown-timeout (default 5m) exceeds that window, in-flight connections will be cut off. To allow the full drain window, raise ExitTimeOut in the plist:

<key>ExitTimeOut</key>
<integer>360</integer>