Configure Self-Hosted Music Library

Configure Self-Hosted Music Library

I stopped paying for Spotify last year. Not because I don't love music—I do—but because I realized I could own my collection outright and stream it everywhere without monthly fees. A self-hosted music server changed how I listen: lossless audio, unlimited access, no throttling, no recommendations I didn't ask for. This guide walks you through building one.

Why Self-Host Music?

Music streaming services lock you into subscription tiers. You rent access; you don't own anything. A self-hosted music server flips that entirely. You upload your FLAC, MP3, or OGG files once and stream them from anywhere—home network, mobile devices, even remote locations with a reverse proxy. The setup is cheaper than one year of Spotify Premium, and it works forever.

I prefer Navidrome for its lightweight footprint and dead-simple interface. It runs on Docker, scales to 50,000+ tracks, and consumes maybe 100MB of RAM idle. The web client is responsive, native mobile apps work beautifully, and you can stream transcoded audio on bandwidth-constrained connections.

Choose Your Application

Navidrome is my default recommendation. It's fast, modern, and maintains an active community. If you're running a modest homelab on a Raspberry Pi or low-power NAS, Navidrome won't strain hardware.

Subsonic is the veteran option—battle-tested, widely supported by client applications, but requires a Java runtime and isn't as lightweight. The original Subsonic is paid; clones like Airsonic exist if you want open-source.

Funkwhale is federation-focused and feature-rich but heavier. Use it if you want social music sharing and are okay with more overhead.

I'm using Navidrome because it starts faster on cold boot, updates cleanly, and the community fixes bugs within days.

Set Up Navidrome with Docker Compose

Assumption: you have Docker and Docker Compose installed. If not, install Docker first.

Create a directory for your music stack:

mkdir -p ~/music-server/music ~/music-server/data
cd ~/music-server

Create this docker-compose.yml:

version: '3.8'

services:
  navidrome:
    image: deluan/navidrome:latest
    container_name: navidrome
    ports:
      - "4533:4533"
    environment:
      ND_LOGLEVEL: info
      ND_BASEURL: "http://localhost:4533"
      ND_ENABLESTARRATING: "true"
      ND_ENABLESHARING: "false"
      ND_SESSIONTIMEOUT: "24h"
      ND_PLAYLISTSMAXSIZE: "500"
      ND_TRANSCODE_MP3: "opus,mp3"
      ND_TRANSCODE_DEFAULT: "mp3"
    volumes:
      - ./data:/data
      - ./music:/music:ro
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:4533/rest/ping"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

Start the container:

docker-compose up -d

Check logs to confirm it's running:

docker-compose logs -f navidrome

Once you see a line like Listening at 0.0.0.0:4533, open http://localhost:4533 in your browser. The default user is admin with password password—change this immediately.

Watch out: Navidrome doesn't have built-in authentication for API endpoints. If you expose it to the internet without a reverse proxy (Caddy, Nginx, or Traefik), anyone who knows your IP can access your library. Always use a reverse proxy or Tailscale.

Organize and Populate Your Music Library

Navidrome scans the ./music folder automatically every hour. To speed up the first scan, place your music files in subdirectories:

music/
├── Artist Name/
│   └── Album Name/
│       ├── 01 - Song.flac
│       ├── 02 - Song.flac
│       └── cover.jpg
└── Another Artist/
    └── Another Album/
        ├── 01 - Song.mp3
        └── 02 - Song.mp3

Navidrome reads ID3 tags, so proper metadata matters. I use MusicBrainz Picard to tag files automatically—it's free and massively faster than manual tagging.

To force an immediate rescan instead of waiting an hour:

docker-compose exec navidrome curl -s http://localhost:4533/rest/startScan.view

Expose Navidrome Securely with Caddy

To access your music library outside your home network, use Caddy as a reverse proxy. It handles HTTPS automatically via Let's Encrypt.

First, buy a domain. I use a cheap domain registrar (Namecheap, Porkbun) and point it to my home IP or use Cloudflare CNAME. For reliability, consider a small RackNerd VPS to run a reverse proxy in the cloud instead.

On your home server, install Caddy:

sudo apt update
sudo apt install -y caddy

Edit /etc/caddy/Caddyfile:

music.yourdomainhere.com {
    encode gzip
    reverse_proxy localhost:4533 {
        header_uri -Authorization
    }
    handle_path /admin/* {
        reverse_proxy localhost:4533
    }
}

Restart Caddy:

sudo systemctl restart caddy

Caddy automatically obtains an SSL certificate and renews it. You can now access https://music.yourdomainhere.com from anywhere.

Tip: Use Tailscale instead of exposing Navidrome to the public internet. Install Tailscale on your server and client devices, then access your music over the encrypted Tailscale network. Zero port forwarding, zero firewall headaches.

Add Authentication

Navidrome's built-in user management is basic but sufficient. Log in as admin, go to Settings → Users, and create accounts for family members. Each gets their own play history and ratings without seeing others' data.

For stronger security, run Authelia in front of Navidrome. This adds TOTP (two-factor authentication) and SSO capabilities, but adds complexity. I skip it for home use—network isolation via Tailscale is enough.

Mobile Streaming

Download Navidrome (official iOS/Android app), Substreamer, or DSub for Android. They all work beautifully with a self-hosted Navidrome instance.

Point your app at your external URL (e.g., https://music.yourdomainhere.com) and log in with your Navidrome credentials. Streaming starts instantly, with options to cache tracks for offline listening.

Transcoding on Demand

If you store FLAC files but want to save mobile bandwidth, Navidrome transcodes on the fly. The Docker image includes FFmpeg, so transcoding works out of the box. Configure it in the environment variables:

ND_TRANSCODE_MP3: "opus,mp3"
ND_TRANSCODE_DEFAULT: "mp3"

This tells Navidrome to transcode to MP3 by default on bandwidth-constrained connections and opus for newer clients.

Backup Your Library

Your music metadata and play history live in ./data/navidrome.db. Back it up regularly:

docker-compose exec navidrome tar -czf - /data > navidrome-backup-$(date +%Y%m%d).tar.gz

Store backups on a separate disk or cloud storage. Your FLAC files are replaceable (you own them), but play history and ratings aren't.

Performance Tuning

For large libraries (10,000+ tracks), adjust Docker memory limits:

services:
  navidrome:
    # ... other config ...
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

Enable database indexing by setting ND_DBPATH: /data/navidrome.db to an SSD if possible. SQLite benefits massively from fast I/O.

Conclusion

You now have a self-hosted music library that rivals Spotify for usability and beats it on flexibility. Your music is yours—no DRM, no licensing agreements, no account termination risk.

Next steps: load your music, invite family to join, and consider setting up automatic backups with Restic or Duplicacy. If you want to go deeper, add Jellyfin for video or Vaultwarden for credential management in the same Docker Compose file.

```