VPS에 Navidrome으로 나만의 음악 스트리밍 서버 구축하기
VPS 환경에서 Navidrome을 사용하여 개인 음악 라이브러리를 스트리밍하는 방법을 안내합니다. Subsonic API 호환 클라이언트 설정과 오프라인 재생을 위한 데이터 저장소 구성, TLS 보안 적용 및 안정적인 백업 전략을 포함한 전체 구축 과정을 상세히 설명합니다.
VPS에서 직접 호스팅하는 음악 스트리밍의 장점
VPS에서 음악 스트리밍을 직접 호스팅한다는 것은 플레이어를 직접 운영하고 음악 파일을 직접 제공한다는 의미입니다. 서버는 사용자가 이미 소유한 파일을 보관하며, 인터넷이 연결된 모든 스마트폰에서 일반적인 로그인 절차를 거쳐 음악에 접근할 수 있습니다. 시작하기 전에 이 방식의 한계를 명확히 이해해야 합니다. 이 방식은 스트리밍 서비스의 플레이어를 대체할 뿐, 그들의 카탈로그를 대체하지는 않습니다. 음악을 구매하거나 리핑하여 서버로 파일을 복사하기 전까지는 라이브러리에 새로운 곡이 추가되지 않습니다.
오디오는 비디오보다 서버 자원을 훨씬 적게 소모합니다. 파일 크기가 작고, 스마트폰은 별도의 도움 없이도 일반적인 오디오 형식을 모두 디코딩할 수 있으며, 한 명의 청취자가 사용하는 대역폭은 화상 통화보다 적습니다. CPU 자원은 이 작업에서 큰 문제가 되지 않습니다. 실제 제약 사항은 디스크 공간입니다. 이 시스템의 성공 여부를 결정하는 나머지 두 가지 요소는 태그의 품질과 스마트폰 앱이 오프라인 재생을 위해 음악을 다운로드할 수 있는지 여부입니다.
어떤 음악 서버를 선택할 것인가: Navidrome, Jellyfin, 아니면 Funkwhale?
Navidrome은 오직 오디오만 고려할 때 기본적으로 선택하는 도구입니다. 하나의 컨테이너에서 실행되는 단일 Go 바이너리이며, 모든 상태를 하나의 SQLite 데이터베이스에 저장합니다. Subsonic API를 지원하므로, 이를 활용하는 다양한 서드파티 모바일 앱을 사용할 수 있습니다. 2026년 8월 기준으로 0.63.2 버전이 최신입니다. 이 프로젝트는 Raspberry Pi Zero와 같은 저사양 하드웨어에서도 원활하게 작동하므로, 서버 소프트웨어 비용을 걱정할 필요가 없습니다.
Jellyfin은 이미 VPS에서 미디어 서버로 Jellyfin을 운영 중이라면 고려할 가치가 있습니다. 음악 라이브러리 기능이 제공되며, Android와 iOS용 Jellyfin 음악 앱인 Finamp를 통해 오프라인 재생을 위한 트랙 다운로드가 가능합니다. 한계는 API에 있습니다. Jellyfin은 Subsonic 엔드포인트를 내장하고 있지 않으며, 이를 추가해주던 커뮤니티 플러그인은 2022년부터 업데이트가 중단되었습니다. 따라서 Subsonic 앱 생태계를 이용할 수 없습니다. 대신 Jellyfin 고유 API를 지원하는 앱을 사용해야 하는데, 선택지가 상대적으로 적습니다.
Funkwhale은 연합(federated) 기능을 제공하는 옵션이며, 2026년 3월에 2.0 버전이 출시되었습니다. Funkwhale 서버는 'pod'라고 불리며, Mastodon의 기반 프로토콜인 ActivityPub을 통해 서로 연합합니다. 한 pod의 사용자는 다른 pod의 공개 라이브러리를 팔로우할 수 있습니다. 또한 Subsonic API의 일부를 지원하는데, 한 가지 알아두어야 할 차이점이 있습니다. Subsonic 프로토콜은 서버가 읽을 수 있는 비밀번호를 요구하기 때문에, 사용자가 각자 설정에서 별도의 Subsonic 비밀번호를 지정해야 합니다. Funkwhale은 웹 앱 외에도 PostgreSQL과 작업 큐(task queue)가 필요하므로 설치 과정이 더 복잡합니다.
연합 기능이 필요하거나 이미 Jellyfin을 운영 중인 경우가 아니라면 Navidrome을 선택하십시오. 이 가이드의 나머지 부분에서는 Docker를 사용하여 Navidrome을 설정합니다.
Subsonic API가 모바일 앱 선택을 결정하는 이유
Subsonic은 음악 서버였으며, 그 HTTP API는 셀프 호스팅 오디오의 공통 언어가 되었습니다. OpenSubsonic은 이를 지속적으로 확장하는 커뮤니티 프로젝트입니다. 이러한 역사 덕분에 사용자는 휴대폰에서 다양한 앱을 선택할 수 있습니다. Navidrome은 자체 모바일 앱을 제공하지 않으며, 그럴 필요도 없습니다. 모든 Subsonic 클라이언트는 서버 주소와 계정 정보만으로 로그인할 수 있기 때문입니다.
이는 오프라인 동기화에서 가장 중요하게 작용하며, 이 기능이 일상적인 사용 환경에서 시스템의 지속 가능 여부를 결정합니다. 터널에 있는 휴대폰은 서버에 연결할 수 없으므로, 클라이언트는 미리 파일을 로컬 저장소로 복사해 두어야 합니다. 모든 클라이언트가 스트리밍을 지원하지만, 다운로드를 지원하는 클라이언트는 일부입니다. navidrome.org/apps의 클라이언트 목록에서 다운로드 지원 여부를 확인할 수 있으며, Android의 Substreamer와 Ultrasonic, iOS의 Amperfy와 play:Sub 등 두 플랫폼 모두에서 선택의 폭이 넓습니다. 가장 강력한 클라이언트 중 다수는 유료 앱이며, Android 사용자들 사이에서는 Symfonium이 가장 자주 언급됩니다. 매일 사용하는 시스템의 핵심 부분이므로, 결정하기 전에 두 가지 이상의 앱을 설치해 보시기 바랍니다.
음악 라이브러리에는 어느 정도의 저장 공간이 필요합니까?
The data behind this chart
[
{
"label": "Opus 128k",
"kbps": 128,
"gb_per_1000_albums": 43
},
{
"label": "MP3 320k",
"kbps": 320,
"gb_per_1000_albums": 108
},
{
"label": "FLAC 16/44.1",
"kbps": 900,
"gb_per_1000_albums": 304
},
{
"label": "FLAC 24/96",
"kbps": "3,000",
"gb_per_1000_albums": "1,013"
}
]이 수치들은 실제 컬렉션을 측정한 것이 아니라 비트레이트에서 도출한 대략적인 규모이며, 산술 방식은 간단하여 본인의 파일과 직접 비교해 볼 수 있습니다. 앨범 하나를 45분, 즉 2,700초로 가정합니다. 초당 킬로비트 단위의 비트레이트에 2,700을 곱한 뒤 8,000으로 나누면 메가바이트 단위가 됩니다. 320 kbps의 경우 앨범 한 장당 108 MB가 필요하므로, 앨범 1,000장은 약 108 GB가 됩니다.
무손실 음원은 결과가 달라집니다. 일반적인 음원을 기준으로 CD 품질의 FLAC은 평균 900 kbps 정도이므로, 동일한 앨범 1,000장을 저장하려면 약 304 GB가 필요합니다. 24 bit, 96 kHz 라이브러리는 대략 1,013 GB에 달하며, 종이 한 장에 목록을 적을 수 있는 분량의 컬렉션이 1테라바이트를 차지하게 됩니다. 모바일 기기에 적합한 128 kbps Opus 복사본을 사용하면 동일한 앨범 1,000장을 43 GB 안에 담을 수 있습니다. 요금제를 선택할 때는 본인의 평균 비트레이트가 유일한 기준이 되므로, 현재 보유한 라이브러리에 du -sh /path/to/music을 실행해 보시기 바랍니다.
대역폭은 비용의 작은 부분에 불과합니다. 320 kbps 스트림은 초당 40킬로바이트이므로, 한 시간 동안 음악을 들으면 약 144 MB의 데이터가 소모됩니다. 한 달에 100시간을 듣더라도 약 14 GB 수준이며, 이는 대부분의 VPS 전송 허용량에서 문제 삼지 않는 수준입니다. 단, 휴대폰에 처음으로 오프라인 동기화를 진행할 때는 하룻밤 사이에 수십 기가바이트의 데이터가 전송될 수 있습니다.
스토리지 티어인가, 컴퓨팅 티어인가?
음악 서버는 연산 작업이 거의 없는 방대한 양의 콜드 데이터 저장소입니다. 초당 40 킬로바이트의 속도로 파일을 읽는 작업은 디스크에 거의 부하를 주지 않으며, CPU는 라이브러리 스캔이나 트랜스코딩 중에만 작동하는데 이 작업은 거의 수행할 일이 없습니다. 따라서 컴퓨팅 플랜의 빠른 NVMe는 이 경우 아무런 이점이 없으며, 오히려 기가바이트당 높은 가격 때문에 FLAC 파일을 업로드하는 데 걸림돌이 됩니다. 이러한 상황에서는 스토리지 VPS가 일반 VPS보다 유리합니다. 스토리지 VPS 플랜은 코어 단위가 아닌 테라바이트 단위로 가격이 책정되기 때문입니다.
메모리는 적당한 수준이면 충분합니다. Navidrome은 수백 메가바이트의 메모리로 개인 라이브러리를 서비스할 수 있으며, 재생보다는 스캔 시점에 메모리 사용량이 정점에 달합니다. 서버에 1 GB 또는 2 GB의 RAM을 할당하고, 나머지 예산은 디스크 용량을 확보하는 데 사용하는 것이 좋습니다.
Docker Compose를 이용한 Navidrome 설치
먼저 컨테이너를 실행할 사용자 ID의 소유로 디렉터리를 생성합니다. Compose가 처음이라면 VPS에서의 Docker Compose에서 이 파일이 전제하는 내용을 확인하십시오.
sudo install -d -m 755 -o 1000 -g 1000 /srv/navidrome /srv/musicdocker-compose.yml를 다른 파일과 섞이지 않도록 별도의 디렉터리에 작성합니다.
services:
navidrome:
image: deluan/navidrome:0.63.2
user: "1000:1000"
ports:
- "127.0.0.1:4533:4533"
restart: unless-stopped
environment:
ND_LOGLEVEL: "info"
ND_SESSIONTIMEOUT: "24h"
ND_SCANNER_SCHEDULE: "@every 24h"
ND_BACKUP_PATH: "/data/backup"
ND_BACKUP_SCHEDULE: "0 4 * * *"
ND_BACKUP_COUNT: "7"
volumes:
- /srv/navidrome:/data
- /srv/music:/music:rodocker compose up -d
docker compose psdocker compose ps를 실행했을 때 서비스가 재시작 상태가 아닌 실행 중으로 표시되어야 합니다. 컨테이너가 반복적으로 재시작된다면 거의 항상 /srv/navidrome의 권한 문제입니다. 이때 docker compose logs navidrome는 쓰기에 실패한 파일 이름을 나타냅니다.
해당 파일에서 네 가지 세부 사항은 설명할 가치가 있습니다. 포트는 127.0.0.1에만 게시되므로, 서버는 리버스 프록시를 통해서만 접근할 수 있으며 외부 인터넷에서 4533 포트로 직접 접근할 수 없습니다. Docker는 자체 방화벽 규칙을 작성하므로, ufw에서 모든 접근을 거부하도록 설정했더라도 일반적인 4533:4533은 외부로 노출된 상태로 유지됩니다. 음악 볼륨은 읽기 전용으로 설정되어 있어 스캐너 버그로 인해 유일한 사본이 삭제되는 일을 방지합니다. ND_SCANNER_SCHEDULE은 기본적으로 비활성화되어 있으며, Navidrome 0.55 이전 버전의 가이드에서는 이를 ND_SCANSCHEDULE이라고 부르지만, 현재는 존재하지 않는 이름입니다. 세 가지 백업 설정은 아래 백업 섹션에서 의존하는 내장 데이터베이스 백업 기능을 활성화합니다.
서버에 음악 파일 업로드하기
연결이 끊겨도 처음부터 다시 시작하지 않고 이어서 전송할 수 있는 rsync를 사용하여 라이브러리를 복사합니다.
rsync -av --info=progress2 ~/Music/ user@music.example.com:/srv/music/소스 경로 끝에 붙는 슬래시(/)는 중요합니다. 슬래시를 생략하면 /srv/music/Music가 발생합니다. 복사가 완료되면 소유권을 수정합니다.
sudo chown -R 1000:1000 /srv/music
id -u컨테이너는 사용자 ID 1000으로 실행되며 마운트 지점은 읽기 전용이므로, 모든 파일은 해당 ID로 읽을 수 있어야 합니다. 만약 VPS의 SSH 계정 ID가 1000이 아니라면, 복사된 파일의 소유자가 달라져 스캔 시 트랙을 하나도 찾지 못하게 되고 웹 인터페이스는 아무것도 표시되지 않은 상태로 유지됩니다. id -u은 현재 사용자의 실제 ID를 출력하며, Docker 컨테이너에서 PUID와 PGID가 작동하는 방식에서 매핑에 대한 자세한 설명을 확인할 수 있습니다.
리버스 프록시와 TLS를 통한 어디서든 접속 가능한 환경 구성
VPS를 가리키는 DNS A 레코드를 설정한 뒤, Caddy에 다음 세 줄을 입력합니다.
music.example.com {
reverse_proxy 127.0.0.1:4533
}sudo systemctl reload caddyCaddy는 첫 번째 요청이 들어올 때 인증서를 요청합니다. ACME(automatic certificate management environment) 챌린지는 포트 80에서 응답하므로 포트 80과 443이 모두 열려 있어야 합니다. nginx를 사용하는 경우 location 블록에 proxy_buffering off;을 추가하십시오. Navidrome은 긴 연결을 통해 웹 인터페이스로 진행 상황 이벤트를 전송하는데, 버퍼링이 켜져 있으면 nginx가 응답을 붙잡고 있는 동안 인터페이스가 대기 상태로 머물기 때문입니다. 만약 별도의 서브도메인이 아닌 /music와 같은 경로 아래에서 서비스한다면, ND_BASEURL을 동일한 경로로 설정해야 합니다. 그렇지 않으면 인터페이스가 빈 페이지로 로드됩니다. 세 가지 주요 프록시에 대한 비교는 VPS에서의 Nginx, Caddy, Traefik 비교에서 확인할 수 있습니다.
사이트를 열고 첫 번째 계정을 생성합니다. 기본 비밀번호는 없으며, 첫 방문자에게 관리자 계정 생성을 요청하므로 주소를 공유하기 전에 이 작업을 완료하십시오. 그런 다음 휴대폰 클라이언트가 사용하는 정확한 경로를 테스트합니다.
SALT=$(openssl rand -hex 6)
TOKEN=$(printf '%s%s' 'YOUR_PASSWORD' "$SALT" | md5sum | cut -d' ' -f1)
curl -s "https://music.example.com/rest/ping.view?u=YOUR_USER&t=$TOKEN&s=$SALT&v=1.16.1&c=curl&f=json"정상적인 응답은 {"subsonic-response":{"status":"ok"로 시작하며 서버 유형으로 navidrome을 명시합니다. "status":"failed"와 오류 코드 40이 포함된 본문이 반환된다면 프록시는 정상이며 자격 증명이 잘못된 것입니다. 이 단계에서 인증서 오류가 발생하면 가장 먼저 해결해야 합니다. 대부분의 휴대폰 클라이언트는 잘못된 인증서를 거부할 때 사용자에게 아무런 정보를 주지 않는 메시지를 표시하기 때문입니다.
첫 번째 스캔 후 라이브러리가 잘못 표시되는 이유
Navidrome은 폴더가 아닌 태그를 기준으로 탐색하므로, 태그에 따라 표시되는 내용이 결정됩니다. 'album artist' 태그가 없는 트랙은 'track artist'를 기준으로 분류됩니다. 이 때문에 각 트랙의 아티스트가 다른 컴필레이션 앨범 하나가 트랙당 앨범 하나씩, 총 20개의 앨범으로 나뉘어 표시될 수 있습니다. 이 문제는 Navidrome이 아닌 파일 자체에서 수정해야 합니다. MusicBrainz Picard나 beets를 사용하면 MusicBrainz 데이터베이스에서 앨범 정보를 조회하여 표준 태그를 파일에 기록할 수 있습니다.
또한 Navidrome은 여러 아티스트가 포함된 태그를 개별 아티스트로 분리합니다. 따라서 이름에 구분자가 포함된 밴드는 의도치 않게 분리될 수 있으며, AC/DC가 가장 흔한 사례입니다. ND_SCANNER_ARTISTSPLITEXCEPTIONS에는 분리해서는 안 되는 이름을 지정할 수 있습니다.
새 파일은 파일 시스템에 저장된 후 몇 초 이내에 파일 감시자(file watcher)에 의해 자동으로 인식됩니다. 이 감시자는 커널의 변경 알림에 의존하는데, 다른 장비에서 마운트한 네트워크 공유 드라이브에 기록된 파일은 이 알림이 전달되지 않습니다. 따라서 이러한 환경에서는 주기적인 ND_SCANNER_SCHEDULE 작업을 통해 라이브러리를 최신 상태로 유지해야 합니다. 전체 재스캔은 모든 파일의 태그를 다시 읽어 들이므로 라이브러리 규모가 크면 속도가 느려집니다. 이것이 바로 아래의 데이터베이스를 보호해야 하는 이유 중 하나입니다.
사용자, 재생 목록 및 공유
관리자가 웹 인터페이스에서 다른 계정을 생성하며, 사용자가 직접 가입할 수는 없습니다. 각 사용자는 별도의 재생 횟수, 재생 목록, 즐겨찾기 및 평가를 가지므로, 한 가구 내에서 하나의 취향 프로필을 공유하게 되는 일이 없습니다.
재생 목록은 두 가지 경로로 생성됩니다. 클라이언트에서 만든 재생 목록은 데이터베이스에 저장됩니다. 라이브러리 폴더에 넣은 .m3u 파일은 스캔 과정에서 가져오며, 이는 데스크톱 플레이어에서 재생 목록을 옮기는 가장 쉬운 방법입니다. 스마트 재생 목록은 .nsp 파일로, 동일한 방식으로 가져오는 작은 JSON 규칙 파일이며 라이브러리가 변경됨에 따라 자동으로 업데이트됩니다.
공유 기능은 2026년 7월에 릴리스된 0.63.0 버전부터 기본적으로 활성화되어 있습니다. 이 기능을 사용하면 사용자가 앨범에 대한 공개 링크를 생성하여 로그인 없이 누구나 열 수 있게 할 수 있습니다. 전체 라이브러리를 보관하는 서버에서는 이러한 동작을 원하지 않을 수 있으며, ND_ENABLESHARING을 false로 설정하면 공유 기능을 끌 수 있습니다.
음악 파일과 데이터베이스를 별도로 백업하기
Navidrome 자체 백업 기능은 데이터베이스만 다룹니다. 문서에 명시된 대로, 이 백업 프로세스는 사용자 정보, 재생 횟수 등을 포함한 데이터베이스만 백업하며 음악 파일이나 설정 파일은 포함하지 않습니다. 두 데이터의 장애 유형이 다르므로 이처럼 분리하여 관리하는 것이 올바른 접근 방식입니다. 음악 파일은 원본 소스 디스크에서 다시 복사할 수 있지만, 재생 횟수, 평점, 즐겨찾기, 재생 목록은 다른 곳에 존재하지 않으며 라이브러리를 다시 스캔해도 복구되지 않습니다.
compose 파일은 이미 매일 밤 /data/backup 경로에 사본을 생성하며 7일 치를 보관합니다. 업그레이드 전에는 수동으로 백업을 하나 생성하십시오.
sudo docker compose run --rm navidrome backup create복구 작업은 현재 데이터베이스를 삭제하고 백업 파일을 해당 위치에 복사하는 방식이므로, 반드시 Navidrome을 중지한 상태에서 실행해야 합니다. 운영 중인 서버에서 복구 작업을 수행하는 것은 안전하지 않습니다.
해당 파일들이 여전히 동일한 VPS에 저장되어 있다면 VPS 장애 시 데이터가 소실됩니다. /srv/navidrome 경로의 데이터를 다른 서버나 오브젝트 스토리지로 주기적으로 전송하십시오. 이는 restic 및 BorgBackup이 수행하는 작업입니다. 전체 디렉터리 용량은 보통 1GB 미만으로 매우 작으므로, 매일 암호화된 사본을 외부로 전송하는 것은 비용이 거의 들지 않으면서도 재스캔으로 복구할 수 없는 모든 데이터를 보호하는 가장 확실한 방법입니다.
FAQ
VPS에서 음악을 트랜스코딩해야 합니까?
거의 그럴 필요가 없습니다. 스마트폰과 브라우저는 MP3, AAC, Opus, FLAC을 자체적으로 디코딩하므로, 서버는 파일을 그대로 전송하며 CPU를 거의 사용하지 않습니다. 단 한 가지 예외는 모바일 데이터로 FLAC 라이브러리를 스트리밍할 때입니다. 약 900 kbps인 FLAC을 128 kbps의 Opus로 변환하면 데이터 사용량을 약 7배 줄일 수 있습니다. Navidrome은 사용자별, 플레이어별로 이 기능을 설정할 수 있으며, 직접 활성화하기 전까지는 꺼져 있습니다.
왜 휴대폰 앱에서 오프라인 감상을 위한 음악 다운로드가 안 됩니까?
오프라인 저장은 서버 기능이 아니라 클라이언트 기능이기 때문입니다. Subsonic API는 클라이언트가 전체 파일을 가져올 수 있게 하지만, 휴대폰에 복사본을 저장할지 여부는 앱이 결정합니다. navidrome.org/apps에서 클라이언트 목록을 확인하고 오프라인 다운로드나 캐싱을 지원한다고 명시된 앱을 선택하십시오. 일부 클라이언트는 이미 재생한 곡만 캐시하며, 이는 여행 전 앨범을 미리 동기화하는 것과는 다릅니다.
왜 스캔 후 하나의 앨범이 여러 앨범으로 나뉘었습니까?
트랙 간에 앨범 아티스트 태그가 누락되었거나 일치하지 않기 때문입니다. Navidrome은 폴더가 아닌 태그를 기준으로 그룹화합니다. 따라서 아티스트 값이 모두 다르고 공통된 앨범 아티스트 값이 없는 12개의 트랙은 12개의 앨범으로 인식됩니다. 앨범의 모든 트랙에 앨범 아티스트 태그를 설정하십시오(컴필레이션 앨범의 경우 보통 Various Artists로 설정). 그 후 다시 스캔하면 됩니다. MusicBrainz Picard나 beets를 사용하면 폴더 전체를 한 번에 수정할 수 있습니다.
자가 호스팅 음악 스트리밍이 Spotify를 대체할 수 있습니까?
플레이어와 라이브러리를 대체할 수는 있지만, 카탈로그를 대체할 수는 없습니다. 라이선스 정책 변경으로 사라질 염려가 없는 나만의 재생 목록과 재생 횟수를 모든 기기에서 유지할 수 있습니다. 하지만 신곡을 자동으로 받을 수 없으며, 다른 사람들의 청취 기록을 기반으로 한 추천 기능도 없습니다. 이 서비스를 운영하는 대부분의 사용자는 음악을 직접 구매하고, 새로운 음악을 발견하기 위한 용도로 저렴한 스트리밍 계정을 병행하여 사용합니다.