Skip to content
macOSDeep Dive Published Updated 2 min readViews unavailable

macOS launchd Socket Activation: Let the Service Manager Own Listeners

Configure launchd-managed sockets and retrieve their descriptors with launch_activate_socket instead of racing to bind a port in every daemon.

Socket activation lets a macOS service manager create a listener before the service process starts. The job’s launchd property list describes sockets; launchd creates the configured descriptors and can start the job when the listener receives work. This separates the stable listening endpoint from the lifetime of the process that handles requests.

For modern code, Apple’s launch_activate_socket API retrieves descriptors by the name of an entry in the job’s Sockets dictionary. It returns an allocated array, so the caller must free that array, and it may contain more than one descriptor. Code must iterate over the returned descriptors rather than assume a single IPv4 listener.

Keep the plist and server contract aligned

A simplified job fragment might declare a passive TCP listener:

<key>Sockets</key>
<dict>
  <key>HTTPListener</key>
  <dict>
    <key>SockServiceName</key>
    <string>8080</string>
    <key>SockType</key>
    <string>stream</string>
    <key>SockFamily</key>
    <string>IPv4</string>
    <key>SockPassive</key>
    <true/>
  </dict>
</dict>

The service then checks in by key:

int *fds = NULL;
size_t count = 0;
int status = launch_activate_socket("HTTPListener", &fds, &count);
if (status != 0) {
    /* status is an error code; do not assume errno was set. */
    return status;
}
for (size_t i = 0; i < count; i++) {
    /* Register each listener with the event loop. */
}
free(fds);

The plist example is a fragment, not a complete launch job. The daemon still needs a correct accept loop, descriptor lifecycle, privilege model, and protocol implementation. Apple documents distinct failures for a missing socket name, a process not managed by launchd, and a socket already activated by that caller; treat those as setup or lifecycle errors instead of repeatedly retrying.

Operational checks

Validate the property list before bootstrapping it, confirm the label and domain, and inspect launchd’s job state and logs after activation. Test both cold activation and repeated connections. Ensure the service can drain or reject work during shutdown and that it does not attempt to bind the same address independently. Otherwise, launchd and the process can contend for the endpoint or the daemon may fail only after a restart.

Socket activation is a lifecycle mechanism, not an authorization policy. Apply firewall and application-level access controls, avoid exposing a listener beyond the intended interface, and document whether the service accepts local, IPv4, IPv6, or Unix-domain clients.

Related:

Sources:

Comments