Music Server

12 July 2024 V1.0

Introduction

If you are motivated by a desire to not to have to pay a music streaming service to curate your music collection and to not even own it as it’s licenced back to you, you are in good company and have arrived in the right place.

I have a moderately sized music collection that I have converted to FLAC format and stored on my Network Accessible Storage (NAS) that I stream within my house.  For listening in the car the FLAC files have been transcoded to mp3 format at a bit rate of 256 kbps and copied to a 64 Gb USB thumb drive.  However, this is far from ideal.  My car’s music system doesn’t seem to be able to handle more than 64 Gb of music files and it becomes tedious when deciding on what to delete when copying new music to the thumb drive.

This started me thinking about streaming music directly to my phone or other compatible device. My search eventually led me to Navidrome and it seemed almost too good to be true. Key features are:

  • Very low resource usage. Runs well even on simple Raspberry Pi Zero and old hardware setups
  • Handles very large music collections
  • Streams virtually any audio format available
  • Reads and uses all your beautifully curated metadata
  • Great support for compilations (Various Artists albums) and box sets (multi-disc albums)
  • Multi-user, each user has their own play counts, playlists, favorites, etc..
  • Multi-platform, runs on macOS, Linux and Windows. Docker images are also provided
  • Ready to use, official, Raspberry Pi binaries and Docker images available
  • Automatically monitors your library for changes, importing new files and reloading new metadata
  • Themeable, modern and responsive Web interface based on Material UI and React-Admin
  • Compatible with all Subsonic/Madsonic/Airsonic clients. See below for a list of tested clients
  • Transcoding on the fly. Can be set per user/player. Opus encoding is supported
  • Translated to 17 languages (and counting)
  • Full support for playlists, with option to auto-import .m3u files and to keep them in sync
  • Smart/dynamic playlists (similar to iTunes). More info here
  • Scrobbling to Last.fmListenBrainz and Maloja (via custom ListenBrainz URL)
  • Sharing public links to albums/songs/playlists
  • Reverse Proxy authentication*
  • Jukebox mode allows playing music on an audio device attached to the server, and control from a client

Bill of Materials

Hardware

HardwareQtyUnit PricePriceSourcePart No.
Raspberry Pi4 (4 Gb)1£55.00£55.00The Pi HutSC0194
Pi4 Case1£14.00£14.00The Pi Hut102476
Pi4 PSU1£9.00£9.00The Pi HitSC0443
microSD (32Gb)1£8.00£8.00The Pi HutSD-32GB
External SSD (240 Gb)1£36.50£36.50The Pi Hut103410
SSD Cable1£6.00£6.00The Pi Hut103222
Total  £128.50  
Table 1 – Hardware

Prices correct at time of writing in June 2024

Software

SoftwarePurpose
NavidromeStreaming app
Raspberry PiOSOperating System
Apache 2.4Configure for reverse proxy with https
Table 2 – Software

Software Installation

If you haven’t got the Raspberry Pi OS imager get it here:

https://downloads.raspberrypi.org/imager/imager_latest.exe

And follow the installation instructions

Get the most recent version of Raspberry Pi OS here:

https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz

Or choose from the download options in the image installation software.  As this is going to be a headless server I recommend getting the “lite” version.

Customise OS Settings

Within the imager application change the following before writing the OS image to the SD card:

General

  • Set hostname : <Your_hostname>
  • Set username and password
  • Configure wireless LAN
  • Set locale
  • Services
  • Enable SSH

Use password authentication for SSH (we’ll setup public-key auth later)

Apply custom settings and let the image be written to the SD card. When done, remove SD card from computer and insert into the RPi.

Power up the RPi.  As the RPi has setup with default network settings you will need to scan your network to discover the IP address that has been assigned via DHCP.  Once you have determined the IP address of the RPi:

Login via SSH and update packages

sudo apt update

Then

sudo apt full-upgrade

Then

sudo reboot

Next, if it doesn’t exist, we’re going to create the filesystem on the external SSD.

When rebooted, log back in via SSH and locate the external hard drive

sudo fdisk -l

Look for a device in the list that has the expected file size, e.g. /dev/sda.  Use this to create the file system on the disk using parted.

Disk /dev/sda: 223.57 GiB, 240057409536 bytes, 468862128 sectors
Disk model: ASM1153USB3.0TOS
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 33553920 bytes

Create partition using GNU parted

Note: parted while relatively easy to use does have some quirks.  It took a few trial runs and an Internet search for the warning messages to get a partition with settings that I was happy with.

sudo parted /dev/sda

This starts parted in interactive mode and after a bit of fiddling around with the options to eliminate the “mis-alignment”, “non-optimised” messages I arrived at this for the command line options:

