SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor

วิธีติดตั้ง Jellyfin บน VPS ด้วย Docker ให้ใช้งานได้จริง

เรียนรู้วิธีติดตั้ง Jellyfin บน VPS ผ่าน Docker พร้อมตั้งค่า block storage และจัดการสิทธิ์ไฟล์ให้ถูกต้อง ป้องกันปัญหา transcoding หนักเกิน CPU และวิธีเข้าถึงสื่ออย่างปลอดภัย

สิ่งที่คุณกำลังสร้าง

การติดตั้ง Jellyfin media server บน VPS: ประกอบด้วยหนึ่งคอนเทนเนอร์, สามโวลุ่ม และดิสก์ block-storage สำหรับเก็บภาพยนตร์และรายการทีวีของคุณ ซึ่งสามารถเข้าถึงได้จากเบราว์เซอร์หรือแอป Jellyfin ใดๆ การติดตั้งนี้ใช้ไฟล์ compose เพียง 15 บรรทัด ปัญหาที่อาจเกิดขึ้นหลังจากนั้นมักมาจากสองสาเหตุหลัก คือสิทธิ์การเข้าถึงไฟล์ (file permissions) ที่คอนเทนเนอร์ไม่สามารถอ่านได้ และการสั่งให้ VPS ที่ไม่มี GPU ทำการแปลงรหัสวิดีโอ (transcoding) ซึ่งเกินขีดความสามารถของระบบ คู่มือนี้จะเน้นเนื้อหาส่วนใหญ่ไปที่สองประเด็นนี้ เนื่องจากเป็นจุดที่มักเกิดปัญหาและต้องมีการสนับสนุนทางเทคนิคบ่อยที่สุด

Jellyfin เป็นซอฟต์แวร์ฟรีและโอเพนซอร์สเต็มรูปแบบ โดยไม่มีการบังคับสร้างบัญชี ไม่มีฟีเจอร์ที่ต้องจ่ายเงินเพื่อปลดล็อก และไม่มีการเก็บข้อมูลการใช้งาน (telemetry) ซึ่งเป็นเหตุผลที่ทำให้มันติดอันดับในเกือบทุกรายการของ สิ่งที่ควรทำ self-hosting ในปี 2026 มันทำหน้าที่เล่นสื่อที่คุณเป็นเจ้าของเท่านั้น ตัวซอฟต์แวร์ไม่ได้มาพร้อมกับเนื้อหาใดๆ และคู่มือนี้ไม่ได้มีเนื้อหาเกี่ยวกับการจัดหาเนื้อหาเหล่านั้น

ความเป็นจริงของการแปลงรหัส (transcoding) ก่อนที่คุณจะเช่าบริการใดๆ

โปรดอ่านส่วนนี้ก่อน เพราะมันจะเปลี่ยนสิ่งที่คุณตัดสินใจซื้อ Media server จะทำงานอย่างใดอย่างหนึ่งเมื่อคุณกดเล่น Direct play คือการสตรีมไฟล์ตามสภาพเดิม: VPS จะอ่านไบต์จากดิสก์แล้วส่งผ่านเครือข่าย ซึ่งแทบไม่ใช้ CPU เลย ส่วน Transcoding คือการแปลงรหัสวิดีโอในขณะนั้น ไม่ว่าจะเป็นการเปลี่ยนความละเอียด, เปลี่ยน codec หรือการฝังคำบรรยายลงในวิดีโอ ซึ่งเป็นงานที่ใช้ CPU สูงมาก

VPS ทั่วไปไม่มี GPU ดังนั้นการแปลงรหัสวิดีโอทุกครั้งจะทำงานบน CPU ด้วย libx264/libx265 ซึ่งการเข้ารหัสด้วยซอฟต์แวร์นั้นสิ้นเปลืองทรัพยากรมาก การแปลงรหัสวิดีโอ 1080p H.264 เพียงรายการเดียวอาจทำให้ vCPU แบบแบ่งใช้งานหลายตัวทำงานเต็มขีดจำกัด ส่วนการแปลงรหัส 4K หรือ HEVC มักจะไม่สามารถประมวลผลได้ทันเวลาจริง ส่งผลให้การเล่นวิดีโอสะดุดและบัฟเฟอร์ไม่สิ้นสุด การแปลงรหัสด้วยฮาร์ดแวร์ (Hardware transcoding) ซึ่งเป็นสิ่งที่ทำให้การใช้งานบนเครื่องที่บ้านที่มี Intel iGPU หรือการ์ดจอ Nvidia มีราคาถูกนั้น จะไม่สามารถใช้งานได้บน VPS เว้นแต่ผู้ให้บริการของคุณจะให้เช่า GPU instance โดยเฉพาะ

ดังนั้นกลยุทธ์ทั้งหมดบน VPS คือ: หลีกเลี่ยงการแปลงรหัส ให้เก็บคลังสื่อของคุณในรูปแบบ codec ที่ไคลเอนต์เล่นได้โดยตรง (native) เช่น วิดีโอ H.264, เสียง AAC หรือ AC3 ในคอนเทนเนอร์ MP4 หรือ MKV และเลือกใช้แอปไคลเอนต์ที่รองรับ Direct play เช่น แอป Jellyfin บน Android TV, iOS และ Roku รวมถึง Infuse, Kodi และ Jellyfin Media Player บนเดสก์ท็อป หากทำเช่นนี้ VPS จะไม่ต้องรัน ffmpeg เพื่อแปลงรหัสระหว่างการเล่น และเครื่องที่มีสเปกเพียง 2 vCPU ก็สามารถสตรีมให้หลายคนพร้อมกันได้ หากคุณวางแผนจะแปลงรหัส คุณจำเป็นต้องใช้เครื่องที่ใหญ่ขึ้นและแพงขึ้นมาก และถึงอย่างนั้นการสตรีม 4K ก็ยังมีความเสี่ยงสูง

