GuidesFiles, photos and homeMusic with Navidrome

Stream your music with Navidrome and rootless Podman

Navidrome on Debian 13 streams your own music collection to the browser and to Subsonic apps on your phone, in rootless Podman under a user of its own behind Caddy, with usage reports and online lookups switched off.

Tested on Navidrome 0.64.2 on Debian 13 (trixie) on a Melonslab server Updated October 2, 2026

Recommended server for this guide

VC-S Micro · 2 vCPU · 8 GB Memory · 250 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€7.99/mo

Deploy now
On this page

What you will set up

Navidrome turns a folder of music files into your own streaming service. It reads the tags of your MP3, FLAC, Opus and other files, and plays them in a web player, or in any phone or desktop app that speaks the Subsonic API. When your phone is on a slow connection, Navidrome can convert a large FLAC file to a small Opus or MP3 stream on the fly.

Here it runs from its official image, in Podman under a user of its own called navidrome, behind Caddy from the Podman guide. Your music sits in a folder in that user's home, which the container can read but not change. For films and TV, the Jellyfin guide sets up a media server in the same way.

Navidrome is a community project started and led by Deluan Quintão, published by "The Navidrome Authors"; the project names no company behind it and no country. It is open source under the GNU General Public License version 3 (GPLv3). By default it contacts several online services:

  • Usage reports ("insights"): 30 minutes after start, and then once a day, Navidrome sends insights.navidrome.org a random instance ID, its version, the operating system, CPU count, memory use, file system types, the number of tracks, albums, artists, playlists, shares and active users, and which settings are on. The project says it keeps the reports for 30 days and does not collect names, IP addresses or song titles. The project runs that server itself, at a hosting company in the United States.
  • Artist information: Navidrome looks up artist pictures, biographies and top songs on Deezer, a French streaming service, by artist name. For files tagged with MusicBrainz IDs, it also asks ListenBrainz, run by the MetaBrainz Foundation, for similar artists.
  • Login page pictures: the server downloads the background pictures of its login page from www.navidrome.org, which answers from Cloudflare's network.

Step 3 switches all of these off. Navidrome has no update check, and Last.fm and Gravatar are off unless you set them up.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • The admin account was created over an SSH tunnel before Navidrome could be reached from the internet; from outside, a second attempt was then refused.
  • Public-domain recordings from the Musopen collection and Creative Commons tracks by Kevin MacLeod were uploaded with rsync and SFTP as the navidrome user, appeared in the library within seconds, and played in the web player.
  • The Subsonic API answered ping, getMusicFolders and stream from outside, as phone apps use it, both for the original file and for an Opus stream converted on the fly.
  • Converting a 3:49 FLAC track to Opus took 4.4 seconds and 5.6 seconds of CPU time.
  • Navidrome logged failed logins with the visitor's real IPv4 or IPv6 address, after a few failed attempts in a row it refused more from that address only, and a made-up address in an X-Real-IP header was ignored.
  • With step 3's settings, Navidrome made no DNS lookups at all while starting, showing artist pages and serving its login page.
  • A database backup was restored, and everything came back by itself after a reboot.

Navidrome used about 22 MB of memory, and about 35 MB while converting a track, with a small library. A large library needs more for its database, and the server's file cache holds the music it plays.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running, and secured with ufw;
  • an A record and an AAAA record for music.example.com pointing at your server;
  • your music on your own computer, in folders, ideally one folder per album.

Navidrome is for music you own: your ripped CDs, your purchases from shops that sell files, such as Bandcamp, and freely licensed music. Your files take as much space on the server as on your computer, so check the server's disk before you upload a large collection.

The examples use music.example.com for Navidrome, 203.0.113.10 for your server's IPv4 address, which ip -brief address show eth0 shows, and 198.51.100.7 for your own address. Replace them throughout.

1. Create the user

As root:

apt install -y rsync
useradd -m -s /bin/bash navidrome
loginctl enable-linger navidrome

rsync is for uploading your music in step 6. To upload as navidrome with the same SSH key you use for root, copy root's key list to the new user:

install -d -m 700 -o navidrome -g navidrome /home/navidrome/.ssh
install -m 600 -o navidrome -g navidrome /root/.ssh/authorized_keys /home/navidrome/.ssh/

Then switch to the user:

machinectl shell navidrome@

Everything up to step 5 runs as navidrome.

2. Create the music folder and the secrets