mkpart gpt ext4 0% 100%

Make the filesystem

mkfs.ext4 /dev/sda1

Create mount point

sudo mkdir /media/music

Mount the drive

sudo mount  /media/music

To maximise the free space run

sudo tune2fs -r 0 /dev/sda1

Auto mount on reboot

sudo blkid
/dev/sda1: UUID="24d169ac-0059-4932-bb75-10eb9c214ef8" BLOCK_SIZE="4096" TYPE="ext4" PARTLABEL="gpt" PARTUUID="2dc7ab0b-5407-4ced-b1c0-09f17fc6271f"

Edit /etc/fstab and insert

UUID=24d169ac-0059-4932-bb75-10eb9c214ef8    /media/music   ext4  defaults  0  0

And reboot

sudo reboot

And that concludes the initial build of the RPi.

Install & Configure Navidrome

Instructions here:

https://www.navidrome.org/docs/installation

Navidrome can be installed either through Docker or with pre-compiled binaries.  Most of the tutorials for installing Navidrome seem to focus on using Docker.  Given the range of pre-compiled binaries available I’m not sure why there is such a focus on installing with Docker

To determine which package need to be downloaded type:

uname -a
Linux neutrino 6.6.28+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.6.28-1+rpt1 (2024-04-22) aarch64 GNU/Linux

aarch64 is the same as arm64 so the package required is:

https://github.com/navidrome/navidrome/releases/download/v0.52.5/navidrome_0.52.5_linux_arm64.tar.gz

Download the binary as follows:

cd /home/pi/Downloads
wget https://github.com/navidrome/navidrome/releases/download/v0.52.5/navidrome_0.52.5_linux_arm64.tar.gz -O Navidrome.tar.gz

Install dependencies

sudo apt install vim ffmpeg

Create user: navidrome

sudo adduser navidrome --system --no-create-home

Create group: navidrome

sudo groupadd navidrome

Add user navidrome to group navidrome

sudo usermod --append --groups navidrome navidrome

Create directory structure

sudo install -d -o navidrome -g navidrome /opt/navidrome
sudo install -d -o navidrome -g navidrome /var/lib/navidrome

Install navidrome

sudo tar -xvzf Navidrome.tar.gz -C /opt/navidrome/
sudo chown -R navidrome:navidrome /opt/navidrome

Create configuration file

cd /var/lib/navidrome

In the working directory, /var/lib/navidrome create a new file named navidrome.toml with the following settings:

sudo nano navidrome.toml and add:

MusicFolder = "/media/music"

Create a Systemd unit called navidrome.service in /etc/systemd/system

cd /etc/systemd/system
sudo touch navidrome.service
sudo chmod 644 navidrome.service
sudo vi navidrome.service

Add the following to the unit:

[Unit]
Description=Navidrome Music Server and Streamer compatible with Subsonic/Airsonic
After=remote-fs.target network.target
AssertPathExists=/var/lib/navidrome
[Install]
WantedBy=multi-user.target
[Service]
User=<user>
Group=<group>
Type=simple
ExecStart=/opt/navidrome/navidrome --configfile "/var/lib/navidrome/navidrome.toml"
WorkingDirectory=/var/lib/navidrome
TimeoutStopSec=20
KillMode=process
Restart=on-failure
# See https://www.freedesktop.org/software/systemd/man/systemd.exec.html
DevicePolicy=closed
NoNewPrivileges=yes
PrivateTmp=yes
PrivateUsers=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap
ReadWritePaths=/var/lib/navidrome
# You can uncomment the following line if you're not using the jukebox This
# will prevent navidrome from accessing any real (physical) devices
#PrivateDevices=yes
# You can change the following line to `strict` instead of `full` if you don't
# want navidrome to be able to write anything on your filesystem outside of
# /var/lib/navidrome.
ProtectSystem=full
# You can uncomment the following line if you don't have any media in /home/*.
# This will prevent navidrome from ever reading/writing anything there.
ProtectHome=true
# You can customize some Navidrome config options by setting environment variables here. Ex:
#Environment=ND_BASEURL="/navidrome"

Now start Navidrome and check that it is running correctly:

sudo systemctl daemon-reload
sudo systemctl start navidrome.service
sudo systemctl status navidrome.service

Enable startup on boot:

sudo systemctl enable navidrome.service

That’s pretty much the initial configuration complete.  If you run a zoned network you might need to add or adjust your firewall rules at this point to permit access on the internal network. At this point your Navidrome server isn’t (shouldn’t be) visible externally on the Internet. That requires some additional configuration that is discussed in later sections.

If Navidrome is being run on a separate network or sub net jump forward at this point to Firewall Configuration to set the rules required and then return here when done.

