Skip to content

Media Servers (Jellyfin & Navidrome)

Linux Tuning

/etc/network/interfaces
# /etc/network/interfaces - Optimized for Maximum Throughput on a VMXNET3 Adapter
source /etc/network/interfaces.d/*

auto lo
iface lo inet loopback

allow-hotplug ADAPTERNAME
iface ADAPTERNAME inet static
        address PUTADDRESSHERE/32
        gateway PUTGATEWAYHERE
        dns-nameservers PUTGATEWAYHERE
        mtu 9000

        # === Hardware-Level Performance Enhancements ===

        # 1. Max out NIC Ring Buffers
        post-up ethtool -G $IFACE rx 4096 tx 4096

        # 2. Enable All Hardware Offloads
        post-up ethtool -K $IFACE tx on rx on sg on tso on gso on gro on
        post-up ethtool -K $IFACE ufo on 2>/dev/null || true
        post-up ethtool -K $IFACE lro on 2>/dev/null || true

        # 3. RSS (Receive Side Scaling) - Spread across multiple cores
        post-up ethtool -L $IFACE combined 16 2>/dev/null || ethtool -L $IFACE rx 16 tx 16 2>/dev/null || true

        # 4. Interrupt Coalescing - Balance between latency and throughput
        post-up ethtool -C $IFACE adaptive-rx off adaptive-tx off 2>/dev/null || true
        post-up ethtool -C $IFACE rx-usecs 50 tx-usecs 50 2>/dev/null || true

        # 5. Enable flow control to prevent drops
        post-up ethtool -A $IFACE rx on tx on 2>/dev/null || true

        # 6. Spread NIC IRQs across available CPUs
        post-up for IRQ in $(grep $IFACE /proc/interrupts | awk -F: '{print $1}' | sed 's/ //g'); do CPU_IDX=$((${CPU_IDX:-0})); CPU_MASK=$(printf "%x" $((1 << CPU_IDX))); echo $CPU_MASK > /proc/irq/$IRQ/smp_affinity 2>/dev/null; CPU_IDX=$(((CPU_IDX + 1) % $(nproc))); done

        # 7. Enable RPS (Receive Packet Steering) - all CPUs
        post-up RPS_MASK=$(printf "%x" $(((1 << $(nproc)) - 1))); for RX in /sys/class/net/$IFACE/queues/rx-*/rps_cpus; do echo $RPS_MASK > $RX 2>/dev/null; done

        # 8. Enable RFS (Receive Flow Steering)
        post-up echo 32768 > /proc/sys/net/core/rps_sock_flow_entries 2>/dev/null
        post-up RX_COUNT=$(ls -d /sys/class/net/$IFACE/queues/rx-* 2>/dev/null | wc -l); for RX in /sys/class/net/$IFACE/queues/rx-*/rps_flow_cnt; do echo $((32768 / RX_COUNT)) > $RX 2>/dev/null; done

        # 9. Disable irqbalance for manual IRQ control
        post-up systemctl stop irqbalance 2>/dev/null || true
        post-up systemctl disable irqbalance 2>/dev/null || true

        # 10. Increase TX queue length
        post-up ip link set $IFACE txqueuelen 10000
/etc/sysctl.d/99-network-performance.conf
# /etc/sysctl.d/99-network-performance.conf
# Kernel-level network performance tuning for maximum throughput

# === Core Network Buffer Sizes ===
net.core.rmem_max = 134217728          # 128MB receive buffer
net.core.wmem_max = 134217728          # 128MB send buffer
net.core.rmem_default = 16777216       # 16MB default receive
net.core.wmem_default = 16777216       # 16MB default send
net.core.optmem_max = 40960            # Ancillary buffer size

# === TCP Buffer Tuning ===
net.ipv4.tcp_rmem = 4096 87380 134217728    # TCP receive buffer (min/default/max)
net.ipv4.tcp_wmem = 4096 65536 134217728    # TCP send buffer (min/default/max)
net.ipv4.tcp_mem = 16777216 16777216 16777216  # TCP memory pages

# === Queue and Backlog Sizes ===
net.core.netdev_max_backlog = 250000   # Packets queued on INPUT side
net.core.netdev_budget = 600           # Packets per NAPI poll
net.core.netdev_budget_usecs = 8000    # Time limit per NAPI poll (microseconds)
net.core.somaxconn = 65535             # Max listen() backlog
net.ipv4.tcp_max_syn_backlog = 8192    # SYN backlog