mkdir -p ~/music ~/backup ~/.config/containers/systemd
umask 077
cat > ~/navidrome.env <<EOF
ND_PASSWORDENCRYPTIONKEY=$(openssl rand -hex 32)
ND_EXTAUTH_USERHEADER=X-Auth-$(openssl rand -hex 16)
EOF
umask 022

~/music is where your music goes, and ~/backup where Navidrome puts copies of its database. The file navidrome.env, readable only by navidrome, holds two secrets:

  • Navidrome stores your users' passwords encrypted, because Subsonic apps log in in a way that needs the original password. Without ND_PASSWORDENCRYPTIONKEY, it uses a key that is the same in every copy of Navidrome, so anyone with your database could read the passwords.
  • Step 3 tells Navidrome to trust Caddy for the visitor's address. Navidrome then also logs in anyone whose request carries a user name in a header called Remote-User, which any user or program on the server could send to 127.0.0.1:8106, and which Caddy would pass on from anyone on the internet. On the test server, that header alone opened the admin account, through Caddy and locally. ND_EXTAUTH_USERHEADER replaces the header's name with a random one that only this file knows.

3. Describe the container

Create ~/.config/containers/systemd/navidrome.container, with your server's IPv4 address in place of 203.0.113.10:

[Unit]
Description=Navidrome music server

[Container]
ContainerName=navidrome
Image=docker.io/deluan/navidrome:latest
Volume=navidrome-data:/data
# Your music, read-only: Navidrome can play it but never change it.
Volume=%h/music:/music:ro
Volume=%h/backup:/backup
# Only Caddy, on this server, can reach Navidrome: the port is not open to the internet.
PublishPort=127.0.0.1:8106:4533
EnvironmentFile=%h/navidrome.env
# Caddy connects from the server's own IPv4 address: trust it for the visitor's address.
Environment=ND_EXTAUTH_TRUSTEDSOURCES=203.0.113.10/32
# No usage reports, artist pictures, biographies or login backgrounds from online services.
Environment=ND_ENABLEINSIGHTSCOLLECTOR=false
Environment=ND_ENABLEEXTERNALSERVICES=false
# A full library scan every night, on top of the watcher that sees new files at once.
Environment=ND_SCANNER_SCHEDULE=@daily
# A copy of the database every night at 03:00, keeping the last 7.
Environment=ND_BACKUP_PATH=/backup
Environment="ND_BACKUP_SCHEDULE=0 3 * * *"
Environment=ND_BACKUP_COUNT=7
AutoUpdate=registry

[Service]
Restart=always

[Install]
WantedBy=default.target

What the settings do:

  • ND_EXTAUTH_TRUSTEDSOURCES lets Navidrome take the visitor's address from Caddy. Inside the container, Caddy's connections arrive from the server's own IPv4 address, as the Podman guide explains. Without this line, every visitor has that address in Navidrome's log, and Navidrome's limit of five login attempts in 20 seconds per address would lock out everyone at once.
  • ND_ENABLEINSIGHTSCOLLECTOR=false stops the daily usage report. The page where you create the admin account still mentions the reports; the log line Insight Collector is DISABLED shows that they are off.
  • ND_ENABLEEXTERNALSERVICES=false also stops the Deezer and ListenBrainz lookups and the login backgrounds, and replaces them with plain built-in ones. Artist pages then show only what is in your files. If you want artist pictures and biographies, leave this line out.
  • @daily runs a full scan once a day. New and changed files are found within seconds anyway, by a watcher; the nightly scan catches anything it missed.

Start it:

systemctl --user daemon-reload
systemctl --user start navidrome
systemctl --user enable --now podman-auto-update.timer
podman logs navidrome 2>&1 | grep -E "DISABLED|Scheduling"

The log shows All external integrations are DISABLED!, Insight Collector is DISABLED, Scheduling periodic scan and Scheduling periodic backup.

4. Create the admin account through a tunnel

Navidrome has no accounts when it starts, and the first visitor to its web page creates the admin account. So create yours before Caddy makes Navidrome public. On your own computer, open an SSH tunnel to the server:

ssh -L 8106:127.0.0.1:8106 root@203.0.113.10

Keep it open, and open http://localhost:8106 in your browser. That goes straight to Navidrome, not through Caddy. Navidrome shows Thanks for installing Navidrome!: enter a Username, a Password and Confirm Password, and choose Create Admin. Navidrome logs you in, with an empty library. Close the tunnel with exit.

5. Put Caddy in front

As root, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:

