ENOSUCHBLOG

Programming, philosophy, pedaling.


Setting up Navidrome with Nginx as a reverse proxy

Feb 2, 2022     Tags: howto, music, workflow    

This post is at least a year old.

This is a quick how-to post, since I struggled to follow some of the recommendations of individual users on the Navidrome subreddit. Hopefully someone will find it useful.

TL;DR: Jump to this section.


Background

Many years ago, I ran a Subsonic instance for myself and my friends. Back then, Subsonic was pretty incredible: it “just worked,” assuming you had a few basic runtime dependencies (namely the Java Runtime and ffmpeg).

Even back then, however, there were ominous signs on the horizon: Subsonic’s author made new releases proprietary, and community maintained forks churned through names more than they did features1: Libresonic, Airsonic, Airsonic-Advanced?

At the same time, the Foosonic web UI began to show its age: the original Flash-based player had to be replaced with an HTML5 player, and the <frame> based interface performed increasingly poorly on modern browsers.

After a hard drive disaster on the machine that my instance ran on, I gave up on Subsonic and its forks entirely and did my own thing for a while. That also became a hassle, and I became a (recalcitrant) Spotify customer.

Fast forward to 2022, and I’m done with Spotify2: no matter what I do, it only recommends me the same half dozen artists. I miss curating my own music without The Algorithm (un-)helpfully placing its thumb on the scale.

A friend recommends Navidrome to me. He describes it as a Subsonic server, which makes me think that’s another (maintained) Subsonic fork. But no: it’s a completely separate codebase, one that supports the REST API “standard” that Subsonic and its offspring use. That’s the best of both worlds: a new codebase (including a new frontend) with none of Subsonic’s baggage and compatibility with the large ecosystem of Subsonic clients.

Installing it is (blessedly) simple, with just a single Dockerfile and two directories (one for server state, and one for my music):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: "3"
services:
  navidrome:
    image: deluan/navidrome:latest
    user: 1000:1000
    ports:
      - "4533:4533"
    restart: unless-stopped
    environment:
      # This is part of my reverse proxy setup; see below.
      ND_BASEURL: /navidrome
      # Re-scan the music library every 15 minutes.
      ND_SCANSCHEDULE: "@every 15m"
      ND_LOGLEVEL: info
      ND_SESSIONTIMEOUT: 24h
    volumes:
      - "/home/william/navidrome/data:/data"
      - "/mnt/media/music:/music:ro"

And running:

1
docker-compose up

That’s it. Five seconds later, Navidrome was already indexing my music, listening on my server’s local host, and waiting for me to configure a new user (and admin) via the web UI. The end result is very nice:

Navidrome's default (album) view

Reverse proxying Navidrome with Nginx

This is perfectly sufficient for local use, but I want to replicate the setup I originally had with Subsonic: a reverse-proxied service poking out from one of my domains, complete with HTTPS.

I prefer URL routes to subdomains (because I’m too lazy to update my Let’s Encrypt configuration), so this is my ideal setup:

1
2
https://domain.net/navidrome -> reverse proxy to local-address:4533
http://domain.net/navidrome -> permanent redirect to https://$domain/$path

On the Navidrome side, I only needed a single configuration change: I needed to set ND_BASEURL: /navidrome in my docker-compose.yml, telling Navidrome to base all URLs on /navidrome instead of /. See the full YAML above for the entire context.

On the Nginx side, all I needed was a new location block under my HTTPS server spec, matching all patterns under /navidrome and passing them onto the underlying server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    return 301 https://$server_name$request_uri;
    # replace domain.net with your domain
    server_name www.domain.net domain.net;
}

server {
  listen 443 ssl default_server;
  listen [::]:443 ssl default_server;
  # replace domain.net with your domain
  server_name www.domain.net domain.net;

  # the rest of your TLS configuration goes here

  location ^~ /navidrome {
      # replace local-address with your navidrome server's IP
      proxy_pass http://local-address:4533/navidrome;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header X-Forwarded-Protocol $scheme;
      proxy_set_header X-Forwarded-Host $http_host;
      proxy_buffering off;
  }
}

(I’ve included the HTTP server block as well, just to show the HTTP to HTTPS redirect.)

…and that’s it! Navigating to https://domain.net/navidrome worked perfectly, including logins. All of the Subsonic-style clients that I tried also worked, using https://domain.net/navidrome in the “server” field3.


  1. To be fair, it’s not easy to maintain someone else’s project, much less add new features to it. 

  2. Third time’s the charm. 

  3. Your mileage may vary. I remember (circa 2015) some of the Subsonic clients being HTTP only or requiring a bare TLD for the server, but hopefully things have improved since then. 


Discussions: Reddit Twitter