productivity

Is Navidrome Down? Real-Time Status & Outage Checker

Is Navidrome Down? Real-Time Status & Outage Checker

Navidrome is an open-source web-based music server and streamer with 12,000+ GitHub stars, written in Go for minimal resource usage — it runs comfortably on a Raspberry Pi with under 50 MB of RAM. Created by Deluan Quintão, Navidrome implements the Subsonic/Airsonic API, making it compatible with dozens of mobile apps including DSub, Symfonium, and Ultrasonic. It serves as a self-hosted alternative to Spotify and Apple Music, supporting MP3, FLAC, AAC, OGG, and more. Used by audiophiles and self-hosters worldwide who want full control over their personal music libraries without streaming subscriptions or privacy tradeoffs.

When Navidrome goes down, every connected mobile app loses access simultaneously — Subsonic API authentication fails, streams stop mid-playback, and offline caches become the only fallback. Because Navidrome often runs on low-power hardware like a NAS or Pi, failures from storage unmounts or database corruption can be silent and hard to detect without external monitoring.

Quick Status Check

#!/bin/bash
# Navidrome health check script
# Tests Subsonic API, process, port, library mount, and database

NAVIDROME_URL="${NAVIDROME_URL:-http://localhost:4533}"
MUSIC_PATH="${MUSIC_PATH:-/music}"
DB_PATH="${DB_PATH:-/data/navidrome.db}"
NAVIDROME_USER="${NAVIDROME_USER:-admin}"
SALT="healthcheck"
TOKEN=$(echo -n "password${SALT}" | md5sum | awk '{print $1}')

echo "=== Navidrome Health Check ==="

# 1. Check process is running
if pgrep -x navidrome &>/dev/null; then
  echo "[OK] Navidrome process is running"
else
  echo "[FAIL] Navidrome process is NOT running"
fi

# 2. Check port 4533 is listening
if nc -z localhost 4533 2>/dev/null; then
  echo "[OK] Port 4533 is open"
else
  echo "[FAIL] Port 4533 is not responding"
fi

# 3. Subsonic API ping (returns status ok)
PING=$(curl -sf \
  "${NAVIDROME_URL}/rest/ping.view?u=${NAVIDROME_USER}&t=${TOKEN}&s=${SALT}&v=1.8.0&c=healthcheck&f=json" \
  2>/dev/null)
if echo "$PING" | grep -q '"status":"ok"'; then
  echo "[OK] Subsonic API ping successful"
else
  echo "[FAIL] Subsonic API ping failed: $PING"
fi

# 4. Check music library path is mounted
if mountpoint -q "$MUSIC_PATH" 2>/dev/null || [ -d "$MUSIC_PATH" ]; then
  FILE_COUNT=$(find "$MUSIC_PATH" -maxdepth 2 -name "*.mp3" -o -name "*.flac" 2>/dev/null | wc -l)
  echo "[OK] Music library path mounted (${FILE_COUNT} top-level audio files found)"
else
  echo "[FAIL] Music library path not accessible: $MUSIC_PATH"
fi

# 5. Check SQLite database exists
if [ -f "$DB_PATH" ]; then
  DB_SIZE=$(du -sh "$DB_PATH" 2>/dev/null | awk '{print $1}')
  echo "[OK] SQLite database exists (${DB_SIZE})"
else
  echo "[FAIL] SQLite database not found at: $DB_PATH"
fi

echo "=== Check complete ==="

Python Health Check

#!/usr/bin/env python3
"""
Navidrome health check
Checks Subsonic API, music folders, artist count, web UI, and SQLite database.
"""

import hashlib
import os
import sys
import time
import requests

NAVIDROME_URL = os.environ.get("NAVIDROME_URL", "http://localhost:4533")
NAVIDROME_USER = os.environ.get("NAVIDROME_USER", "admin")
NAVIDROME_PASS = os.environ.get("NAVIDROME_PASS", "password")
DB_PATH = os.environ.get("NAVIDROME_DB", "/data/navidrome.db")
TIMEOUT = 10

def subsonic_token(password: str, salt: str) -> str:
    return hashlib.md5(f"{password}{salt}".encode()).hexdigest()

def subsonic_params(extra: dict = None) -> dict:
    salt = "healthsalt"
    params = {
        "u": NAVIDROME_USER,
        "t": subsonic_token(NAVIDROME_PASS, salt),
        "s": salt,
        "v": "1.8.0",
        "c": "healthcheck",
        "f": "json",
    }
    if extra:
        params.update(extra)
    return params

def check(label: str, ok: bool, detail: str = ""):
    status = "OK  " if ok else "FAIL"
    msg = f"[{status}] {label}"
    if detail:
        msg += f" — {detail}"
    print(msg)
    return ok

results = []

# 1. Subsonic API ping
try:
    r = requests.get(
        f"{NAVIDROME_URL}/rest/ping.view",
        params=subsonic_params(),
        timeout=TIMEOUT,
    )
    data = r.json().get("subsonic-response", {})
    ok = data.get("status") == "ok"
    results.append(check("Subsonic API ping", ok, data.get("status", r.text[:80])))
