C# Unix domain sockets
last modified October 17, 2026
In this article we show how to use Unix domain sockets in C#.
A Unix domain socket is an endpoint for communication between
processes on the same computer. Instead of an IP address and a port, it is
identified by a path in the file system, such as
/var/run/docker.sock. The data never leaves the computer and does
not pass through the network stack, so Unix domain sockets are faster than TCP
over the loopback interface. Access is controlled by the file system
permissions of the socket file.
In .NET, a Unix domain socket is a regular Socket created with the
AddressFamily.Unix address family; its address is a
UnixDomainSocketEndPoint. Despite the name, Unix domain sockets
are also supported on Windows 10 and later.
C# Unix domain socket echo
In the first example, we create a server and a client that communicate through a Unix domain socket.
using System.Net.Sockets;
using System.Text;
string path = Path.Combine(Path.GetTempPath(), "echo.sock");
File.Delete(path);
var endpoint = new UnixDomainSocketEndPoint(path);
using var server = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
server.Bind(endpoint);
server.Listen();
Console.WriteLine($"Listening on {path}");
Task serverTask = Task.Run(async () =>
{
using Socket conn = await server.AcceptAsync();
var buffer = new byte[1024];
int n = await conn.ReceiveAsync(buffer);
string msg = Encoding.UTF8.GetString(buffer, 0, n);
Console.WriteLine($"Server received: {msg}");
await conn.SendAsync(Encoding.UTF8.GetBytes(msg.ToUpperInvariant()));
});
using (var client = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified))
{
await client.ConnectAsync(endpoint);
await client.SendAsync(Encoding.UTF8.GetBytes("hello from the client"));
var buffer = new byte[1024];
int n = await client.ReceiveAsync(buffer);
Console.WriteLine($"Client received: {Encoding.UTF8.GetString(buffer, 0, n)}");
}
await serverTask;
File.Delete(path);
The client sends a message; the server sends it back in uppercase.
string path = Path.Combine(Path.GetTempPath(), "echo.sock"); File.Delete(path); var endpoint = new UnixDomainSocketEndPoint(path);
The socket is identified by a path in the temporary directory. If a socket
file is left over from a previous run, binding would fail, so we delete it
first. The File.Delete method does nothing if the file does not
exist.
using var server = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); server.Bind(endpoint); server.Listen();
We create a stream socket in the Unix address family. Binding creates the
socket file; Listen starts listening for connections. The rest is
the same as with TCP sockets.
using Socket conn = await server.AcceptAsync();
The server task accepts a connection, receives a message, and sends a reply.
await client.ConnectAsync(endpoint);
await client.SendAsync(Encoding.UTF8.GetBytes("hello from the client"));
The client connects to the same endpoint and sends a message.
$ dotnet run Listening on /tmp/echo.sock Server received: hello from the client Client received: HELLO FROM THE CLIENT
C# HttpClient over a Unix domain socket
Some services provide an HTTP API on a Unix domain socket. A well-known
example is the Docker Engine, which listens on
/var/run/docker.sock. The SocketsHttpHandler has a
ConnectCallback property, with which we can tell
HttpClient how to open its connections. In the following example,
we use it to call the Docker Engine API.
using System.Net.Http.Json;
using System.Net.Sockets;
var handler = new SocketsHttpHandler
{
ConnectCallback = async (context, token) =>
{
var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
try
{
await socket.ConnectAsync(new UnixDomainSocketEndPoint("/var/run/docker.sock"), token);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
};
using var client = new HttpClient(handler)
{
BaseAddress = new Uri("http://docker/")
};
var version = await client.GetFromJsonAsync<DockerVersion>("version");
Console.WriteLine($"Docker {version?.Version}, API {version?.ApiVersion}, {version?.Os}/{version?.Arch}");
var images = await client.GetFromJsonAsync<List<DockerImage>>("images/json");
var tagged = (images ?? []).Where(image => image.RepoTags is { Count: > 0 });
foreach (var image in tagged)
{
Console.WriteLine($"{image.RepoTags![0],-40} {image.Size / 1024 / 1024,5} MB");
}
record DockerVersion(string Version, string ApiVersion, string Os, string Arch);
record DockerImage(string Id, List<string>? RepoTags, long Size);
The program prints the Docker version and the list of local images. To run it,
Docker must be installed and the user must have access to the socket; on
Linux, the user must be a member of the docker group.
ConnectCallback = async (context, token) =>
{
var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
try
{
await socket.ConnectAsync(new UnixDomainSocketEndPoint("/var/run/docker.sock"), token);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
The callback is invoked whenever the handler needs a new connection. Instead of
connecting to the host and port from the URL, we connect to the Docker socket
and return a stream for it. With ownsSocket: true, disposing the
stream also closes the socket. If the connection fails, we dispose the socket
and rethrow the exception.
using var client = new HttpClient(handler)
{
BaseAddress = new Uri("http://docker/")
};
The host name in the URL is not used to connect, but HTTP requires one in the
Host header, so we choose an arbitrary name.
var version = await client.GetFromJsonAsync<DockerVersion>("version");
The GetFromJsonAsync method sends a GET request and deserializes
the JSON response into a record. We declare only the properties we need; the
other properties of the response are ignored. The property names are matched
case-insensitively.
$ dotnet run Docker 29.1.3, API 1.52, linux/amd64 mcr.microsoft.com/dotnet/sdk:10.0 875 MB mcr.microsoft.com/dotnet/aspnet:10.0 219 MB mcr.microsoft.com/dotnet/runtime:10.0 193 MB mysql:8.4 772 MB python:3.14-bookworm 977 MB
Note that the example must be run as a project created with
dotnet new console. File-based apps run with
dotnet run app.cs have Native AOT enabled by default, which turns
off the reflection-based JSON serialization used here.
Source
UnixDomainSocketEndPoint class - language reference
In this article we have used Unix domain sockets in C#.
Author
List all C# tutorials.