# === TCP Performance Optimizations ===
net.ipv4.tcp_congestion_control = bbr  # Use BBR congestion control
net.core.default_qdisc = fq            # Fair queue scheduler (optimal for BBR)
net.ipv4.tcp_window_scaling = 1        # Enable window scaling
net.ipv4.tcp_timestamps = 1            # Enable timestamps
net.ipv4.tcp_sack = 1                  # Selective acknowledgment
net.ipv4.tcp_fack = 1                  # Forward acknowledgment
net.ipv4.tcp_low_latency = 1           # Optimize for latency
net.ipv4.tcp_slow_start_after_idle = 0 # Don't reduce cwnd after idle
net.ipv4.tcp_tw_reuse = 1              # Reuse TIME_WAIT sockets
net.ipv4.tcp_fin_timeout = 15          # Faster FIN timeout
net.ipv4.tcp_keepalive_time = 300      # Keepalive probe frequency (seconds)
net.ipv4.tcp_keepalive_probes = 5      # Keepalive probe count
net.ipv4.tcp_keepalive_intvl = 15      # Keepalive probe interval (seconds)
net.ipv4.tcp_max_tw_buckets = 2000000  # Max TIME_WAIT sockets
net.ipv4.tcp_fastopen = 3              # Enable TCP Fast Open (client+server)
net.ipv4.tcp_mtu_probing = 1           # Enable MTU probing
net.ipv4.tcp_no_metrics_save = 1       # Don't cache connection metrics
net.ipv4.tcp_moderate_rcvbuf = 1       # Auto-tune receive buffer

# === UDP Buffer Tuning ===
net.ipv4.udp_rmem_min = 8192
net.ipv4.udp_wmem_min = 8192

# === Connection Tracking ===
net.netfilter.nf_conntrack_max = 2000000
net.nf_conntrack_max = 2000000
net.netfilter.nf_conntrack_tcp_timeout_established = 1200

# === IPv4 General ===
net.ipv4.ip_forward = 0                # Disable if not routing
net.ipv4.ip_local_port_range = 1024 65535  # Expanded port range

# === Security ===
net.ipv4.tcp_syncookies = 1            # SYN flood protection
net.ipv4.conf.all.rp_filter = 1        # Reverse path filtering
net.ipv4.conf.default.rp_filter = 1

# === Core System Limits ===
fs.file-max = 2097152                  # Max file descriptors
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 524288

# === Virtual Memory ===
vm.swappiness = 10                     # Reduce swap usage
vm.dirty_ratio = 15                    # Start background writeback at 15%
vm.dirty_background_ratio = 5          # Background writeback at 5%
vm.vfs_cache_pressure = 50             # Retain more inode/dentry cache
vm.max_map_count=262144

# === RPS/RFS ===
net.core.rps_sock_flow_entries = 32768
/etc/fstab
# ALWAYS use UUIDs to map drives, NEVER dev names (i.e. sda sdb)
# sda/sdb etc. enter into race conditions on boot (vmware vdisk init times maybe?). so this mount command will get stuck trying to mount an already mounted drive (grub grabbed it) and your boot will hang, then your secondary mount will timeout and fail silently.
UUID="96b6e8a0-076b-4cf7-bf73-0168f1d3b231" /opt/cachedrive ext4

# high performance NFS mount example

# the settings nocto, and ac* are critical. NFS has caching by DEFAULT, and we're going to keep using it but the defaults are huge, so I'm turning them all to 1 second. actimeo=1` alone is sufficient. It sets all four values (`acregmin`, `acregmax`, `acdirmin`, `acdirmax`) to 1.

TRUENASIP:/mnt/mount/datastore/folder /some/linux/folder_mount_point nfs rw,auto,async,nosuid,nodev,noexec,hard,noatime,nodiratime,vers=4.2,proto=tcp,nconnect=16,rsize=1048576,wsize=1048576,nocto,actimeo=1 0 0

command to reload sysctl.d without restarting

sysctl --system

command brick to generate swapfile

