Many local APIs expose HTTP endpoints on Unix domain sockets instead of TCP ports so they stay bound to the host and avoid opening an extra listener on the network. That makes socket-based requests useful for container runtimes, reverse proxies, build daemons, and other control-plane services that are meant to be reached only from the local machine.

With cURL, --unix-socket replaces the normal TCP connect step with a filesystem socket path while the URL still supplies the request scheme, host, and path. The transport goes through the socket file, but the HTTP layer still sees the URL host unless Host: is overridden explicitly, which matters for name-based routing behind a local reverse proxy or multi-tenant listener.

Socket access is controlled by normal filesystem permissions, so ownership and group membership decide who can talk to the service. Most local sockets expect plain HTTP rather than HTTPS, and Linux abstract namespace sockets use --abstract-unix-socket with the socket name only, without the leading @ that tools such as ss display. The examples below keep the service name, hostname, and socket path masked while preserving a realistic local API layout.

Steps to connect over Unix sockets with cURL:

  1. Inspect the socket path first so the request targets the right file and the current user can actually open it.
    $ ls -l /run/local-api/http.sock
    srw-rw----  1 root  localapi  0 Mar 29 10:14 /run/local-api/http.sock

    Privileged sockets can expose full administrative control over the backing service. Treat access to paths such as /var/run/docker.sock as equivalent to privileged local access.

    Use --unix-socket for pathname sockets like this one. On Linux, abstract namespace sockets use --abstract-unix-socket and the argument should not include the leading @ shown by some tools.

  2. Send a basic request through the Unix socket to confirm that the service answers normally over local HTTP.
    $ curl --silent --show-error \
      --unix-socket /run/local-api/http.sock \
      http://localhost/ping
    OK

    Once cURL reaches the socket successfully, request paths, headers, methods, and response handling work the same way as a normal HTTP request.

  3. Use the URL host to target the expected virtual host or tenant while keeping the transport on the same local socket.
    $ curl --silent --show-error \
      --unix-socket /run/local-api/http.sock \
      http://tenant-preview.internal.example/health
    {
      "status": "ok",
      "service": "local-api",
      "route": "tenant-preview",
      "path": "/health",
      "host": "tenant-preview.internal.example"
    }

    The socket path decides how cURL connects, but the URL host still becomes the HTTP Host value. That is why a masked name such as tenant-preview.internal.example can still select the correct local site or tenant over the same socket.

  4. Inspect the verbose transcript when the request needs proof that the transfer used the Unix socket path rather than a network port.
    $ curl -q --verbose --silent --show-error --output /dev/null \
      --unix-socket /run/local-api/http.sock \
      http://localhost/health 2>&1 | grep -E '^\*   Trying|^\* Connected|^> GET|^> Host:'
    *   Trying /run/local-api/http.sock:0...
    * Connected to localhost (/run/local-api/http.sock) port 0
    > GET /health HTTP/1.1
    > Host: localhost

    The Trying and Connected to lines should show the socket path, and port 0 is expected because Unix domain sockets do not use TCP ports. The -q option keeps personal ~/.curlrc defaults out of the debug transcript so the decisive lines stay readable.

  5. Turn the same request into a script-friendly health check by discarding the body and printing only the response and exit status.
    $ curl --silent --show-error --output /dev/null \
      --write-out 'status=%{response_code}\nexit=%{exitcode}\n' \
      --unix-socket /run/local-api/http.sock \
      http://localhost/ping
    status=200
    exit=0

    A healthy check returns a real HTTP status plus exit=0. If the socket path is missing or inaccessible, cURL fails before any HTTP response is received, which usually leaves status=000 with a non-zero exit code such as 7.