20240725 - self-hosting a music streaming server on a raspberry pi with navidrome


,NOTICE'

This post draws heavily from my friend Jack's fantastic guide on hosting a website using nginx and from his encouragement and help debugging issues I had with my setup. His guide goes a bit more in detail about the web server stuff, and gives alternatives for a lot of the tools I use in my post.

,SECOND NOTICE'

Do your own research on this one! I try to explain some background on how networking and servers work, but I'm pretty new to all this stuff so theres a good chance I miss something or tell a half-truth along the way.


(\__/)
(='.'=)
(")_(")

Thoughts

I am currently writing this guide while listening to the wonderful forgotten 2008 album Evolove by JAMETATONE and the EVOLOVE Vortex from a bench in the back yard of Misfit Kava Bar in Bushwick, NY. At the time of writing this guide, only one other person has rated this album on rateyourmusic.com. It was the person who I downloaded it from on soulseek and they rated it in 2010. They used to make fantastic music under the name tinyfolk and rated this album 5 stars so I trusted their judgement on it and patiently spent over a year sporadically seaching for it on soulseek until finally they logged on and I was able to download it. It is one of the weirdest albums I've ever heard (long prog-y hip hop influenced indietronica songs) and I love it so much. For me, finding obscure and lost indiepop music from the early 2000's is one of the greatest joys of life. Until winter of 2024, I would download this music on my laptop and listen to it at home, work or school. When away from my laptop I would use my phone to listen to music on spotify, or illegally uploaded albums on youtube, and most of my downloaded music would inevitably find its way onto the backburner because it was so much easier for me to open up my spotify app, type in "tiger trap" and listen to the same album I'd listened to on average once a week since I was 13 years old. Exiting the world of spotify, for me, means I can spend more time connecting with all of the deeply personal music that I spend so much of my time hunting down.

If you're here, I probably don't need to tell you how horrible streaming services are, but I will list a few points:

This blog will guide you through the process of setting up a Navidrome server to stream your music collection. Navidrome is a self-hosted streaming server which provides an interface similar to modern streaming services like spotify or apple music. It supports making playlists, saving your favorite albums and once you connect it to a web server you will be able to access it over the internet from anywhere on your phone or computer. Once you get it set up you will not miss spotify one bit!

My main goal in writing this blog post is to document the specific process I used to set up my streaming server as a person with very little prior knowledge of linux, servers, networking, etc. I aim to share what I learned and hopefully help other people escape the horrible world of paid streaming services. By the end of this guide you will have a functional music server as well as a general purpose self-hosted website you can use for anything you want!


Setting up your raspberry pi

The first thing you need is a computer to host our server on. This guide assumes you are using a Raspberry Pi. I am using a Raspberry Pi 3b, but this guide should apply to most other models with no changes. You could also likely run this on any computer running linux with minimal changes, or a computer running Windows or Mac OS with more changes. If you are unfamiliar with Raspberry Pi, they are essentially single board computers that typically run some form of linux. They are great because they are relatively cheap, have easily accessible GPIO pins (which are not actually used in this post) and have a large online ecosystem of support for use in DIY projects.

If you have not set up an operating system on your Raspberry Pi yet, their website has a great guide on doing so! I recommend installing the default Raspberry Pi OS which is a port of Debian. My Pi has the 32-bit OS installed, but a 64-bit version came out recently, and that shouldn't change any of the steps in this guide so I would install it! To follow that guide, you will need an SD card, a keyboard, a mouse, an HDMI monitor and an HDMI cable (Pi 4 and 5 only have micro HDMI ports so if you have one of those you will probably need an adaptor cable). This step shouldn't be too dissimilar to setting up any new computer. There seem to be ways to skip the keyboard, mouse and monitor and move straight to the next step where we access the Pi through SSH but I haven't tested them.