sudo fallocate -l 40G /swapfile && \
sudo chmod 600 /swapfile && \
sudo mkswap /swapfile && \
sudo swapon /swapfile && \
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Jellyfin

Installation (Linux)

curl https://repo.jellyfin.org/install-debuntu.sh | sudo bash

I use Jellarr to make sure my configurations are saved. I have a bat file for easy deployment here (requires git and Node 20+):

run_jellarr.bat
@echo off
cd /d "%~dp0"

if not exist "jellarr" (
    git clone https://github.com/venkyr77/jellarr.git jellarr
    cd jellarr
    npm install
) else (
    cd jellarr
)

npm run dev -- --configFile "%~dp0jellarr_config.yml"

My Customizations

ATOW I don't know when these changes, if any, will actually get merged into Jellyfin. So, here's what I've done and how to set it up.

https://github.com/alchemyyy/jellyfin

place these script outside of the respective jellyfin repo clone folders and run it.

build-jellyfin-debian.bat
@echo off
setlocal

set REPO_DIR=%~dp0jellyfin
set OUTPUT_DIR=%~dp0jellyfin-build-output

echo Building Jellyfin Server for Debian (linux-x64)...
echo.

cd /d "%REPO_DIR%"

dotnet publish Jellyfin.Server/Jellyfin.Server.csproj ^
    -c Release ^
    -f net10.0 ^
    -r linux-x64 ^
    --self-contained true ^
    -o "%OUTPUT_DIR%"

if %ERRORLEVEL% NEQ 0 (
    echo.
    echo BUILD FAILED
    pause
    exit /b 1
)

echo.
echo Build complete. Output: %OUTPUT_DIR%
echo Transfer this folder to your Debian server and run with: ./jellyfin --webdir /path/to/jellyfin-web/dist
pause
build-jellyfin-web.bat
@echo off
cd /d "%~dp0jellyfin-web"
echo Installing dependencies...
call npm ci --engine-strict=false
echo Building local hls.js...
cd /d "%~dp0jellyfin-web\hls.js"
call npm run build
echo Copying local hls.js build over node_modules...
copy /Y "dist\hls.js" "%~dp0jellyfin-web\node_modules\hls.js\dist\hls.js"
copy /Y "dist\hls.js.map" "%~dp0jellyfin-web\node_modules\hls.js\dist\hls.js.map"
cd /d "%~dp0jellyfin-web"
echo Building jellyfin-web (production)...
call npm run build:production
echo Done. Output is in the dist folder.
pause

(you must clone hls.js and put dump it into the clone repo root)

to install web: zip up the "dist" folder and copy onto the jellyfin machine. then dump it into /usr/share/jellyfin-web/ the server gets built to "dist" too, dump it into usr/lib/jellyfin/ on the jellyfin machine.

This script re-does the permissions from that custom server build, and fixes possibly broken symlinks. Run on the jellyfin machine AFTER swapping the server out.

jellyfin_server_build_swapper_helper.sh
chmod -R 777 /usr/lib/jellyfin/
ln -sf /usr/lib/jellyfin/jellyfin /usr/bin/jellyfin
ln -s /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/jellyfin/libjemalloc.so

atow I don't have these settings definable in the UI. So, AFTER creating your libraries, run this script on the jellyfin machine.

jellyfin_crosslibraryseriesmergeenableonall.sh
#!/bin/bash
# Enables EnableCrossLibrarySeriesMerging on all Jellyfin libraries.
# Usage: sudo bash enable-cross-library-merging.sh

JELLYFIN_ROOT="/var/lib/jellyfin/root/default"

if [ ! -d "$JELLYFIN_ROOT" ]; then
    echo "Jellyfin root not found at $JELLYFIN_ROOT"
    exit 1
fi