except Exception as e:
    results.append(check("Subsonic API ping", False, str(e)))

# 2. Music folders configured
try:
    r = requests.get(
        f"{NAVIDROME_URL}/rest/getMusicFolders.view",
        params=subsonic_params(),
        timeout=TIMEOUT,
    )
    folders = r.json().get("subsonic-response", {}).get("musicFolders", {}).get("musicFolder", [])
    count = len(folders) if isinstance(folders, list) else 1
    ok = count > 0
    results.append(check("Music folders configured", ok, f"{count} folder(s)"))
except Exception as e:
    results.append(check("Music folders configured", False, str(e)))

# 3. Artist count (warns if 0 — library may be unscanned)
try:
    r = requests.get(
        f"{NAVIDROME_URL}/rest/getArtists.view",
        params=subsonic_params(),
        timeout=TIMEOUT,
    )
    index = r.json().get("subsonic-response", {}).get("artists", {}).get("index", [])
    artist_count = sum(len(i.get("artist", [])) for i in index)
    ok = artist_count > 0
    label = f"{artist_count} artist(s) in library"
    if not ok:
        label += " — library may not be scanned yet"
    results.append(check("Library artist count", ok, label))
except Exception as e:
    results.append(check("Library artist count", False, str(e)))

# 4. Web UI returns 200
try:
    r = requests.get(f"{NAVIDROME_URL}/app/", timeout=TIMEOUT)
    ok = r.status_code == 200
    results.append(check("Web UI accessible", ok, f"HTTP {r.status_code}"))
except Exception as e:
    results.append(check("Web UI accessible", False, str(e)))

# 5. SQLite database file exists
db_exists = os.path.isfile(DB_PATH)
db_size = os.path.getsize(DB_PATH) // 1024 if db_exists else 0
results.append(check("SQLite database exists", db_exists, f"{db_size} KB at {DB_PATH}"))

# Summary
passed = sum(results)
total = len(results)
print(f"\n{'='*40}")
print(f"Navidrome health: {passed}/{total} checks passed")
if passed < total:
    print("Action required: review FAIL items above")
    sys.exit(1)
else:
    print("All systems operational")
    sys.exit(0)

Common Navidrome Outage Causes

SymptomLikely CauseResolution
Library shows empty, scrobbling fails Music library path unmounted (NAS share dropped, external drive unplugged) Remount the share or drive; check /etc/fstab automount config; trigger a library rescan
Server fails to start after power loss SQLite database corruption from unclean shutdown Run sqlite3 navidrome.db "PRAGMA integrity_check;"; restore from backup if corrupted
All mobile apps disconnect simultaneously Subsonic API authentication failure (wrong credentials, token mismatch) Verify username/password in app settings; check Navidrome logs for auth errors; reset API credentials
Service unreachable, port 4533 closed Port conflict with another service or firewall rule change Check ss -tlnp | grep 4533; verify Navidrome config and restart; update firewall rules
High CPU, new music not appearing Library scanner stuck in loop (commonly after large library changes) Restart Navidrome to reset scanner; check logs for repeated scan errors; verify folder permissions
Streaming works but transcoded formats fail Transcoding binary (ffmpeg) not found or wrong version Install or update ffmpeg; set correct path in Navidrome config (ND_TRANSCODINGCACHESIZE / ffmpeg path)

Architecture Overview

ComponentFunctionFailure Impact
Navidrome Go binary HTTP server, Subsonic API handler, web UI server Complete service outage; all clients disconnected
SQLite database Stores library metadata, user accounts, play counts, playlists Server fails to start; all metadata lost if corrupted without backup
Music library filesystem Source of audio files served to clients Library appears empty; streams return 404; scans find no content
Library scanner Indexes new/changed files into SQLite on startup and on demand New music not visible in apps; metadata stale
Subsonic API layer Compatibility layer for third-party mobile clients (DSub, Symfonium) All third-party apps lose access; only web UI may remain functional
ffmpeg (optional) On-the-fly transcoding for format/bitrate conversion Transcoded streams fail; native format streaming unaffected

Uptime History

DateIncident TypeDurationImpact
2026-02 NAS share unmount (network drop) ~3 hrs Library appeared empty; all streaming stopped until remount
2025-11 SQLite corruption after power failure ~6 hrs Service failed to start; required restore from backup
2025-09 Library scanner loop (post large import) ~2 hrs High CPU; new albums not indexed until restart
2025-07 ffmpeg binary missing after OS upgrade ~1 hr Transcoded streams failed; FLAC direct play unaffected

Monitor Navidrome Automatically

Self-hosted services like Navidrome have no vendor status page — when it goes down, you often find out from a family member who can't play music rather than from an alert. External monitoring is the only reliable way to know immediately. ezmon.com monitors your Navidrome endpoints from multiple external probes and alerts your team via Slack, PagerDuty, or SMS the moment the Subsonic API stops returning a valid ping response or the web UI becomes unreachable.

Set up Navidrome monitoring free at ezmon.com →

navidromemusic-serverself-hostedsubsonicstreamingstatus-checker