Connect to Navidrome for the first time by browsing to its home page at:

http://<IIP Address for Navidrome>:4533

Create your first user. This will be your admin user, a super user that can manage all aspects of Navidrome, including the ability to manage other users.

Post Install Configuration

Set hostname by editing /etc/hostname and adding the FQDN for your server

<hostname.domain.tld>

Although Navidrome is now installed properly and can be used, it is highly recommended that you implement the following post-installation steps.

Encrypted passwords

“To be able to keep compatibility with the Subsonic API and its clients, Navidrome needs to store user’s passwords in its database. By default, Navidrome encrypts the passwords in the DB with a shared encryption key, just for the sake of obfuscation as this key can be easily found in the codebase.

This key can be overridden by the config option PasswordEncryptionKey. Once this option is set and Navidrome is restarted, it will re-encrypt all passwords with this new key. This is a one-time only configuration, and after this point the config option cannot be changed anymore or else users won’t be able to authenticate.

Set Encryption Key

Due to limitations with the Subsonic API, Navidrome is unable to properly hash passwords and thus encrypts them.  We need to change the default encryption key.

Edit the navidrome.toml file in /var/lib/navidrome again and set the PasswordEncryptionKey option.

PasswordEncryptionKey = "<Your Strong Encryption Key Here"

Along with adding a password encryption key some additional tweaks to the navidrome configuration file can be made with the following lines:

EnableTranscodingConfig = false
LogLevel = 'DEBUG'
ScanSchedule = '@every 12h'
TranscodingCacheSize = '1000MiB'
Address = "<IP Address of Server>"
BaseUrl = "/"

Don’t forget to restart Navidrome again to reload the config file

sudo systemctl restart navidrome

Copy Music Files

Copy music files from local storage to Navidrome (assumes that you are in the root directory for your music) by logging in on SSH to the local storage. Once logged in navigate to the root directory of you music files and type:

scp -rv * pi@<IP Address>:/media/music

Enter password for remote server when requested.

Depending on the size of your music collection this might take a while.  Go and make yourself a drink…

If your local storage is a Synology NAS and you’ve installed the MediaServer or PhotoStation packages you will find that these packages run a service that creates hidden directories named “@eaDir”.  These directories are the equivalent of thumbs.db on Windows where the package stores thumbnail files associated with iTunes support and will be copied across.

If you don’t have or need iTunes support the @eaDir directories can be safely deleted with the command:

find . -type d -name "@eaDir" -print0 | xargs -0 rm -rf

To keep in sync either setup rsync with a cron job or if you are lazy, like me, and run Windows use WinSCP to copy across any new music that you get.

External Access Configuration

Since the whole purpose of setting up a music server is to make it accessible from the Internet there are a few more steps that need to be taken to ensure that Navidrome can be accessed externally and securely.

In no particular order these are:

  • Configure your ISP’s or hosting provider’s DNS server for your domain to “point” at the Navidrome server public IP address;
  • Configure the necessary firewall rules and/or port forwarding rules to ensure that the server is reachable from its public IP address to whatever the internal IP address of the server is;
  • Configure a reverse proxy server to allow secure access via HTTPS;
  • Get or update your existing TLS Certificate.

As I already host a number of services making the necessary changes was fairly straightforward.  The DNS change involved nothing more than adding a CNAME record to point at my existing A record. The DNS entry is below:

Host/name: <Your_hostname>
Type: CNAME
TTL: 1 hour
Value: <Your_domain_name.>

The firewall rules and forwarding were already in place and I run Apache 2.4 with Virtual Hosts, each virtual host being managed through SNI.  As the Navidrome server is being run on a separate host on the main domain I needed to increase the scope of the TLS certificate from LetsEncrypt as follows:

sudo certbot --expand -d foo.example.com -d bar.example.com -d baz.example.com

And follow the terminal prompts to generate and install the expanded certificate.

Firewall Configuration

Policies (rules) required

Create custom policy (rule) for Navidrome

ProtocolSource PortsDestination Port
TCP0 – 655354533
UDP0 – 655354533

Trust >> DMZ2

ServicePurpose
SSHPermit SSH or SCP to allow copying of current and new music to Navidrome
NavidromeAllow access from local network to Navidrome on port 4533 (HTTP)
ParameterNetwork/hostComment
Source IP:192.168.x.x/24Trusted network
Destination IP:192.168.x.x/32Navidrome host
Routing:NAT noneNAT not required

DMZ >> DMZ2

ServicePurpose
NavidromeAllow access from local network to Navidrome on port 4533 (HTTP) via reverse proxy
ParameterNetwork/hostComment
Source IP192.168.x.x/24DMZ network
Destination IP192.168.x.x/32Navidrome host
RoutingNAT noneNAT not required