count=0
for opts in "$JELLYFIN_ROOT"/*/options.xml; do
    [ -f "$opts" ] || continue
    lib="$(basename "$(dirname "$opts")")"

    if grep -q '<EnableCrossLibrarySeriesMerging>true</EnableCrossLibrarySeriesMerging>' "$opts"; then
        echo "[$lib] already enabled"
    elif grep -q '<EnableCrossLibrarySeriesMerging>' "$opts"; then
        sed -i 's|<EnableCrossLibrarySeriesMerging>false</EnableCrossLibrarySeriesMerging>|<EnableCrossLibrarySeriesMerging>true</EnableCrossLibrarySeriesMerging>|' "$opts"
        echo "[$lib] updated false -> true"
        ((count++))
    else
        sed -i 's|</LibraryOptions>|  <EnableCrossLibrarySeriesMerging>true</EnableCrossLibrarySeriesMerging>\n</LibraryOptions>|' "$opts"
        echo "[$lib] inserted"
        ((count++))
    fi
done

echo ""
echo "Updated $count libraries. Restart Jellyfin and rescan libraries to regenerate keys."
echo "  sudo systemctl restart jellyfin"

Jellyfin-Server

Features

  1. Aspect Ratio Computation from Trickplay Data** (26901ca9) Server-side counterpart to the web UI feature — computes the actual aspect ratio of content by analyzing trickplay images for black bars.

  2. Cross-Library Series Joining (a340180d) Joins series across multiple libraries (not just across folders within a single library) and deduplicates cross-library series in search results.

Jellyfin-Web

Features

  1. Detected Aspect Ratio via Trickplay Analysis (563dbcea) Adds a "Detected aspect ratio" option that uses trickplay image black bar analysis to automatically determine and apply the correct aspect ratio during playback. (Went through a couple revert/re-apply cycles before landing.)

  2. Mixed Libraries Show Tall Cards (e54fddf9) Makes mixed-content libraries display tall (portrait) cards instead of the default layout.

Fixes

  1. HLS.js Tuning for FFmpeg Segment Gaps (bf1c253f) Adjusts hls.js config (stretchShortVideoTrack, nudgeMaxRetry: 10, maxBufferHole: 0.5) to prevent micro-stalls and fatal errors caused by audio/video frame boundary mismatches in FFmpeg-muxed HLS streams — especially during long playback sessions.

  2. CSP-safe HLS.js Worker Loading (07805ffc) Loads the hls.js transmuxer worker from a file URL instead of an inline blob URL, avoiding internalException errors on origins with strict Content Security Policy.

  3. Image Loading During Fast Scrolling (6957b66d) Stops aggressively unloading images that scroll out of view, and adds retry logic for failed image preloads so thumbnails don't stay grey permanently.

  4. Notification Permission Check (70604623) Checks Notification.permission before calling showNotification() to prevent uncaught TypeError when permission hasn't been granted.

CSS Customziations

Dump this into branding->css textbox in the admin dashboard

jellyfin.css
.homePage .emby-scrollbuttons {
    display: none;
}

.homePage .section0 .itemsContainer {
    display: grid !important;
    grid-template-columns: repeat(auto-fill, min(max(15%, 270px), 650px));
    justify-content: center;
    margin: 0 auto;
    gap: 1em;
}

.homePage .section0 .itemsContainer .card {
    width: 100% !important;
    height: auto !important;
}

.homePage .section0 .itemsContainer .card .cardBox,
.homePage .section0 .itemsContainer .card .cardScalable {
    width: 100% !important;
    padding-bottom: 56.25% !important;
    height: 0 !important;
    position: relative !important;
}

.homeSectionsContainer {
    display: flex;
    flex-direction: column;
}

.homeSectionsContainer .section0 {
    order: 0 !important;
}
.homeSectionsContainer .section1 {
    order: 1 !important;
}
.homeSectionsContainer .section6 {
    order: 2 !important;
}
.homeSectionsContainer .section4 {
    order: 3 !important;
}
.homeSectionsContainer .section2 {
    order: 4 !important;
}
.homeSectionsContainer .section3 {
    order: 5 !important;
}
.homeSectionsContainer .section5 {
    order: 6 !important;
}

.section6 {
    display: flex;
    flex-direction: column;
}
.section6 .verticalSection:nth-child(5) {
    order: 1 !important;
} /* Television */
.section6 .verticalSection:nth-child(4) {
    order: 2 !important;
} /* Movies */
.section6 .verticalSection:nth-child(2) {
    order: 3 !important;
} /* Anime */
.section6 .verticalSection:nth-child(1) {
    order: 4 !important;
} /* Animated */
.section6 .verticalSection:nth-child(3) {
    order: 5 !important;
} /* Cartoons */

.sectionTitleContainer-cards {
    padding-top: 0 !important;
}

