How to Deploy Navidrome on a VPS
Updated Sep 2026
verified on Ubuntu 26.04 · Sep 2026Self-host Navidrome on a tiny VPS with Docker Compose and HTTPS — a Subsonic-compatible music server that streams your own library to web, mobile and desktop apps.
- A small VPS — 1 vCPU / 512 MB RAM is plenty for the server
- Enough disk for your music library
- A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
- A domain you can point at the server
- Docker Engine + Compose installed (see the base guide below)
What Navidrome is
Navidrome is a self-hosted music streaming server. It indexes a folder of music files, serves a web player, and — the part that matters most — speaks the Subsonic API, so dozens of existing mobile and desktop music apps can connect to it as if it were a streaming service. Your library, your server, whichever client you prefer.
It is a single Go binary under GPL-3.0, rated 2 / 5 to deploy. The install is one container. The two things worth getting right are file permissions (Navidrome runs as your user, not root) and setting a password encryption key before you create any users.
Server sizing
Navidrome is the lightest media server in the catalog. On our test box (a GCP e2-standard-2 on Ubuntu 26.04) the idle container used about 16 MB of RAM and 333 MB of disk. The catalog's floor is 256 MB of RAM.
- 512 MB RAM / 1 vCPU — comfortable for a personal library, with room for the OS and a reverse proxy.
- 1 GB RAM+ — only if the same box runs other services.
The first scan of a large library is the busiest moment; upstream's own estimate is from under a minute for fewer than 1,000 songs to 15 minutes or more past 50,000. After that, CPU is mostly idle unless clients ask for transcoded streams. Disk is the real constraint — a lossless library outgrows a small VPS disk quickly, so plan for attached block storage if yours is large.
Prepare the server
This guide assumes Docker Engine and the Compose plugin are installed, along
with a non-root user and a ufw firewall. If not, work through
Docker & Compose on Ubuntu first.
Open SSH and the reverse proxy ports only:
sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw --force enable
sudo ufw status verbose
Paid link — we earn a commission if you shop through it.
Install Navidrome (Docker Compose)
Permissions first
Navidrome needs read-write access to /data (its database and cache),
read access to /music, and upstream is firm that it should not run as
root. The image uses a user: directive — the PUID/PGID variables you
may know from linuxserver images have no effect here. So create the folders as
your own user and record your IDs:
mkdir -p ~/navidrome && cd ~/navidrome
mkdir -p data music backup
printf 'PUID=%s\nPGID=%s\n' "$(id -u)" "$(id -g)" > .env
The password encryption key
To stay compatible with the Subsonic API, Navidrome has to store user
passwords in a recoverable form. By default it encrypts them with a shared key
that is in the public source code, which is obfuscation rather than
protection. Setting your own PasswordEncryptionKey fixes that — and upstream
warns it is a one-time setting: once passwords are encrypted with it,
changing it locks users out. Set it now, before the first user exists:
cd ~/navidrome
grep -q ND_PASSWORDENCRYPTIONKEY .env || echo "ND_PASSWORDENCRYPTIONKEY=$(openssl rand -hex 32)" >> .env
Keep a copy of .env with your backups. Without that key, a restored database
has passwords nobody can use.
The compose file
This is upstream's compose example with the port on loopback, the music mounted read-only, and automatic database backups turned on:
cd ~/navidrome
cat > docker-compose.yml <<'YAML'
services:
navidrome:
image: deluan/navidrome:latest
container_name: navidrome
user: "${PUID}:${PGID}"
ports:
# Loopback only — Caddy is the sole route in from outside.
- "127.0.0.1:4533:4533"
restart: unless-stopped
environment:
ND_PASSWORDENCRYPTIONKEY: ${ND_PASSWORDENCRYPTIONKEY}
ND_BACKUP_PATH: /backup
ND_BACKUP_SCHEDULE: "0 3 * * *"
ND_BACKUP_COUNT: 7
volumes:
- ./data:/data
- ./music:/music:ro
- ./backup:/backup
YAML
docker compose up -d
Check that it started and answers:
cd ~/navidrome
for i in $(seq 1 30); do curl -fsS -o /dev/null http://127.0.0.1:4533/ && break; sleep 2; done
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4533/
docker compose logs --tail 20 navidrome
The line to look for in the log is Navidrome server is ready!, followed by
the first library scan.
HTTPS + domain
Point an A record for music.example.com at the server's public IP, wait
for it to resolve, then put Caddy in front — see
Automatic HTTPS with Caddy:
music.example.com {
reverse_proxy 127.0.0.1:4533
}
Navidrome's security notes recommend exactly this: its built-in HTTP server
works, but a reverse proxy should handle TLS. If you'd rather serve it under a
path such as example.com/music, set ND_BASEURL: /music in the environment
and proxy that path. If Caddy runs as a container, use
reverse_proxy navidrome:4533 inside a shared compose file instead of
127.0.0.1.
First run
Open https://music.example.com. The first screen is admin user creation:
pick a username and password and click Create Admin. As with most servers of
this kind, whoever reaches the page first gets the admin account, so do this
immediately after the proxy is up.
Then upload your music. The simplest route is rsync from your own computer
into ~/navidrome/music, organised as Artist/Album/tracks. Navidrome notices
new files on its own; you can browse as soon as the first albums appear.
To use a phone app, pick any Subsonic-compatible client and point it at
https://music.example.com with your Navidrome username and password.
Securing it
- One user per listener. Play counts, favourites and playlists are per user.
- Leave transcoding config locked. Upstream disables editing transcoding
commands in the web UI by default, because a transcoding command is a command
that runs on your server. Enable
ND_ENABLETRANSCODINGCONFIGonly while you make a change, then turn it off again. - Rely on the rate limiter, but use strong passwords. Login attempts are rate-limited by default, but Subsonic clients authenticate with your password on every request, so make it a unique one.
- Never drop the
user:line to "fix" permissions. Running as root makes a permission error disappear by removing the protection. Fix the folder ownership instead.
Backups
The compose file above already makes a daily database backup at 03:00,
keeping seven, in ~/navidrome/backup. Those contain the database only —
users, play counts, playlists — not the music and not your .env. You can
also make one on demand:
cd ~/navidrome
docker compose run --rm navidrome backup create
ls -la backup
Then copy the backups, .env (the encryption key) and docker-compose.yml
off the box, along with the music if it doesn't exist anywhere else:
cd ~/navidrome
tar czf navidrome-$(date +%F).tar.gz backup .env docker-compose.yml
To restore, upstream is explicit that the server must be stopped first:
docker compose stop, then docker compose run --rm navidrome backup restore
with the backup file, then docker compose start.
Upgrades
cd ~/navidrome
docker compose pull
docker compose up -d
latest follows each release. Read the release notes before upgrading across
a larger version jump; the automatic backup gives you a recent restore point
either way.
Troubleshooting
The container exits at startup with unable to open database file. The
data folder isn't writable by the IDs in .env. Fix it with
sudo chown -R $(id -u):$(id -g) ~/navidrome/data.
The web UI works but the library is empty, and the log says
open /music: permission denied. The music folder isn't readable. Either
chown it to your user, or open it for reading with
sudo chmod -R a+rX ~/navidrome/music. Upstream notes the misleading
Target folder does not exist warning that comes with it — the folder exists;
the container user just can't open it.
A client app can't log in. Check the server URL includes https:// and no
port, and that the same username and password work in the web UI. If the
web UI works and the app doesn't, read docker compose logs --tail 50 navidrome while the app retries — the failed request shows up there.
Verification + next steps
You're done when you can load https://music.example.com over a valid
certificate, sign in, play a track in the browser, connect a Subsonic app on
your phone, and see a file in ~/navidrome/backup the morning after.
From there, automate the library with Lidarr, which watches artists for new releases and files them into your music folder. For films and TV, Jellyfin is the companion server; for audiobooks and podcasts, Audiobookshelf.