DMZ2 >> Untrust

Permit access to external hosts

ServicePurpose
DNSName Resolution
HTTPUpdate Servers
HTTPSUpdate Servers
FTPUpdate Servers
NTPTime Service
ParameterNetwork/hostComment
Source IP192.168.x.x/32Navidrome host
Destination IPAny IPv4Connect to all IPv4 addresses
Routing: NAT(Source Translation)NAT required

DMZ2 >> DMZ

Permit traffic to pass back through reverse proxy

ServicePurpose
NavidromeAllow access from local network to Navidrome on port 4533 (HTTP) via reverse proxy
ParameterNetwork/hostComment
Source IP192.168.x.x/32Navidrome host
Destination IP192.168.x.x/24DMZ network
RoutingNAT noneNAT not required

Port Forwarding

Source IPDestination IPPort
Public IP address192.168.x.x80
Public IP192.168.x.x443

Note: Port 80 should be permanently redirected to port 443 for HTTPS on the reverse proxy

Reverse Proxy Configuration

The notes below are linked and taken directly from the Navidrome website.

Network configuration

“Even though Navidrome comes with an embedded, full-featured HTTP server, you should seriously consider running it behind a reverse proxy (Ex: Caddy, Nginx, Traefik, Apache) for added security, including setting up SSL. There are tons of good resources on the web on how to properly setup a reverse proxy.

When using Navidrome in such configuration, you may want to prevent Navidrome from listening to all IPs configured in your computer, and only listen to localhost. This can be achieved by setting the Address flag to localhost”

Reverse proxy authentication

“When reverse proxy authentication is used, the verification is done by another system. By checking a specific HTTP header, Navidrome assumes you are already authenticated. This header can be configured via ReverseProxyUserHeader configuration option. By default, the Remote-User header is used.

By default, Navidrome denies every attempt. Authentication proxy needs to be whitelisted in CIDR format, using ReverseProxyWhitelist. Both IPv4 and IPv6 are supported.”

Edit the navidrome.toml file and add:

ReverseProxyUserHeader = "Remote-User"
ReverseProxyWhitelist = "<IP Address_of_proxy_here>"

Edit the httpd.conf file in /etc/httpd/conf and add the following:

<VirtualHost *:80>
     ServerName <Your_FQDN_here>
     Redirect permanent / https://<Your_FQDN_here>
</VirtualHost>

Edit the httpd-le-ssl.conf file and add the following:

<VirtualHost *:443>
     ServerName <Your_FQDN_here>
     ProxyPass / http://192.168.x.x:4533/
     ProxyPassReverse / http://192.168.x.x:4533/
     DocumentRoot "/var/www/<domain_name>/<host_name>/html/"
     DirectoryIndex index.htm
<Directory /var/www/<domain_name>/<host_name>
     Options +Indexes +FollowSymLinks +MultiViews
     AllowOverride All
     Require all granted
</Directory>
# Other directives here
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;"
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/<domain.tld>/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/<domain.tld>/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/<domain.tld>/chain.pem
</VirtualHost>

Restart httpd server:

systemctl restart httpd

That’s it. You should now be able to connect to Navidrome at https://<FQDN> via your web browser and if all is working you will be presented with the login page.

Final Thoughts

Navidrome is an awesome piece of software. In the few minutes between starting it up and creating the admin user it had already indexed the first 300 albums that I had copied to the server.

It’s quick and intuitive to use from the built in web server.  I have also tried connecting using SubStreamer, Amperfy and HalpoPlayer. All work and of the three HalpoPlayer is a clear favourite for me with its clear, minimalist, no nonsense interface. SubStreamer would have been my preferred player as it’s much richer in its features. However, while appearing to have been around much longer it has not had an update for over a year and is riddled with bugs and stability issues that made it unusable for me.

The single biggest problem for me was configuring the reverse proxy. The learning experience here is: pay attention to the syntax and don’t omit trailing “/“ on the server URL.

However, I think that the Navidrome documentation on reverse proxy configuration is a bit “sketchy” around authentication and IP whitelisting. There are essentially two configuration settings:

“When reverse proxy authentication is used, the verification is done by another system. By checking a specific HTTP header, Navidrome assumes you are already authenticated. This header can be configured via ReverseProxyUserHeader configuration option. By default, the Remote-User header is used.

By default, Navidrome denies every attempt. Authentication proxy needs to be whitelisted in CIDR format, using ReverseProxyWhitelist. Both IPv4 and IPv6 are supported.”

In respect of “ReverseProxyUserHeader” it’s not clear what the purpose of setting it to “Remote-User” is or whether it can take on any other values. It’s also not clear what specific HTTP header is being referred to here.