.homePage .section1 .overflowBackdropCard,
.homePage .section5 .overflowBackdropCard {
    max-width: max(15%, 270px) !important;
}

.homePage .section6 .card {
    width: min(max(8vw, 120px), 200px) !important;
}

.homePage .padded-left,
.homePage .emby-scroller {
    padding-left: 1% !important;
}

.homePage .section0 .padded-left,
.homePage .section0 .emby-scroller {
    padding-left: 0 !important;
    padding-right: 0 !important;
}

.homePage .section0 > .sectionTitle {
    font-size: 0 !important;
    line-height: 0 !important;
    display: block !important;
    text-align: center !important;
}

#.homePage .section0 > .sectionTitle::after {
#    content: "Libraries";
#    font-size: 1.35rem;
#    line-height: normal;
#}

REDACTED

Linux tuning

nano /etc/default/jellyfin

# comment out the MALLOC_TRIM_THRESHOLD i.e. disable it

# Tweaks
MALLOC_ARENA_MAX="2"
DOTNET_gcServer = "1"
DOTNET_GCConserveMemory = "9"
DOTNET_GCHeapHardLimitPercent = "0x5a"
DOTNET_GCHighMemPercent=48
NVIDIA_DRIVER_CAPABILITIES=all
NVIDIA_VISIBLE_DEVICES=all

Important Config Notes

  • Enable audio downmix boost, use AC4 as the algorithm
  • Query what codecs your specific GPU supports
  • Trickplay: use keyframes
  • Networking: Known Proxies: 127.0.0.1.
    • This makes it so caddy is seen as a known proxy, which makes all traffic use X-Forwarded-For, which makes it so users IPs are resolved instead of the cloudflare IP they're connecting with.
  • Only use TVDB and TMDB as metadata services. Use OpenSubtitles as subtitle service, pay for a bit of VIP if you need a ton of API for a bit.
  • Always save all metadata types to library folders

My Libraries

  • #AutoAnime
  • #AutoMovies
  • #AutoTV
  • Animated
  • Anime
  • Cartoons
  • Movies
  • Television

Jellyfin WebSocket "Token is required" — EnableLegacyAuthorization

Problem: Every WebSocket connection to Jellyfin fails immediately. Browser console spams:

WebSocket connection to 'ws://<host>:8096/socket?api_key=...' failed:
web socket closed
nulling out web socket

Server-side logs show:

[ERR] Jellyfin.Api.Middleware.ExceptionMiddleware: Error processing request: Token is required. URL GET /socket.

All HTTP API requests work fine — only WebSocket is broken.

Root cause: The bundled jellyfin-apiclient (v1.11.0, deprecated) authenticates WebSocket connections by passing api_key=<token> as a query parameter. Starting in Jellyfin 10.11, the server introduced EnableLegacyAuthorization as an opt-in flag to disable deprecated auth methods (query string api_key, old header formats). In 10.12 this defaults to false, and in 10.13 legacy auth will be removed entirely.

When EnableLegacyAuthorization is false, the server ignores the api_key query parameter on the /socket endpoint, sees no valid token, and rejects the upgrade with "Token is required." The WebSocket never establishes.

Fix: Edit /etc/jellyfin/system.xml:

<EnableLegacyAuthorization>true</EnableLegacyAuthorization>

Then restart: sudo systemctl restart jellyfin

This is a stopgap — the real fix is for the web client to send auth via the Authorization header instead of query string, but the deprecated jellyfin-apiclient library doesn't support that for WebSocket connections.

Relevant links: - PR #13306 — Add option to disable deprecated legacy authorization — the PR that introduced EnableLegacyAuthorization - Issue #15962 — Default EnableLegacyAuthorization=false breaks auth in 10.12 - Issue #12990 — API Authentication documentation incorrect - Jellyfin API Authorization gist — detailed explanation of the auth deprecation timeline

Music server. Best there is, flawless.

Clients

I use feishin as a desktop app.

I use subtracks on android.

Installation

Installer (auto grabs latest github linux amd64 release):

navidrome_install_debian.sh
#!/usr/bin/env bash
set -euo pipefail

# === Navidrome Installer for Debian-based Systems ===

if [[ $EUID -ne 0 ]]; then
    echo "This script must be run as root."
    exit 1
fi

