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.fm, ListenBrainz 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
Hardware | Qty | Unit Price | Price | Source | Part No. |
---|---|---|---|---|---|
Raspberry Pi4 (4 Gb) | 1 | £55.00 | £55.00 | The Pi Hut | SC0194 |
Pi4 Case | 1 | £14.00 | £14.00 | The Pi Hut | 102476 |
Pi4 PSU | 1 | £9.00 | £9.00 | The Pi Hit | SC0443 |
microSD (32Gb) | 1 | £8.00 | £8.00 | The Pi Hut | SD-32GB |
External SSD (240 Gb) | 1 | £36.50 | £36.50 | The Pi Hut | 103410 |
SSD Cable | 1 | £6.00 | £6.00 | The Pi Hut | 103222 |
Total | £128.50 |
Prices correct at time of writing in June 2024
Software
Software | Purpose |
---|---|
Navidrome | Streaming app |
Raspberry PiOS | Operating System |
Apache 2.4 | Configure for reverse proxy with https |
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:
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
Protocol | Source Ports | Destination Port |
TCP | 0 – 65535 | 4533 |
UDP | 0 – 65535 | 4533 |
Trust >> DMZ2
Service | Purpose |
SSH | Permit SSH or SCP to allow copying of current and new music to Navidrome |
Navidrome | Allow access from local network to Navidrome on port 4533 (HTTP) |
Parameter | Network/host | Comment |
Source IP: | 192.168.x.x/24 | Trusted network |
Destination IP: | 192.168.x.x/32 | Navidrome host |
Routing: | NAT none | NAT not required |
DMZ >> DMZ2
Service | Purpose |
Navidrome | Allow access from local network to Navidrome on port 4533 (HTTP) via reverse proxy |
Parameter | Network/host | Comment |
Source IP | 192.168.x.x/24 | DMZ network |
Destination IP | 192.168.x.x/32 | Navidrome host |
Routing | NAT none | NAT not required |
DMZ2 >> Untrust
Permit access to external hosts
Service | Purpose |
DNS | Name Resolution |
HTTP | Update Servers |
HTTPS | Update Servers |
FTP | Update Servers |
NTP | Time Service |
Parameter | Network/host | Comment |
Source IP | 192.168.x.x/32 | Navidrome host |
Destination IP | Any IPv4 | Connect to all IPv4 addresses |
Routing: NAT | (Source Translation) | NAT required |
DMZ2 >> DMZ
Permit traffic to pass back through reverse proxy
Service | Purpose |
Navidrome | Allow access from local network to Navidrome on port 4533 (HTTP) via reverse proxy |
Parameter | Network/host | Comment |
Source IP | 192.168.x.x/32 | Navidrome host |
Destination IP | 192.168.x.x/24 | DMZ network |
Routing | NAT none | NAT not required |
Port Forwarding
Source IP | Destination IP | Port |
Public IP address | 192.168.x.x | 80 |
Public IP | 192.168.x.x | 443 |
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.