Once the operating system is installed, there are a few things you need to do.

  1. Connect the PI to the internet.
  2. NOTE: Later on in this guide, you will need admin access to your router. If you have a roommate or partner who set up your internet you will need to get the password from them. If they forgot it, the default login, or reset instructions might printed on the label on the back of your router. If you live in an apartment building and do not manage your own internet, you may be out of luck. Jack hints in his guide that there are ways to get by without admin access but they are beyond my knowledge level.

    If at all possible you should connect your Pi to your router directly with an ethernet cable, my Pi lives right next to my router and I have never had any reason to move it. Wifi would also work but streaming your music would be slower. You may need to disable Wifi on your Pi, but if an ethernet cable is plugged in, it should default to the wired connection. Load any webpage in your Pi's internet browser to verify that it is connected to the internet.

  3. Enable SSH on the pi
  4. SSH stands for Secure Shell Protocol. Setting it up will allow you to remotely control your Pi from your regular computer so long as it is connected to the same network as the Pi. For the rest of the guide you will be interacting with the Pi purely through this remote command line interface, rather than a desktop, which will allow you to ditch the keyboard, mouse and monitor.

    Raspberry Pi, again, has a guide for setting up SSH which I recommend you follow, but I will leave an important point here.

    • When you find the private IP address of your Pi, write it down somewhere, we will be needing it often throughout this guide. An IP address is a unique identifier assigned to every device connected to a network. In this case, what we are finding is the IP address assigned to your Pi by your router, which will allow it to be identified by any other devices also connected to the same network, but NOT to outside devices on the world wide web. This local IP address will likely look like 192.168.1.SOMENUMBER. It is entirely possible that your Pi has the same private IP address as mine but since they are connected to different routers it is like they are in separate universes so they don't conflict with each other.

    Once you have enabled SSH on the Pi, you should be able to connect to it using your main computer, which we will be using for the rest of this guide. Open up your favorite command-line interface (I use the default "Terminal" app on my macbook) and run the following command:

    • Replace "admin" with the name of the admin user you created when setting up the Pi. It probably defaulted to either "admin" or "pi" if you didn't change it to anything else.
    • Replace the IP address after the "@" with your Pi's IP address found in the last step
    ssh admin@192.168.1.148

    The Pi will then prompt you to enter your password you created when setting up the Pi. Type it in (typed text probably won't appear in the terminal when entering a password but it is still reading your keypresses) and press enter and you should be in! Now any commands you type will be run on the Pi rather than your local machine! If you are unfamiliar with using command-line interfaces that is okay, I will do my best to explain any commands we use. Your Pi's default command line interface is probably bash, which is a UNIX-like shell and has the same commands used on Mac OS and Linux for the most part, so any documentation you find for those systems will likely apply to your Pi as well.

  5. Set up a static IP address for your Pi.
  6. A tricky thing about IP addresses is that they can change! It doesn't happen often, but our router could decide one day to assign a different address to our Pi which could cause problems for us. Luckily we are able to assign a static IP address to our Pi, and while it's not strictly necessary, it will make our lives easier in the long run. There are plenty of guides online covering this, but I followed this oneand it worked great!


Get a domain name and point it to your Pi

The next two steps of this post will guide you through self-hosting a website on your Pi. If you want your music server to be able to access your navidrome server outside of your home network you need a website to attach it to!

Domain names are essentially a human friendly way of pointing to an IP address. Rather than typing in a bunch of numbers when accessing a website, we type in the domain name, and a DNS (Domain Name Server) points us to the IP address of the server running the site we want to access, which then gives our browser whatever content that website serves. You are going to be setting up this process for your website. Remember, at the end of this guide you will have both a functional music server and a self-hosted website, so if you have any interest in using your pi for hosting a personal website, project website or something else, you will want to choose whatever domain name you would use for that purpose as well!

  1. Register a domain name
  2. There are many sites that will let you register domain names and they all work similarly, my server is hosted using a name I registered using GoDaddy (which I don't recommend because they recently restricted their API access. namecheap or cloudflare are probably good!). Any domain name will cost money (mine is $11/year) and be aware that different domain names and extensions cost different amounts. Many domain name registration services also offer hosting services (and will probably try to get you to use them) but you don't need that since you will be hosting the website yourself so be sure not to sign up for that!

  3. Enable Port forwarding from your router to your Pi
  4. This is how you point http and https requests coming into your router to your Pi.
    • Find your Router's private IP address
    • This process differs depending on your operating system, so if you have trouble, google it. You should be able to find your router's IP address in the network settings on your computer. On Mac OS, you go to system settings, select Network, select Wi-Fi and press "details" next to the network you are connected to. There should be a line labelled Router with an IP Address. Mine is 192.168.1.1, yours will likely look similar.
    • Log into your router
    • Type your Router's private IP address into your web browser and press enter. Every router/ISP(Internet Service Provider) has a different interface but some sort of page should appear prompting you to log in. The username and password were set by whoever set up your internet. If you can't find this info, the default is often printed on the label on your router (it may be admin:password) and there is always a method to reset it, which is specific to your router. You can likely find this method printed on the router itself or in the router's manual.
    • Enable port forwarding
    • After logging in, look for a section called "Port Forwarding", it may be under an Advanced Settings menu. Add two new Port Forwarding rules to send ports 80 (the default port for sending http) and 443 (the default port for sending https) to your Pi's IP address. This interface will look different depending on your router/ISP but one of these rules from my router is shown below.

  5. Pointing your domain name to your router
  6. Now that the router is pointing to the Pi, the next link in the chain is pointing our domain name to the router. Similar to how the router might assign the Pi a different IP address, our ISP may assign a different IP address to our router. Unfortunately we are not able to assign a static IP address to the router so instead we will configure the domain name to respond to dynamic IP addresses and run a script on the Pi to periodically check the router's IP address and send that to our domain name to keep its records up to date (ie. pointing at our router). Your site will still work if you set this IP address manually, it just won't update itself.

    • Creating "A" records in your DNS records
    • DNS records tell a DNS (Domain Name Server) information about a domain, including what IP address is associated with that domain. We want to tell the DNS to associate the domain name you registered with your router's IP address. There are many DNS records that give different information about a domain name, but the one we care about is the "A" record which provides the IPv4 address you want to associate with your domain.

      First we need to go to our domain name provider's website (in my case GoDaddy) and find the page for managing your domain's DNS. On this page there should be some DNS records that were created for us. If they don't already exist, we want to create two "A" records. One will have the name (or host) "@" and the other will have the name "www". Enter your router's public IP address into the data (or value) field for both of them. The "www" record will allow us to access our site through the url "www.yourdomain.com" and the "@" record will allow you to access it through "yourdomain.com". It's nice to have both! TTL specifies how often DNS servers will update their records. Mine are set to update every 10 minutes. My records look like this:

    • Running a dynamic DNS script on your Pi
    • As mentioned above, there is a chance that our ISP could assign a different IP address to our router and we want our Pi to update these DNS records automatically so we don't have to manually set them again.

      There are a few ways to do this. Jack's guide recommends using ddclient which seems like a convenient way to handle it. I recommend setting it up by taking a look at that section of Jack's guide. Your router may even provide options for configuring dynamic DNS which would allow you to handle dynamic DNS without even using the Pi! I realized after getting my script set up that my router from Optimum does provide seem to provide dynamic DNS management.

      I was unable to get ddclient working with GoDaddy, but it should work for most cases. Previously, I had set up a bash script as a cron job to periodically update my DNS records but GoDaddy recently restricted API access to only accounts that own more than 10 domains, which effectively blocked my script. I have yet to find a workaround and will likely by migrating my domain away from GoDaddy soon! Don't use them!


Setting up nginx

nginx is a free and open source web server that is currently used to host approximately a third of all websites connected to the world wide web. Web servers are able to accept HTTP/HTTPS requests and return data to the user, often in the form of html files which your browser will display. nginx can also function as a reverse proxy, forwarding these requests to a specific server on our host computer. This will come in handy later in this post!

,NOTICE' In the following code, replace "yourdomain" with the domain you registered in the previous step. For example, if you registered "mycoolwebsite.com", type "mycoolwebsite" (without the .com) wherever you see "yourdomain". Anywhere you do see a ".com" you should change to whatever domain extension you registered, such as ".party", ".gay", or ".motorcycles"

  1. Install nginx

    You will often see the word sudo placed before unix commands. It is essentially a keyword that runs the command as the computer's superuser rather than your current user, which will give you permission to do just about anything, such as install packages to the computer or access restricted files. apt-get is a command-line tool that allows you to install and update software. First we update the apt-get tool and then we use it to install nginx.

    sudo apt-get update
    sudo apt-get install nginx
  2. Configure your nginx server

    Now we need to create a configuration file for our nginx server. nano is a command-line native text editor. This command will create an empty text file called "yourdomain" and open it in nano editor so we can edit it.

    sudo nano /etc/nginx/sites-available/yourdomain

    In nano editor, enter the following code

    
    server {
        listen 80;
        listen [::]:80;
                    
        server_name yourdomain.com, www.yourdomain.com;
                    
        root /var/www/yourdomain;
        index index.html;
                    
        location / {
            try_files $uri $uri/ =404;
        }
    }
    server {
        listen       80;
        server_name  www.yourdomain.com;
        return       301 http://yourdomain.com$request_uri;
    }

    Once you have typed the above lines, you can save the file and exit nano by typing CTRL + X followed by Y and then the ENTER key

  3. Enable this web server by adding it to nginx's enable list
    sudo ln -s /etc/nginx/sites-available/yourdomain /etc/nginx/sites-enabled/yourdomain
  4. Remove nginx's default site, we are going to create our own
    sudo rm -rf /etc/nginx/sites-enabled/default
  5. Reload nginx to update it with the settings you changed
    sudo nginx -s reload
  6. Create a directory to host your websites files
    sudo mkdir /var/www/yourdomain
  7. Create a basic html file
    sudo nano /var/www/yourdomain/index.html

    Enter the following then save and exit nano. This will create a basic webpage that displays "Hello world!"

    
    <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta http-equiv="X-UA-Compatible" content="ie=edge">
            <title>hiiii :3</title>
        </head>
        <body>
            <p>Hello world!</p>
        </body>
    </html>
    

Enabling https

Now that you have your website working, you need to set up https support. https is standard and is really important for privacy/security reasons that I don't entirely understand, but the good news is that this is probably the easiest step in this whole guide.

We will be using EEF's certbot which will automatically configure our nginx server for us and create TLS certs. The following is a paraphrasing of the important parts of their setup guide.

  1. Install snapd
    sudo apt update
    sudo apt install snapd
    then reboot
    sudo reboot
    then install core from snapd
    sudo snap install core
  2. Install certbot
    sudo snap install --classic certbot
  3. Prepare certbot
    sudo ln -s /snap/bin/certbot /usr/bin/certbot
  4. Run certbot: this will get your certs and modify your nginx config file
    sudo certbot certonly --nginx
  5. Test automatic renewal
    sudo certbot renew --dry-run
  6. Confirm that certbot worked Visit https://www.yourwebsite.com in your browser and look for the lock icon in the URL bar.


Setting up a local navidrome server

Navidrome is a piece of software that lets you stream your personal music collection. There are several ways to set it up, but we are going to do it using Docker. Docker is kind of like a tiny virtual machine. It sets up a container inside of your operating system for the purpose of running a specific application, and helps to nicely package all dependencies, making it easy for us to set up the application!

In this step we will set up navidrome to run on our local network and in the next step we will connect it to the external internet.

First you need to create a docker compose file. This is the configuration file that will tell your computer how to set up your navidrome server.

  1. Create a directory for our navidrome setup
    cd /opt/stacks
    sudo mkdir navidrome
    cd navidrome
    Create a directory for navidrome data and make sure your current user has access to this directory
    sudo mkdir data
    sudo chown $USER: /opt/stacks/navidrome/data
  2. Create a docker compose file.
    sudo nano compose.yaml
    version: "3"
    services:
        navidrome:
            image: deluan/navidrome:latest
            user: 1000:1000
            ports:
              - "4533:4533"
            restart: unless-stopped
            environment:
                ND_SCANSCHEDULE: "@every 5m"
                ND_LOGLEVEL: info
                ND_SESSIONTIMEOUT: 24h
                ND_BASEURL: "/navidrome"
                volumes:
                  - "./data:/data"
                 - "<PATHTOMUSIC>:/music:ro"

    Important parts of this code:

    • image: deluan/navidrome
    • This points to docker hub which hosts docker images created by the navidrome developers. It will automatically fetch the image created for your processor (my raspberry pi has an arm v7 processor, yours could be different if you have a different pi version!)
    • ports: - "4533:4533"
    • This specifies that our server will be hosted on port 4533 of our local network. A port, in the world of networking, is the next step of specificity after the IP address. When a request comes to your IP address, it probably wants to connect to a specific process within the device at this IP address. These processes are specified by their port number. Some port numbers are reserved for certain protocols (http is on port 80, ssh is on port 22). In our case, Navidrome will be running on port 4533, so if a user wants to access navidrome they will need both the IP address of the Pi and the port number of our server (don't worry, we will simplify this into a domain name in the next step!)
    • environment:
    • There are a ton of options for customizing your navidrome installation. These are not necessary for the server to work but feel free to look through and add any that you want! I have my ND_SCANSCHEDULE (how often your navidrome server will scan for new files) set to five minutes because I upload new music to my server very frequently and I'm too impatient to wait for it to show up after I upload it but if you're not uploading very often or have patience you should probably set this to a longer interval.
    • volumes
    • replace <PATHTOMUSIC> with the path to where your music is stored (mine is on an external SSD at /media/admin/mason_ssd/Music).

  3. Start your navidrome server

    docker compose up -d

    This will start your navidrome server! The -d command option will run the server in detached mode, meaning that it won't be tied to this specific shell instance. You can run it without the -d option if you want to see a detailed real-time log from the server, but the server will shut down if you close your shell.

  4. Test your local navidrome server!

    The server should now be running and accessible on your local network. You can access it through your web browser by navigating to port 4533 of your raspberry pi's local IP address. For me that looks like this: http://192.168.1.148:4533 but your pi's IP address will likely be different.


Connecting navidrome to your nginx server

Your server should now be working great for streaming music at home, but wouldn't it be great to be able to access it from anywhere? That is where the nginx server we set up earlier comes in! Our goal is to create a page on our website which will be served by our navidrome server rather than a static html file like our homepage was. We do this by setting up a reverse proxy. When a user requests the relevant page on our website: www.yourwebsite.com/navidrome" for example, this reverse proxy will handle redirecting them to the navidrome server which we have set up on port 4533, effectively creating a bridge between this local server and the whole of the internet.

To create this bridge, we need to do the following:

You should now be able to access your server from any internet connection by navigating to your url:

www.yourwebsite.com/navidrome


Using your server

Now that you've set up your server comes the fun part, loading up music and listening to it!


Addendums

convenient commands to have on hand

additional notes

Using an external SSD as your Navidrome music folder like I do can have its issues. Whenever I restart my Pi, it will mounted my drive with an extra number at the end (my drive is named mason_ssd, and when i restart the pi it will mount as mason_ssd1 or sometimes another number). This breaks the path to the music folder set in navidrome configuration. For now I just manually edit the configuration file whenever I need to restart my Pi (which isn't very often). The correct solution would be to not rely on the Pi's automatic disk mounting utility, and set up a script to manually mount it, but I haven't gone down that rabbit hole just yet!