# --- Prompt for user and group ---
read -rp "Enter the Linux user to run Navidrome as: " ND_USER
read -rp "Enter the Linux group to run Navidrome as: " ND_GROUP

# Create group if it doesn't exist
if ! getent group "$ND_GROUP" > /dev/null 2>&1; then
    echo "Group '$ND_GROUP' does not exist. Creating..."
    groupadd "$ND_GROUP"
fi

# Create user if it doesn't exist
if ! id -u "$ND_USER" > /dev/null 2>&1; then
    echo "User '$ND_USER' does not exist. Creating..."
    useradd -r -g "$ND_GROUP" -s /usr/sbin/nologin -M "$ND_USER"
fi

# --- Install dependencies ---
apt update
apt upgrade -y
apt install -y ffmpeg jq curl wget

# --- Create directories ---
install -d -o "$ND_USER" -g "$ND_GROUP" /opt/navidrome
install -d -o "$ND_USER" -g "$ND_GROUP" /var/lib/navidrome

# --- Download latest Navidrome ---
LATEST_VERSION=$(curl -s https://api.github.com/repos/navidrome/navidrome/releases/latest | jq -r '.tag_name' | sed 's/^v//')
echo "Downloading Navidrome v${LATEST_VERSION}..."
wget "https://github.com/navidrome/navidrome/releases/download/v${LATEST_VERSION}/navidrome_${LATEST_VERSION}_linux_amd64.tar.gz" -O /tmp/navidrome.tar.gz

tar -xzf /tmp/navidrome.tar.gz -C /opt/navidrome/
chown -R "$ND_USER":"$ND_GROUP" /opt/navidrome
rm /tmp/navidrome.tar.gz

# --- Write navidrome.toml ---
cat > /var/lib/navidrome/navidrome.toml << 'TOML'
MusicFolder = "/shares/media/Music"

Scanner.Schedule = "0"
Scanner.WatcherWait = "1s"
Scanner.ScanOnStartup = true
Scanner.FollowSymlinks = true
Scanner.PurgeMissing = "always"

DevExternalScanner = true
# NFS share is configured with multithreading
# Otherwise, beware this setting.
DevScannerThreads = 8
DevSelectiveWatcher = true
DevOptimizeDB = true

EnableArtworkPrecache = true
ImageCacheSize = "8GB"
TranscodingCacheSize = "8GB"
RecentlyAddedByModTime = true

#LastFM.ApiKey = ""
#LastFM.Secret = ""
#Spotify.ID = ""
#Spotify.Secret = ""

# === SHARING & AUTH ===
# EnableSharing = true
# AuthWindowLength = "0s"
# AuthRequestLimit = 11111
TOML

chown "$ND_USER":"$ND_GROUP" /var/lib/navidrome/navidrome.toml

# --- Write systemd service ---
cat > /etc/systemd/system/navidrome.service << EOF
[Unit]
Description=Navidrome Music Server and Streamer compatible with Subsonic/Airsonic
After=remote-fs.target network.target
AssertPathExists=/var/lib/navidrome

[Install]
WantedBy=multi-user.target

[Service]
User=${ND_USER}
Group=${ND_GROUP}
Type=simple
ExecStart=/opt/navidrome/navidrome --configfile "/var/lib/navidrome/navidrome.toml"
WorkingDirectory=/var/lib/navidrome
TimeoutStopSec=20
KillMode=process
Restart=on-failure

DevicePolicy=closed
NoNewPrivileges=yes
PrivateTmp=yes
PrivateUsers=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap
ReadWritePaths=/var/lib/navidrome

ProtectSystem=full
EOF

# --- Enable and start ---
systemctl daemon-reload
systemctl enable navidrome.service
systemctl start navidrome.service

echo ""
echo "Navidrome v${LATEST_VERSION} installed and running."
echo "  User/Group: ${ND_USER}:${ND_GROUP}"
echo "  Config:     /var/lib/navidrome/navidrome.toml"
echo "  Service:    systemctl status navidrome"
echo "  Web UI:     http://<your-ip>:4533"

Tricks

Clear Playlists:

sudo systemctl stop navidrome
sqlite3 /var/lib/navidrome/navidrome.db "DELETE FROM playlist_tracks; DELETE FROM playlist;"
sudo systemctl start navidrome