Bluetooth in Snapcast

Posted by Nathan Osman on March 28, 2026

One of the things I’ve been experimenting with when building out my smart home is multi-room audio (the ability to play perfectly synchronized audio from multiple devices). Thankfully there is already a mature solution for routing and synchronizing audio: Snapcast. The only tricky part is getting audio from various sources and feeding it into the Snapcast server. In this article, we will take a look at Bluetooth audio via A2DP.

The Goal

This article is going to walk you through setting up a headless Linux device that hosts the Snapcast server. We are going to assume a Debian-based distribution, which means these instructions should also work well on a Raspberry Pi running Raspberry Pi OS. This article may still be useful if you are using another distribution, though you will have to adjust the package commands, at the very least.

You will also need a connected Bluetooth receiver if your device doesn’t include one.

Setting Up the Host

Begin by logging into the host and running the following command:

sudo loginctl enable-linger $USER

This enables applications to run under your account even when you aren’t logged in. Without linger enabled, systemd user services (such as PipeWire) wouldn’t start until you logged in and would be terminated when you logged out.

Speaking of PipeWire, let’s install it and a few other necessary packages:

sudo apt install bluez libspa-0.2-bluetooth pipewire wireplumber snapserver

Setting Up Pipewire

The first step is to enable PipeWire and WirePlumber (session manager for PipeWire):

systemctl --user enable pipewire
systemctl --user enable wireplumber

Snapserver cannot read directly from PipeWire, but it can read from a FIFO pipe. Therefore we need to create a sink that writes its data to a pipe. We can do this by creating a configuration file:

mkdir -p ~/.config/pipewire/pipewire.conf.d
cat > ~/.config/pipewire/pipewire.conf.d/snapserver.conf << 'EOF'
context.modules = [
  {
    name = libpipewire-module-pipe-tunnel
    args = {
      tunnel.mode      = sink
      pipe.filename    = "/tmp/snapfifo"
      node.name        = "Snapserver"
      node.description = "Snapserver Sink"
      media.class      = Audio/Sink
      audio.position   = [ FL FR ]
      audio.format     = S16LE
      audio.rate       = 48000
      audio.channels   = 2
    }
  }
]
EOF

Start PipeWire with:

systemctl --user start pipewire

Setting Up WirePlumber

We need to work around a little quirk in WirePlumber. The login service (logind) assigns seat states to sessions. Remote sessions are marked as “online” but the bluez.lua script included with WirePlumber looks for “active” desktop sessions before monitoring for Bluetooth devices. So we need to disable this behavior and the best way to do that is to create a copy of the default wireplumber.conf:

mkdir -p ~/.config/wireplumber
cp /usr/share/wireplumber/wireplumber.conf ~/.config/wireplumber

Now open the file and scroll down to the “main” section:

wireplumber.profiles = {
  main = {
    ...

Add the following line to the “main” section:

wireplumber.profiles = {
  main = {
    monitor.bluez.seat-monitoring = disabled
    ...

Save and close the file. WirePlumber will now use your modified user copy of wireplumber.conf instead of the system-wide one.

We also need to tell WirePlumber to use our PipeWire sink from above whenever a Bluetooth device is connected. Fortunately, this is pretty easy to do:

mkdir -p ~/.config/wireplumber/wireplumber.conf.d
cat > ~/.config/wireplumber/wireplumber.conf.d/snapserver.conf << 'EOF'
monitor.bluez.rules = [
  {
    matches = [
      {
        "node.name" = "~bluez_input.*"
      }
    ]
    actions = {
      update-props = {
        "node.target" = "Snapserver"
      }
    }
  }
]
EOF

When a supported Bluetooth audio device connects, a node is created. The rule above will match it and set its target property to the “Snapserver” sink, instead of the default audio sink (which is usually the primary sound card).

Start WirePlumber with:

systemctl --user start wireplumber

Setting Up Snapcast and Bluetooth

We’re almost there!

Open the /etc/snapserver.conf file and scroll down to the [stream] section and find the source directive. Change it to:

source = pipe:///tmp/snapfifo?name=PipeWire&codec=pcm&sampleformat=48000:16:2

Snapserver is probably already running, so you will need to restart it to pick up the changes:

systemctl --user restart snapserver

Lastly we can set the name of our Bluetooth device:

bluetoothctl system-alias "Snapcast Audio"

Testing It Out

You can now pair a device to “Snapcast Audio” and it should be streamed to the Snapcast server. Other Snapcast clients on the local network should automatically find it and begin playing the audio. If your Bluetooth device is having trouble connecting to the host, try trusting and initiating the connection from the host:

bluetoothctl trust [MAC]
bluetoothctl connect [MAC]