นอกจากนี้ ให้คำนวณเรื่องแบนด์วิดท์ด้วย เพราะเป็นอีกหนึ่งสิ่งที่มักจะคาดไม่ถึง การทำ Direct play จะส่งไฟล์ด้วยบิตเรตของไฟล์นั้นๆ ไฟล์ 1080p ที่บีบอัดมาจะใช้ความเร็ว 8-12 Mbps, 1080p Blu-ray remux ใช้ 20-30 Mbps และ 4K HDR ใช้ 40-80 Mbps หากมีคน 3 คนกำลัง Direct play ไฟล์ขนาด 10 Mbps พร้อมกัน จะเท่ากับมีการใช้ความเร็วอัปโหลดต่อเนื่องจาก VPS ของคุณถึง 30 Mbps ให้ตรวจสอบตัวเลขสองค่าในแผนบริการของคุณ: ความเร็วพอร์ต (รองรับการอัปโหลด 30 Mbps ได้หรือไม่?) และโควตาการถ่ายโอนข้อมูลรายเดือน ภาพยนตร์ความยาว 2 ชั่วโมงที่บิตเรต 10 Mbps จะใช้ข้อมูลประมาณ 9 GB ดังนั้นหากคุณมีโควตา 1 TB ต่อเดือน คุณจะดูหนังได้ประมาณร้อยกว่าเรื่องต่อเดือน หรือวันละ 3-4 เรื่อง แต่หากเป็นครัวเรือนที่ดู 4K ซึ่งใช้บิตเรตสูงกว่า 4 ถึง 8 เท่า โควตาจะหมดเร็วขึ้นมาก อย่าลืมนำกิจกรรมอื่นที่ใช้การรับส่งข้อมูลจากเครื่องเดียวกันมาคำนวณรวมในงบประมาณนี้ด้วย รวมถึง การทำ self-hosted RustDesk relay ซึ่งจะรับส่งข้อมูลตลอดทั้งเซสชันของรีโมทเดสก์ท็อปในกรณีที่อุปกรณ์สองเครื่องไม่สามารถเชื่อมต่อกันโดยตรงได้

ข้อกำหนดเบื้องต้น

  • VPS ประเภท KVM ที่ใช้ Ubuntu 24.04 แบบติดตั้งใหม่ พร้อมสิทธิ์ root หรือ sudo และติดตั้ง Docker รวมถึงปลั๊กอิน Compose เรียบร้อยแล้ว
  • พื้นที่จัดเก็บข้อมูลแบบ block-storage สำหรับสื่อบันเทิง โดยกำหนดขนาดให้เหมาะสมกับคลังข้อมูลของคุณ (ดูรายละเอียดการคำนวณขนาดด้านล่าง) ดิสก์หลักขนาดเล็กที่มาพร้อมกับ VPS ไม่ใช่ที่สำหรับเก็บไฟล์ภาพยนตร์ของคุณ
  • ชื่อโดเมนหากคุณต้องการเข้าถึงผ่าน HTTPS แบบสาธารณะ หรือใช้ WireGuard VPN บน VPS เดียวกัน หากคุณต้องการเก็บทุกอย่างไว้เป็นส่วนตัว
  • สื่อบันเทิงที่คุณมีสิทธิ์ในการสตรีมอย่างถูกต้องตามกฎหมาย เช่น ไฟล์ที่คุณคัดลอกเอง, ไฟล์ที่คุณบันทึกเอง หรือไฟล์ที่คุณเป็นเจ้าของ

การเมานต์บล็อกสตอเรจก่อน

เชื่อมต่อโวลุ่มผ่านแผงควบคุมของผู้ให้บริการ จากนั้นค้นหาและเมานต์โวลุ่มดังกล่าว ตรวจสอบชื่ออุปกรณ์จาก lsblk ซึ่งมักจะเป็นชื่อในรูปแบบ /dev/sdb หรือ /dev/vdb โดยต้องไม่ใช่ดิสก์หลัก (root disk)

lsblk
sudo mkfs.ext4 /dev/sdb          # ONLY on a new, empty volume: this ERASES it
sudo mkdir -p /mnt/media
sudo blkid /dev/sdb              # copy the UUID shown for this device

ให้เมานต์โดยใช้ UUID แทนการใช้ /dev/sdb เนื่องจากลำดับตัวอักษรของอุปกรณ์อาจเปลี่ยนแปลงได้หลังจากรีบูต ซึ่งอาจส่งผลให้คุณฟอร์แมตหรือเมานต์ดิสก์ผิดลูก ให้เพิ่มบรรทัดลงใน /etc/fstab ดังนี้:

UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  /mnt/media  ext4  defaults,nofail  0  2
sudo mount -a
df -h /mnt/media

nofail มีความสำคัญมาก หากไม่มีค่านี้ เมื่อมีการถอดบล็อกโวลุ่มออก เซิร์ฟเวอร์จะไม่สามารถบูตเข้าสู่ระบบได้และจะเข้าสู่โหมด emergency shell ทันที ข้อผิดพลาดที่พบบ่อยที่สุดคือการรันคำสั่ง mkfs.ext4 บนโวลุ่มที่มีข้อมูลอยู่แล้ว ซึ่งจะทำให้ข้อมูลทั้งหมดถูกลบ ให้ฟอร์แมตเฉพาะโวลุ่มใหม่เท่านั้น หากดิสก์มีข้อมูลของคุณอยู่แล้ว ให้ข้ามไปที่ขั้นตอนการเพิ่มบรรทัดใน fstab ได้เลย

จัดระเบียบสื่อให้ตรงตามรูปแบบที่ Jellyfin ต้องการ

Jellyfin จะจับคู่ข้อมูลเมตา (metadata) ตามชื่อโฟลเดอร์และชื่อไฟล์ หากจัดระเบียบไม่ถูกต้อง ภาพยนตร์จะปรากฏเป็นไฟล์ที่ไม่มีชื่อและไม่มีโปสเตอร์ หรือตอนของซีรีส์อาจถูกจับคู่ผิดเรื่อง มีหลักการ 4 ประการที่จะช่วยให้การทำงานส่วนใหญ่สำเร็จลุล่วง: ภาพยนตร์แต่ละเรื่องควรอยู่ในโฟลเดอร์ของตนเองโดยใช้ชื่อไฟล์ที่ตรงกันในรูปแบบ Name (Year) (Jellyfin ถือว่าปีที่ฉายไม่บังคับ แต่การระบุจะช่วยให้การจับคู่แม่นยำขึ้นมาก); โฟลเดอร์ของซีซันควรตั้งชื่อว่า Season 01 ไม่ใช่ S01; ไฟล์ตอนของซีรีส์ควรใช้รูปแบบ S01E01; และตอนพิเศษ (specials) ควรเก็บไว้ใน Season 00

/mnt/media
├── Movies
│   ├── Blade Runner (1982)
│   │   └── Blade Runner (1982).mkv
│   └── Arrival (2016)
│       └── Arrival (2016).mkv
└── Shows
    └── Severance (2022)
        ├── Season 01
        │   ├── Severance - S01E01.mkv
        │   └── Severance - S01E02.mkv
        └── Season 00
            └── Severance - The Lexington Letter.mkv

ส่วน (Year) ในชื่อภาพยนตร์ไม่ใช่แค่การตกแต่ง แต่เป็นการระบุความแตกต่างของภาพยนตร์ที่นำกลับมาสร้างใหม่ เพื่อให้ระบบจับคู่ดึงชื่อเรื่องที่ถูกต้องมาแสดง ควรแยก Movies และ Shows ไว้เป็นโฟลเดอร์ระดับบนสุดแยกจากกัน เนื่องจากแต่ละโฟลเดอร์จะกลายเป็นคลังข้อมูล (library) ของ Jellyfin ที่มีประเภทเนื้อหาเฉพาะเจาะจง การนำมารวมกันจะทำให้ผู้ให้บริการข้อมูลเมตาสับสน ทั้งนี้ Jellyfin สามารถจัดทำดัชนีโฟลเดอร์ที่สามสำหรับรูปภาพได้เช่นกัน แต่ประสบการณ์การใช้งานจะจำกัดเมื่อเทียบกับเซิร์ฟเวอร์ที่สร้างมาเพื่อจัดการรูปภาพโดยเฉพาะ ดังนั้นหากอัลบั้มภาพของคุณมีความสำคัญ ควรแยกไปใช้เซิร์ฟเวอร์เฉพาะทางอย่าง PhotoPrism หรือ Immich และปล่อยให้เซิร์ฟเวอร์นี้จัดการเฉพาะภาพยนตร์และรายการโทรทัศน์เท่านั้น

สิทธิ์การเข้าถึง: สาเหตุอันดับหนึ่งที่ทำให้คลังข้อมูลว่างเปล่า

นี่คือความเข้าใจผิดที่ทำให้หลายคนเสียเวลาไปทั้งเย็น ภาพลักษณ์ jellyfin/jellyfin อย่างเป็นทางการ ไม่ รองรับตัวแปรสภาพแวดล้อม PUID/PGID เนื่องจากตัวแปรดังกล่าวเป็นของอิมเมจจาก LinuxServer.io (lscr.io/linuxserver/jellyfin) สำหรับอิมเมจอย่างเป็นทางการ คุณสามารถควบคุมผู้ใช้งานได้ด้วยคีย์ user: ในไฟล์ compose และหากคุณละเว้นคีย์นี้ คอนเทนเนอร์จะทำงานในฐานะ root ไม่ว่าคุณจะใช้วิธีใด กฎยังคงเหมือนเดิมคือ uid/gid ที่คอนเทนเนอร์ใช้ทำงานต้องสามารถอ่านและเข้าถึงทุกไดเรกทอรีสื่อได้

เราจะรันด้วย uid/gid 1000 ซึ่งเป็นผู้ใช้งานที่ไม่ใช่ root คนแรกบน Ubuntu รุ่นมาตรฐาน ให้ตรวจสอบค่าของคุณและตั้งค่าความเป็นเจ้าของดังนี้:

id                                  # confirm your user is uid=1000 gid=1000
sudo chown -R 1000:1000 /mnt/media
sudo find /mnt/media -type d -exec chmod 755 {} \;
sudo find /mnt/media -type f -exec chmod 644 {} \;
mkdir -p ~/jellyfin/config ~/jellyfin/cache
sudo chown -R 1000:1000 ~/jellyfin

ไดเรกทอรีจำเป็นต้องมีบิต execute (คือ x ใน 755) ไม่ใช่แค่สิทธิ์การอ่าน หากไม่มีบิตนี้ คอนเทนเนอร์จะไม่สามารถเข้าไปในโฟลเดอร์ได้แม้ว่าจะแสดงรายชื่อไฟล์ได้ก็ตาม กับดักที่ทำให้คลังข้อมูลว่างเปล่าทั้งคลังคือไดเรกทอรีแม่: หาก uid ของคอนเทนเนอร์ไม่สามารถเข้าถึงจุด mount ได้ มันก็จะไม่สามารถไปถึง /media/Movies หรือ /media/Shows ได้เลย และคลังข้อมูลทั้งหมดจะว่างเปล่าทันทีพร้อมกับข้อความ Access to the path ... is denied ใน log โฟลเดอร์สื่อใดก็ตามที่อ่านไม่ได้จะถูกบันทึกไว้และข้ามไป ดังนั้นไฟล์จำนวนมากที่คัดลอกเข้ามาในฐานะ root จะหายไปจากคลังข้อมูลโดยไม่มีการแจ้งเตือน นี่คือเหตุผลที่เราต้องใช้ chown แบบ recursive และตั้งค่าบิต execute บนทุกไดเรกทอรี แทนที่จะแก้ไขเพียงโฟลเดอร์เดียว

ไฟล์ docker-compose

services:
  jellyfin:
    image: jellyfin/jellyfin:12
    container_name: jellyfin
    user: "1000:1000"
    restart: unless-stopped
    ports:
      - "127.0.0.1:8096:8096"
    volumes:
      - ./config:/config
      - ./cache:/cache
      - /mnt/media:/media:ro
    environment:
      - JELLYFIN_PublishedServerUrl=https://jellyfin.example.com

อธิบายทีละบรรทัด: jellyfin/jellyfin:12 ติดตาม release line ปัจจุบันเวอร์ชัน 12.x (ณ วันที่ 2026-10-10 คือ 12.2) user: "1000:1000" กำหนด uid/gid ที่กระบวนการ Jellyfin จะรันด้วย ค่านี้ไม่ได้เปลี่ยนความเป็นเจ้าของหรือโหมดของไฟล์ใดๆ เนื่องจาก Docker จะคงค่า uid/gid ของโฮสต์ไว้บนไดเรกทอรีที่ mount มา นี่คือเหตุผลที่ chown ด้านบนต้องตรงกัน /config เก็บข้อมูลทั้งเซิร์ฟเวอร์, บัญชีผู้ใช้, คลังสื่อ, ข้อมูลเมตา และสถานะการรับชม ดังนั้นจึงต้องเขียนข้อมูลได้และเป็นส่วนที่คุณต้องสำรองข้อมูลไว้ /cache เป็นพื้นที่ทำงานชั่วคราว การ mount คลังสื่อเป็น :ro (แบบอ่านได้อย่างเดียว) นั้นมีจุดประสงค์ เพราะ Jellyfin มีค่าเริ่มต้นในการเก็บรูปภาพและข้อมูลเมตาไว้ภายใต้ /config จึงไม่จำเป็นต้องเขียนข้อมูลลงในคลังสื่อของคุณ และการตั้งค่าแบบอ่านได้อย่างเดียวจะช่วยป้องกันไฟล์ของคุณจากการถูกลบโดยไม่ตั้งใจหรือจากปลั๊กอินที่ทำงานผิดพลาด พอร์ตถูกผูกไว้กับ 127.0.0.1 โดยเจตนา เนื่องจากหน้าล็อกอินเว็บของ Jellyfin เป็น HTTP ธรรมดา เราจึงไม่เปิดพอร์ต 8096 สู่สาธารณะ JELLYFIN_PublishedServerUrl คือที่อยู่ที่เซิร์ฟเวอร์ใช้ประกาศสำหรับการค้นหาอัตโนมัติในเครือข่ายท้องถิ่น (LAN UDP broadcast) ดังนั้นไคลเอนต์ที่อยู่ผ่านอินเทอร์เน็ตจะไม่เห็นค่านี้และจะใช้ URL ที่คุณพิมพ์ลงในแอปโดยตรง ให้ตั้งค่าเป็นที่อยู่ที่ต้องการให้ไคลเอนต์รับทราบ และเตรียมตัวที่จะต้องกรอก URL นั้นด้วยตนเองบนอุปกรณ์ระยะไกล

เริ่มการทำงานจากไดเรกทอรี compose:

docker compose up -d
docker logs -f jellyfin

การรันครั้งแรก: วิซาร์ดการตั้งค่าและไลบรารีของคุณ

เนื่องจากพอร์ตถูกผูกไว้กับ localhost ให้เข้าถึงวิซาร์ดผ่าน SSH tunnel จากแล็ปท็อปของคุณ แทนการเปิดช่องโหว่บนไฟร์วอลล์:

ssh -L 8096:127.0.0.1:8096 you@your-vps-ip

จากนั้นเปิดเบราว์เซอร์ไปที่ http://localhost:8096 วิซาร์ดจะแนะนำคุณตั้งแต่การเลือกภาษา ไปจนถึงการสร้าง admin user ด้วยรหัสผ่านที่คาดเดายาก บัญชีนี้คือเซิร์ฟเวอร์ของคุณ ดังนั้นห้ามใช้รหัสผ่านที่ใช้แล้วทิ้ง เพิ่มไลบรารีแรกของคุณ: เลือกประเภทเนื้อหาเป็น Movies แล้วชี้ไปที่ /media/Movies (path ภายใน คอนเทนเนอร์ ไม่ใช่ path บนโฮสต์) และทำซ้ำสำหรับ Shows ที่ /media/Shows เมื่อเสร็จสิ้น Jellyfin จะเริ่มสแกน ผลลัพธ์ที่ถูกต้องคือโปสเตอร์และชื่อเรื่องจะปรากฏขึ้นภายในหนึ่งหรือสองนาทีสำหรับไลบรารีขนาดเล็ก คุณสามารถเพิ่มหรือแก้ไขไลบรารีภายหลังได้ที่ Dashboard → Libraries และบังคับให้สแกนใหม่ด้วย Scan All Libraries โปสเตอร์เหล่านั้นยังเป็นสิ่งที่ทำให้ frontend ทางเลือกน่าสนใจเมื่อตั้งค่าพื้นฐานเสร็จแล้ว เนื่องจาก Halcyon สร้างไลบรารีเดียวกันขึ้นใหม่ให้เป็นร้านวิดีโอในยุค 90 ที่เดินเลือกชมได้ จากงานศิลป์ที่ Jellyfin ดึงข้อมูลมาไว้แล้ว

หากคุณจำเป็นต้องใช้การ transcoding ให้เปิด Dashboard → Playback → Transcoding และตั้งค่า transcode temp path ไปที่ /cache/transcodes เพื่อให้ข้อมูลชั่วคราวถูกเขียนลงบน cache volume แทนที่จะทำให้ /config เต็ม ให้คงค่า hardware acceleration ไว้ที่ None เนื่องจากไม่มี GPU สำหรับใช้เร่งความเร็ว

การเข้าถึงจากระยะไกล: TLS reverse proxy หรือเก็บไว้ใน VPN

คุณมีวิธีที่ปลอดภัย 2 วิธีในการเข้าถึง Jellyfin จากภายนอก และมีวิธีที่ไม่ปลอดภัยหนึ่งวิธีที่ควรหลีกเลี่ยง วิธีที่ไม่ปลอดภัยคือการเปิดพอร์ต 8096 ออกสู่สาธารณะโดยตรง เนื่องจากข้อมูลการเข้าสู่ระบบจะถูกส่งเป็นข้อความธรรมดา (cleartext) และพอร์ตจะถูกโจมตีแบบ brute-force ภายในเวลาไม่กี่ชั่วโมง

ทางเลือก A, TLS reverse proxy ให้วาง Jellyfin ไว้บน subdomain หลัง Traefik พร้อม TLS อัตโนมัติสำหรับแอป Docker ของคุณ หรือหลัง nginx ที่ใช้ ใบรับรอง Let's Encrypt ที่ออกโดย Certbot Jellyfin ใช้ WebSockets สำหรับการอัปเดตแบบเรียลไทม์ ดังนั้น proxy ต้องส่งต่อ upgrade headers ไปด้วย Traefik จะจัดการส่วนนี้โดยอัตโนมัติ ส่วน nginx จำเป็นต้องระบุค่าเหล่านี้ให้ชัดเจน และต้องใช้ HTTP/1.1 ไปยัง upstream มิฉะนั้นการอัปเกรดจะไม่เกิดขึ้น:

location / {
    proxy_pass http://127.0.0.1:8096;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

ตั้งค่า JELLYFIN_PublishedServerUrl ให้เป็นที่อยู่ https:// เพื่อให้ระบบ autodiscovery ภายในแจ้ง URL ที่ถูกต้อง แอปจากระยะไกลจะใช้ที่อยู่ที่คุณกำหนด และควรเพิ่ม fail2ban เพื่อชะลอการพยายาม brute-force หน้าเข้าสู่ระบบ เมื่อเซิร์ฟเวอร์เปิดใช้งานต่อสาธารณะแล้ว ให้ใช้ Uptime Kuma ตรวจสอบ URL นั้น เพื่อให้คุณทราบถึงปัญหา downtime ก่อนที่ผู้ชมของคุณจะพบเสียเอง ให้ตั้งค่าการแจ้งเตือนไปยัง เซิร์ฟเวอร์ ntfy ที่คุณโฮสต์เอง เพื่อให้การตรวจสอบที่ล้มเหลวส่งเป็นข้อความแจ้งเตือนเข้ามือถือ แทนที่จะเป็นอีเมลที่คุณเพิ่งมาอ่านในเช้าวันถัดไป การเปิดหน้าเข้าสู่ระบบบนอินเทอร์เน็ตสาธารณะเป็นช่วงเวลาที่ดีในการตรวจสอบความปลอดภัยของเซิร์ฟเวอร์ส่วนที่เหลือ แทนที่จะเชื่อใจเพียงแค่พอร์ต 443 เท่านั้น: sudo ss -tlnp จะแสดงรายการพอร์ตที่กำลังฟังอยู่ทั้งหมดและ process ที่อยู่เบื้องหลัง หากคุณมีการ deploy โค้ดของคุณเองบนเซิร์ฟเวอร์นี้ open-kritt จะสแกน repository และ dependency เพื่อหาช่องโหว่ ซึ่งเป็นการตรวจสอบ source code ไม่ใช่พอร์ตที่เปิดใช้งาน

ทางเลือก B, เก็บไว้เป็นส่วนตัวบน VPN ห้ามเปิดพอร์ต 8096 ออกสู่อินเทอร์เน็ตสาธารณะโดยเด็ดขาด ให้เข้าถึง Jellyfin ผ่าน WireGuard tunnel ที่สิ้นสุดที่เซิร์ฟเวอร์เดียวกันเท่านั้น สำหรับการใช้งานในครัวเรือน นี่เป็นทางเลือกที่ปลอดภัยและง่ายที่สุด ไม่ต้องใช้ใบรับรอง ไม่มีการเปิดเผยต่อสาธารณะ และไม่มีพื้นผิวให้โจมตีแบบ brute-force การผูกพอร์ต (binding) กับ localhost ในไฟล์ compose นั้นไม่เพียงพอในกรณีนี้ เพราะพอร์ตที่เปิดบน 127.0.0.1 จะเข้าถึงได้จากตัว VPS เท่านั้น ไม่ใช่จาก VPN peers ให้เปิดพอร์ตบนที่อยู่ tunnel ของเซิร์ฟเวอร์แทน (ตัวอย่างเช่น "10.8.0.1:8096:8096" โดยใช้ IP ของเซิร์ฟเวอร์ WireGuard ของคุณเอง) หรือคงค่า localhost ไว้แล้ววาง proxy ที่ฟังบนที่อยู่ tunnel ไว้ด้านหน้า จากนั้นจึงเชื่อมต่อผ่าน VPN ดูรายละเอียดเพิ่มเติมได้ที่ การตั้งค่า WireGuard VPN สำหรับ VPS ส่วนตัว เพื่อสร้าง tunnel ดังกล่าว

การคำนวณขนาดพื้นที่จัดเก็บและการสำรองข้อมูล

ให้จัดสรรงบประมาณตามคุณภาพของไฟล์ ไม่ใช่จำนวนไฟล์ ภาพยนตร์ความละเอียด 1080p แบบบีบอัดจะมีขนาด 4-15 GB ต่อเรื่อง, 1080p remux มีขนาด 20-40 GB, ซีรีส์ความละเอียด 1080p หนึ่งซีซันมีขนาด 15-40 GB และเนื้อหาความละเอียด 4K ทุกประเภทจะมีขนาด 40-100 GB ต่อเรื่อง คลังสื่อที่มีภาพยนตร์หลายร้อยเรื่องรวมกับซีรีส์จำนวนหนึ่งควรใช้พื้นที่ขนาด 2-4 TB การจัดสรรพื้นที่ block volume ให้เกินความจำเป็นไว้ตั้งแต่ต้นนั้นประหยัดกว่าการย้ายข้อมูลในภายหลัง

/config คือสถานะทั้งหมดของเซิร์ฟเวอร์ ดังนั้นจึงเป็นสิ่งเดียวที่คุณต้องสำรองข้อมูล ให้ทำ snapshot หรือหยุดการทำงานแล้วใช้ tar เพื่อสำรองข้อมูล และเก็บสำเนาไว้ภายนอกเครื่อง:

docker compose down
sudo tar czf jellyfin-config-$(date +%F).tgz -C ~/jellyfin config
docker compose up -d

/cache และโฟลเดอร์ transcode เป็นข้อมูลที่ทิ้งได้ ส่วนสื่อที่อยู่ใน /mnt/media คุณควรสำรองข้อมูลแยกต่างหากหรือยอมรับว่าสามารถ rip ใหม่ได้ ซึ่งคนส่วนใหญ่เลือกวิธีหลังเนื่องจากขนาดไฟล์ที่ใหญ่ การอัปเกรดเป็น docker compose pull && docker compose up -d; tag :12 ด้านบนจะคงอยู่ในสาย 12.x ดังนั้นการขยับไปเวอร์ชันหลักถัดไปเป็นการแก้ไข tag โดยเจตนา โปรดอ่านบันทึกการปล่อยรุ่นของ Jellyfin ก่อนดำเนินการ เพราะการย้าย schema ของคลังข้อมูลมักเกิดขึ้นในเวอร์ชันหลัก Jellyfin ได้ตัดเลข "10." นำหน้าออกจากเลขเวอร์ชันตั้งแต่รุ่น 12.0 (กันยายน 2026): 12.0 คือสิ่งที่ควรจะเป็น 10.12 โดยไม่มีรุ่น 11.x และ 10.11.x เป็นรุ่นสุดท้ายของแผนผังเดิม หากไฟล์ compose เก่าของคุณยังระบุ jellyfin/jellyfin:10 แสดงว่ามันถูกตรึงไว้ที่ 10.11 (tag นั้นอัปเดตครั้งล่าสุดเมื่อ 2026-06-06 เป็น 10.11.11) และจะไม่ดึงรุ่น 12.x มาใช้งาน การเปลี่ยนไปใช้ :12 ไม่ใช่แค่การแก้ไข tag: 12.0 มีการเปลี่ยนแปลง database schema และเขียนข้อมูลใหม่ในการบูตครั้งแรก ดังนั้นให้หยุดเซิร์ฟเวอร์และสำรองข้อมูลตามขั้นตอนข้างต้นก่อน เพราะการสำรองข้อมูลนั้นเป็นวิธีเดียวที่จะย้อนกลับได้ การอัปเกรดเป็น 12.0 สามารถทำได้โดยตรงจาก 10.10.7 หรือรุ่น 10.11.x ใดๆ และจำเป็นต้องทำการสแกนคลังข้อมูลใหม่ทั้งหมดหลังจากนั้น ซึ่งจะใช้เวลานานกว่าปกติอย่างเห็นได้ชัด การใช้ tag ที่ระบุเวอร์ชันชัดเจนควบคู่กับการสำรองไดเรกทอรีสถานะคือสูตรสำเร็จสำหรับ container ที่ต้องทำงานตลอดเวลา และเป็นรูปแบบเดียวกันกับที่ใช้ใน การรักษาหน่วยความจำและตารางเวลาของ agent ที่ self-host ไว้ให้คงอยู่หลังการรีบูต

รูปแบบความล้มเหลวและข้อความที่คุณจะพบ

คลังข้อมูลว่างเปล่าหลังจากการสแกน บันทึกที่ Dashboard → Logs (หรือ ~/jellyfin/config/log/log_*.log) แสดงข้อความดังนี้:

System.UnauthorizedAccessException: Access to the path '/media/Movies' is denied.

UID ของคอนเทนเนอร์ไม่สามารถอ่านพาธดังกล่าวได้ สาเหตุ: สื่อข้อมูลถูกครอบครองโดย root หรือ UID อื่นที่ไม่ใช่ค่า user: ของคุณ, ไดเรกทอรีขาดสิทธิ์ execute bit หรือตัว mount point หลักเองที่ UID นั้นไม่สามารถเข้าถึงได้ วิธีแก้ไข: ใช้ chown -R 1000:1000 /mnt/media, ตั้งค่าไดเรกทอรีเป็น 755, ตั้งค่าไฟล์เป็น 644 แล้วทำการสแกนใหม่อีกครั้ง

การเล่นไฟล์ทำให้ CPU ทำงานหนักและเกิดการบัฟเฟอร์ docker stats jellyfin แสดงค่า CPU ใกล้ 100% คูณด้วยจำนวนคอร์ของคุณ และ Dashboard → Playback ระบุเซสชันว่าเป็น Transcode โดยมีความเร็วต่ำกว่า 1.0x ไคลเอนต์ไม่ได้ทำการ direct-play ส่งผลให้ VPS ต้องทำ CPU-transcoding ซึ่งช้ากว่าเวลาจริงและทำให้การเล่นสะดุด สาเหตุ: codec หรือคอนเทนเนอร์ไม่รองรับ, การฝังซับไตเติลลงในวิดีโอ (burn-in) หรือการทำ HDR tone-mapping วิธีแก้ไข: เปลี่ยนไปใช้ไคลเอนต์ที่รองรับ direct-play, เก็บไฟล์ต้นฉบับในรูปแบบ H.264/AAC, ใช้ซับไตเติลแบบ text (SRT) แทนซับไตเติลแบบ image (PGS/VOBSUB) ซึ่งไคลเอนต์ Jellyfin ส่วนใหญ่ไม่สามารถแสดงผลเองได้และบังคับให้ต้องทำ burn-in และหลีกเลี่ยงการเล่น 4K HDR บนเครื่องที่ใช้ CPU เพียงอย่างเดียว

"No compatible streams are available." ข้อความเต็มมักจะเป็น "This client isn't compatible with the media and the server isn't sending a compatible media format." ไคลเอนต์ปฏิเสธไฟล์ต้นฉบับและกระบวนการ transcode สำรองก็ไม่สามารถเริ่มทำงานได้ สาเหตุ: คำสั่ง ffmpeg ผิดพลาด, ไฟล์ไม่สามารถอ่านได้ หรือโปรไฟล์ของผู้ใช้บล็อกการแปลงวิดีโอ วิธีแก้ไข: อ่านบรรทัดคำสั่ง ffmpeg ใน Dashboard → Logs, ตรวจสอบว่าไฟล์สามารถเล่นได้จริง, ตรวจสอบสิทธิ์การเล่นของผู้ใช้หากคุณจำเป็นต้องพึ่งพาการ transcoding และลองใช้ไคลเอนต์อื่นเพื่อตัดปัญหาเรื่องความเข้ากันได้ของ codec ในเบราว์เซอร์

ภาพยนตร์ไม่มีโปสเตอร์หรือแสดงโปสเตอร์ผิด ข้อมูลเมตาไม่ตรงกัน สาเหตุ: โฟลเดอร์หรือชื่อไฟล์ไม่ตรงกับชื่อเรื่องตามที่ผู้ให้บริการข้อมูลเมตาระบุ, เป็นภาพยนตร์รีเมคหรือชื่อเรื่องทั่วไปที่ไม่มีปีระบุเพื่อแยกเวอร์ชัน, โฟลเดอร์ซีซันตั้งชื่อว่า S01 แทนที่จะเป็น Season 01 หรือตอนของซีรีส์ไม่ได้อยู่ในรูปแบบ S01E01 วิธีแก้ไข: เปลี่ยนชื่อไฟล์ให้เป็นไปตามรูปแบบข้างต้น จากนั้นเลือก Refresh metadata → Replace all หรือใช้ฟังก์ชัน Identify กับรายการนั้นๆ เพื่อระบุรายการที่ถูกต้องจาก TMDB/TVDB

FAQ

VPS สามารถแปลงรหัสวิดีโอ (transcode) โดยไม่มี GPU ได้หรือไม่?

ได้ แต่ต้องใช้ CPU เพียงอย่างเดียวซึ่งสิ้นเปลืองทรัพยากรมาก การแปลงรหัสซอฟต์แวร์ระดับ 1080p เพียงรายการเดียวอาจทำให้ vCPU หลายคอร์ทำงานเต็มประสิทธิภาพ และสำหรับ 4K หรือ HEVC มักจะไม่สามารถประมวลผลได้ทันเวลาจริง ทำให้การเล่นวิดีโอเกิดการบัฟเฟอร์ วิธีที่ดีที่สุดคือหลีกเลี่ยงการแปลงรหัส โดยจัดเก็บคลังสื่อของคุณในรูปแบบ H.264/AAC และใช้แอปพลิเคชันฝั่งไคลเอนต์ที่รองรับการเล่นโดยตรง (direct-play) เพื่อให้ VPS ทำหน้าที่เพียงส่งข้อมูลเท่านั้น ควรเช่าอินสแตนซ์ที่มี GPU เฉพาะในกรณีที่คุณจำเป็นต้องแปลงรหัสแบบเรียลไทม์จริงๆ เท่านั้น

ทำไมคลังสื่อ Jellyfin ของฉันถึงว่างเปล่าหลังจากสแกน?

สาเหตุเกือบทั้งหมดมาจากสิทธิ์การเข้าถึงไฟล์ (permissions) อิมเมจ jellyfin/jellyfin อย่างเป็นทางการจะทำงานภายใต้ user: ที่คุณกำหนด (หรือ root) หากไฟล์เหล่านั้นไม่สามารถอ่านได้โดย uid ดังกล่าว บันทึกการสแกนจะแจ้ง Access to the path ... is denied และข้ามไฟล์เหล่านั้นไป ให้แก้ไขความเป็นเจ้าของไฟล์ด้วย chown -R 1000:1000 /mnt/media และกำหนดสิทธิ์ execute bit ให้กับไดเรกทอรี (755) แล้วทำการสแกนใหม่ นอกจากนี้ให้ตรวจสอบไดเรกทอรีหลักด้วย เพราะหาก uid ของคอนเทนเนอร์ไม่สามารถเข้าถึง /mnt/media ได้ ก็จะไม่สามารถเข้าถึงโฟลเดอร์คลังสื่อได้เลยและผลลัพธ์จะว่างเปล่า สาเหตุที่พบบ่อยเป็นอันดับสองคือโครงสร้างโฟลเดอร์ไม่ตรงกับที่ Jellyfin คาดหวัง

ฉันจะเข้าถึง Jellyfin จากระยะไกลได้อย่างปลอดภัยได้อย่างไร?

มีสองทางเลือกที่ดี ทางเลือกแรกคือวางไว้หลัง TLS reverse proxy บนโดเมนย่อยเพื่อให้การล็อกอินและการสตรีมถูกเข้ารหัส และเพิ่ม fail2ban ห้ามเปิดพอร์ต 8096 โดยตรงเด็ดขาดเพราะจะส่งรหัสผ่านของคุณเป็นข้อความธรรมดา ทางเลือกที่สองคือเก็บไว้เป็นส่วนตัวทั้งหมดและเข้าถึงผ่าน VPN เท่านั้น ซึ่งเป็นทางเลือกที่ปลอดภัยและง่ายที่สุดสำหรับใช้งานในบ้าน ให้ระบุที่อยู่สาธารณะให้กับแอปโดยตรง เนื่องจากฟีเจอร์ autodiscovery เป็นการกระจายสัญญาณภายในเครือข่ายท้องถิ่น จึงไม่สามารถเข้าถึงไคลเอนต์ที่เชื่อมต่อผ่านอินเทอร์เน็ตได้

Jellyfin บน VPS ต้องการพื้นที่ดิสก์และแบนด์วิดท์เท่าใด?

พื้นที่ดิสก์ขึ้นอยู่กับคุณภาพ: ให้เผื่อไว้ 4-15 GB ต่อภาพยนตร์ 1080p แบบบีบอัด, 20-40 GB ต่อไฟล์ remux และ 40-100 GB สำหรับ 4K ดังนั้นคลังสื่อส่วนใหญ่จึงต้องการ block volume ขนาด 2-4 TB ส่วนแบนด์วิดท์จะถูกกำหนดโดยอัตราการส่งข้อมูล (bitrate) ของการเล่นโดยตรง ซึ่งอยู่ที่ 8-12 Mbps ต่อการสตรีม 1080p หนึ่งรายการ และมากกว่านั้นมากสำหรับ 4K ดังนั้นควรตรวจสอบว่าความเร็วพอร์ตของคุณรองรับจำนวนผู้ชมพร้อมกันได้ และคอยตรวจสอบขีดจำกัดการถ่ายโอนข้อมูลรายเดือน หากคุณวางแผนจะแปลงรหัส ให้เพิ่มประสิทธิภาพ CPU แต่หากวางแผนจะเล่นโดยตรง ให้ความสำคัญกับแบนด์วิดท์มากกว่าจำนวนคอร์

การรัน Jellyfin บน VPS ถูกกฎหมายหรือไม่?

Jellyfin เป็นซอฟต์แวร์โอเพนซอร์สฟรีและการใช้งานนั้นถูกกฎหมายโดยสมบูรณ์ สิ่งที่สำคัญคือเนื้อหา: ให้สตรีมเฉพาะสื่อที่คุณเป็นเจ้าของหรือได้รับอนุญาตให้ถือครอง เช่น ไฟล์ที่ริปจากแผ่นของคุณเอง, การบันทึกรายการ หรือไฟล์ที่คุณมีสิทธิ์ครอบครอง Jellyfin ไม่ได้มาพร้อมกับสื่อใดๆ และไม่มีช่องทางในการจัดหาสื่อเหล่านั้น มันเป็นเพียงโปรแกรมเล่นสำหรับคลังสื่อที่คุณมีอยู่แล้วเท่านั้น