music.example.com {
    reverse_proxy 127.0.0.1:8106 {
        # Navidrome also reads the visitor's address from these: drop any a visitor sends.
        header_up -X-Real-IP
        header_up -True-Client-IP
    }
}

Restart Caddy with systemctl --user restart caddy. Caddy sets X-Forwarded-For itself, but passes other headers on as the visitor sent them, and Navidrome prefers X-Real-IP and True-Client-IP when they are there. Without the two lines, a visitor could choose the address Navidrome logs and limits.

Navidrome needs no ND_BASEURL at its own name: it builds links, such as share links and artist pictures for apps, from the name Caddy passes on, and they came out as https://music.example.com/... in the test. You only need ND_BASEURL, such as ND_BASEURL=/music, if you serve Navidrome under a path of another site.

From your own computer, check that the admin page is taken:

curl -s https://music.example.com/auth/createAdmin -H 'Content-Type: application/json' -d '{"username":"test","password":"test1234"}'

The answer is {"error":"Cannot create another first admin"}.

To check that Navidrome sees your real address, log in once with a wrong password on https://music.example.com. Then, as navidrome:

podman logs navidrome 2>&1 | grep auth/login

The line has httpStatus=401 remoteAddr=198.51.100.7: your own address, not the server's.

6. Upload your music

On your own computer, copy your music folder to the server as navidrome:

rsync -av --progress ~/Music/ navidrome@203.0.113.10:music/

The slash after ~/Music/ copies what is in the folder, not the folder itself. Run the same command again after adding albums: rsync only sends new and changed files. Any SFTP program works too, such as FileZilla or WinSCP on Windows: log in as navidrome with your key, and upload into music.

Navidrome watches the folder, and new albums show up within a few seconds. To follow along, as navidrome:

podman logs -f navidrome 2>&1 | grep Scanner

Each new folder gives a line such as Scanner: Completed processing folder audioCount=4 ... tracksImported=4.

Upload as navidrome, not as root. Inside the container, Navidrome runs as root, which is the navidrome user outside it, so it can read everything navidrome owns. A folder that root copied in, with permissions for root only, is skipped with Scanner: Skipping unreadable directory ... permission denied. As root, fix that with:

chown -R navidrome:navidrome /home/navidrome/music

Navidrome sorts by tags, not by file names, so files with good Artist, Album and Title tags show up well. A tag editor such as MusicBrainz Picard fixes tags on your computer before you upload.

7. Listen

Open https://music.example.com and log in. Choose an album, and Play. The player at the bottom keeps playing while you browse.

For your phone, use an app that speaks the Subsonic API. There are many, free and paid, and Navidrome's site lists them under Apps. Some that exist today: on Android, Ultrasonic, Tempus and Symfonium; on iOS, Amperfy and play:Sub; on both, Substreamer. In the app, enter https://music.example.com as the server, with your Navidrome user name and password. We tested the API with curl, not with these apps.

You can test the API yourself. Subsonic apps send the user name, a random s and t, the MD5 hash of the password followed by s. In a terminal on your own computer, with your password in place of PASSWORD:

S=$(openssl rand -hex 6)
T=$(printf '%s%s' 'PASSWORD' "$S" | md5sum | cut -d' ' -f1)
Q="u=admin&t=$T&s=$S&v=1.16.1&c=test&f=json"
curl -s "https://music.example.com/rest/ping?$Q"
curl -s "https://music.example.com/rest/getMusicFolders?$Q"

Both answer with "status":"ok", the second with your library, Music Library. To find a song's ID, and download it as an app would:

curl -s "https://music.example.com/rest/search3?$Q&query=Morning"
curl -s -o song.opus "https://music.example.com/rest/stream?$Q&id=SONG_ID&format=opus&maxBitRate=128"

Leave out &format=opus&maxBitRate=128 to get the original file.

To give each family member their own account, with their own favourites and playlists, open the menu under the person icon at the top right, Users, and Create.

8. Transcoding

Navidrome comes with ffmpeg, and converts tracks when an app asks for a smaller format, such as Opus at 128 kbit/s on a phone. Each app and browser that has played something shows up under Players, in the menu under the person icon, where you can set a Transcoding and a Max. Bit Rate for it.

On the test server's 2 vCPUs, ffmpeg converts a whole track in a few seconds, well ahead of playback:

SourceConverted toTookCPU time
FLAC, 3:49Opus 128 kbit/s4.4 s5.6 s
FLAC, 2:34MP3 192 kbit/s4.2 s5.8 s
MP3, 3:25Opus 128 kbit/s5.3 s5.4 s

So each track uses just over one core for about five seconds, a few percent of the server spread over the song. Navidrome keeps converted tracks in a 100 MB cache, and the same track at the same quality played again took no CPU at all. Several family members listening at once is no strain for this server.

9. Online services

Step 3 switched off everything that contacts other servers. To check, as root, watch the DNS lookups the server makes while you use Navidrome:

apt install -y tcpdump
tcpdump -n -i any port 53

Restart Navidrome as navidrome with systemctl --user restart navidrome, open its login page, and open an artist. With step 3's settings, tcpdump shows nothing from Navidrome. Without ND_ENABLEEXTERNALSERVICES=false, the test server looked up api.deezer.com, auth.deezer.com, pipe.deezer.com and www.navidrome.org. Stop tcpdump with Ctrl+C.

If you do want some of these:

  • Artist pictures and biographies: remove the ND_ENABLEEXTERNALSERVICES=false line, and keep ND_ENABLEINSIGHTSCOLLECTOR=false to keep the usage reports off. That brings back Deezer, ListenBrainz and the login backgrounds.
  • Scrobbling, saving what you play to a Last.fm or ListenBrainz profile, also needs the external services. ListenBrainz works per user, with a token from their ListenBrainz profile. Last.fm needs an API key of your own, from Last.fm, as ND_LASTFM_APIKEY and ND_LASTFM_SECRET in navidrome.env. We did not test scrobbling.

After each change, run systemctl --user daemon-reload and systemctl --user restart navidrome as navidrome.

Navidrome's Shares are public links to a song, album or playlist, which anyone with the link can play without an account. They expire after a year unless you set another date, and the Shares page lists and deletes them.

10. Back up

Your music is a copy of what is on your computer, so whether to back it up from the server is your choice. Navidrome's own data is not: users, passwords, playlists, favourites, ratings and play counts are in its database.

Step 3 makes Navidrome copy its database to ~/backup every night at 03:00, keeping the last seven. To make one now, as navidrome:

podman exec navidrome navidrome backup create
ls ~/backup

Back up ~/backup, ~/navidrome.env and ~/.config/containers/systemd/navidrome.container. Without the key in navidrome.env, nobody can log in to a restored database, and the two together give access to every account, so keep them as safe as the server. To get them off the server every night, use restic on these three, and on ~/music too if you want a copy of your music there.

To restore a backup, as navidrome, with the file name from ls ~/backup:

systemctl --user stop navidrome
podman run --rm -v navidrome-data:/data -v ~/backup:/backup --env-file ~/navidrome.env \
  -e ND_BACKUP_PATH=/backup docker.io/deluan/navidrome:latest \
  backup restore -b navidrome_backup_2026.10.02_17.16.04.db -f
systemctl --user start navidrome

It ends with Restore complete. On the test server, a playlist made after the backup was gone, and the users could still log in, from the web and through the Subsonic API.

11. Updates

The podman-auto-update.timer from step 3 checks once a day for a newer Navidrome image, restarts the container on it, and goes back to the old image if the new one does not start. To see what it would update now, as navidrome:

podman auto-update --dry-run

UPDATED false means you have the newest image. Navidrome updates its database by itself when a new version starts. Read the release notes on Navidrome's GitHub page now and then, as settings are sometimes renamed or removed.

Troubleshooting

An album you uploaded does not show up. The files are not readable by navidrome, and the log has Skipping unreadable directory. Run the chown command from step 6. If the log shows nothing at all, start a scan from the web page: choose the activity icon at the top right, the zigzag line, and then the round arrows for a quick scan or the magnifying glass for a full one.

Every visitor has the server's own address in the log, and a few failed logins lock everyone out. ND_EXTAUTH_TRUSTEDSOURCES is missing, or does not hold the server's IPv4 address. Check it with ip -brief address show eth0.

Nobody can log in, and the log says Password Encryption Key changed! Users won't be able to login!. ND_PASSWORDENCRYPTIONKEY in navidrome.env is not the key the passwords were encrypted with, for example after restoring the database without its navidrome.env. Put the original file back from your backup, and restart Navidrome. On the test server, everyone could log in again. Keep navidrome.env with your backups.

Run it on your own server

VC-S Micro

€7.99/mo

vCPU
2
Memory
8 GB
Storage
250 GB
Transfer
10 TB
Standard
HDD · RAID 10
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides