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

วิธีติดตั้ง Vaultwarden บน VPS ด้วย Docker แบบปลอดภัย

เรียนรู้วิธีติดตั้ง Vaultwarden บน VPS เพื่อจัดการรหัสผ่านด้วยตนเองผ่าน Docker รองรับแอป Bitwarden พร้อมตั้งค่า HTTPS, Admin Token, Fail2ban และวิธีสำรองข้อมูลที่ถูกต้อง

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

โปรแกรมจัดการรหัสผ่านที่คุณเป็นเจ้าของโดยสมบูรณ์: Vaultwarden ที่ทำงานในคอนเทนเนอร์ขนาดเล็กหนึ่งตัวหลัง reverse proxy ซึ่งทำหน้าที่จัดการ TLS termination โดยให้แอป Bitwarden อย่างเป็นทางการบนโทรศัพท์ แล็ปท็อป และเบราว์เซอร์ของคุณเชื่อมต่อเข้ามา Vaultwarden เป็นการนำ Bitwarden server API มาเขียนใหม่ด้วยภาษา Rust และใช้โปรโตคอลเดียวกับ bitwarden.com ดังนั้นไคลเอนต์อย่างเป็นทางการจึงใช้งานกับมันได้โดยไม่ต้องแก้ไข ตราบใดที่คุณอัปเดตเซิร์ฟเวอร์ให้เป็นปัจจุบันอยู่เสมอ (การออกไคลเอนต์เวอร์ชันใหม่บางครั้งจำเป็นต้องใช้ Vaultwarden เวอร์ชันที่รองรับกัน เช่น จำเป็นต้องใช้ 1.37.2 สำหรับไคลเอนต์เวอร์ชัน 2026.8.0 ขึ้นไป) แต่ตัวมันใช้ RAM เพียงประมาณ 100 MB เท่านั้น ซึ่งน้อยกว่าชุดซอฟต์แวร์อย่างเป็นทางการที่ต้องใช้หลายคอนเทนเนอร์

การติดตั้งใช้เพียง Compose ไม่กี่บรรทัด สิ่งสำคัญ 3 ประการที่อาจทำให้ระบบใช้งานไม่ได้คือ: ต้องมี TLS ก่อนที่คุณจะโหลด web vault, ต้องปิดการสมัครสมาชิกสาธารณะทันทีหลังจากที่คุณสร้างบัญชีของตนเองเสร็จสิ้น และต้องสำรองข้อมูล volume พร้อมทั้งทดสอบการกู้คืนข้อมูลเสมอ เพราะไดเรกทอรีนั้นเก็บรหัสผ่านทั้งหมดที่คุณมีไว้

ข้อกำหนดเบื้องต้นและข้อควรระวังที่สำคัญ

  • VPS ที่ติดตั้ง Docker Engine และปลั๊กอิน Compose บน Ubuntu 24.04 KVM ที่ติดตั้งใหม่ โดยใช้สิทธิ์ root หรือ sudo หน่วยความจำ 512 MB เพียงพอสำหรับการใช้งานจริง แต่ 1 GB จะช่วยให้ทำงานได้สะดวกขึ้น นี่เป็นหนึ่งในบริการที่ใช้ทรัพยากรน้อยที่สุดที่คุณสามารถรันได้ และจัดอยู่ในอันดับต้นๆ ของ รายการบริการที่คุ้มค่าแก่การทำ self-hosting อย่างไรก็ตาม ควรเลือกขนาด VPS ให้เหมาะสมกับบริการอื่นที่จะรันร่วมกัน เพราะการติดตั้ง คลังรูปภาพแบบ self-hosted เช่น PhotoPrism หรือ Immich บน VPS เดียวกันจะทำให้ความต้องการ RAM เพิ่มขึ้นเป็นระดับกิกะไบต์ ในขณะที่ Vaultwarden แทบไม่ใช้ทรัพยากรเพิ่มเลย การคำนวณนี้ใช้กับ media front end ที่คุณอาจติดตั้งเพิ่มในภายหลังด้วย เนื่องจาก การปรับแต่งคลัง Jellyfin ให้เหมือนร้านเช่าวิดีโอในยุค 90 หมายถึงการเพิ่ม container ที่ต้องรันตลอดเวลาและต้องเผื่อทรัพยากรสำหรับการทำ transcoding ภายใต้งบประมาณเดียวกัน
  • โดเมนที่มี A record (และ AAAA หากคุณใช้ IPv6) ชี้ vault.example.com ไปยัง VPS ของคุณ ใบรับรอง TLS จะออกให้สำหรับชื่อนี้โดยเฉพาะ ดังนั้น DNS จะต้องชี้มาที่เซิร์ฟเวอร์ให้ถูกต้องก่อนเริ่มดำเนินการ
  • พอร์ต 80 และ 443 ต้องเปิดให้เข้าถึงได้จากอินเทอร์เน็ต โดยให้ reverse proxy เป็นผู้จัดการการเชื่อมต่อ ห้าม ให้ Vaultwarden รับการเชื่อมต่อโดยตรง พอร์ต 80 ใช้สำหรับการทำ ACME certificate challenge และการเปลี่ยนเส้นทางจาก HTTP ไปเป็น HTTPS เท่านั้น
  • ข้อควรระวังที่สำคัญที่สุด: ไคลเอนต์ของ Bitwarden จะปฏิเสธการเชื่อมต่อกับเซิร์ฟเวอร์ที่ไม่ใช่ HTTPS ไม่มีขั้นตอน "ทดสอบผ่าน http ก่อน" เพราะวิธีนั้นใช้งานไม่ได้จริง ด้วยเหตุผลที่ชัดเจนซึ่งจะกล่าวถึงในหัวข้อถัดไป

เหตุใดจึงเลือก Vaultwarden แทนที่จะใช้ Bitwarden stack แบบทางการ

ใช้ไคลเอนต์เดียวกัน แต่ใช้ทรัพยากรน้อยกว่ามาก การติดตั้ง Bitwarden แบบ self-hosted อย่างเป็นทางการนั้นมาในรูปแบบชุดของคอนเทนเนอร์ (ประกอบด้วย MSSQL Express, Identity, Api, Admin และอื่นๆ) ซึ่งต้องการ RAM อย่างน้อย 2 GB นอกจากนี้ Bitwarden ยังมี Bitwarden lite ซึ่งเป็น Docker image เดี่ยวที่ต้องการ RAM อย่างน้อย 200 MB แต่รุ่นนี้ออกแบบมาเพื่อการใช้งานส่วนตัวและ home lab ไม่ใช่สำหรับธุรกิจ และคุณต้องรับผิดชอบการจัดการฐานข้อมูลและการสำรองข้อมูลทั้งหมดด้วยตนเอง ในขณะที่ Vaultwarden เป็น binary ไฟล์เดียวที่จัดเก็บข้อมูลทุกอย่างไว้ในฐานข้อมูล SQLite เป็นค่าเริ่มต้น และใช้ RAM เพียงไม่กี่สิบเมกะไบต์ในสถานะ idle สำหรับผู้ใช้คนเดียว ครอบครัว หรือทีมขนาดเล็ก นี่จึงเป็นตัวเลือกที่ชัดเจนที่สุด และเนื่องจากมันรองรับ Bitwarden API อย่างครบถ้วน ข้อมูลของคุณจึงสามารถย้ายไปมาระหว่าง Vaultwarden และ bitwarden.com ได้อย่างอิสระ

สิ่งที่คุณต้องแลกคือฟีเจอร์ระดับองค์กรส่วนใหญ่ เช่น ไม่มีการทำ SCIM provisioning (แม้ว่าการรองรับ OpenID Connect SSO จะถูกเพิ่มเข้ามาในเวอร์ชัน 1.35.0 แล้วก็ตาม) และเนื่องจากคุณเป็นผู้ดูแลระบบเอง งานด้านการอัปเดตแพตช์, การจัดการ HTTPS และการสำรองข้อมูลจึงเป็นหน้าที่ของคุณ คู่มือฉบับนี้จะครอบคลุมงานทั้งสามส่วนดังกล่าว

เหตุผลที่ HTTPS ไม่ใช่ทางเลือก

Bitwarden web vault และส่วนขยายเบราว์เซอร์จะทำการสร้าง encryption keys ของคุณภายในเบราว์เซอร์โดยใช้ Web Crypto API (window.crypto.subtle) เบราว์เซอร์จะเปิดใช้งาน crypto.subtle เฉพาะใน secure context เท่านั้น ซึ่งได้แก่ HTTPS หรือกรณีพิเศษของ loopback addresses เช่น http://localhost และ http://127.0.0.1 หากใช้งานผ่าน http://vault.example.com ธรรมดา ฟังก์ชันนี้จะถูกปิดใช้งาน (undefined) ดังนั้นทันทีที่แอปพยายามสร้าง key ระบบจะหยุดทำงานและแสดงข้อความใน console ดังนี้:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'importKey')

หน้าเว็บจะค้างหรือแสดงข้อผิดพลาดเกี่ยวกับ crypto ทั่วไป และไม่สามารถล็อกอินได้ ทั้งไคลเอนต์บนเดสก์ท็อป มือถือ และเบราว์เซอร์จะตรวจสอบ URL ที่โฮสต์เอง หากพบว่าเป็น http (หรือเข้าถึงไม่ได้) ระบบจะปฏิเสธการเชื่อมต่อพร้อมข้อความ:

This is not a recognized Bitwarden server. You may need to check with your provider or update your server.

ทั้งสองกรณีมีสาเหตุเดียวกันคือ: ไม่มี HTTPS ที่ถูกต้อง ดังนั้นเราจึงต้องตั้งค่า TLS ให้เรียบร้อยก่อน และห้ามเปิดใช้งาน vault ผ่าน http โดยเด็ดขาด แม้จะเป็นเพียงการตรวจสอบเบื้องต้นก็ตาม

ขั้นตอนที่ 1 การตั้งค่า DNS และ reverse proxy (TLS ก่อน)

ชี้ระเบียน DNS ไปยัง VPS ของคุณและตรวจสอบว่าระบุที่อยู่ IP ได้ถูกต้อง:

dig +short vault.example.com

บรรทัดที่แสดงผลต้องเป็น IP ของ VPS ของคุณ หากว่างเปล่าหรือผิดพลาด ให้แก้ไข DNS และรอจนกว่า TTL จะหมดอายุ การออกใบรับรองจะล้มเหลวหากชื่อโดเมนไม่ชี้มาที่เซิร์ฟเวอร์

สำหรับส่วนหน้า HTTPS คู่มือนี้ใช้ Traefik ซึ่งจะออกและต่ออายุใบรับรอง Let's Encrypt โดยอัตโนมัติและทำงานร่วมกับ Compose ได้ทันที หากคุณยังไม่ได้ใช้งาน ให้ทำตาม การตั้งค่า Traefik reverse proxy และ TLS อัตโนมัติ ก่อน ซึ่งจะสร้าง Docker network ภายนอก (proxy ด้านล่าง) และ ACME resolver (letsencrypt) ที่บริการ Vaultwarden จะเชื่อมต่อเข้าไป ส่วน nginx แบบปกติที่ใช้ใบรับรองที่ออกด้วยตนเองนั้นทำงานในลักษณะเดียวกันจากฝั่งของ Vaultwarden

ต้องการใช้ nginx และ Certbot แทน Traefik หรือไม่? ให้รัน Vaultwarden บน 127.0.0.1:8080 (เพิ่ม ports: ["127.0.0.1:8080:80"] เข้าไปใน service และลบ labels ของ Traefik ออก) จากนั้นออกใบรับรองและทำ proxy ไปยังบริการดังกล่าว ส่วนของการจัดการใบรับรองครอบคลุมอยู่ใน การออกใบรับรอง Let's Encrypt ด้วย Certbot และ nginx สิ่งสำคัญเพิ่มเติมคือการทำ WebSocket upgrade บน path ของการแจ้งเตือน:

server {
    listen 443 ssl;
    server_name vault.example.com;

    client_max_body_size 525M;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

สังเกตบรรทัด X-Real-IP ซึ่งเป็นสิ่งที่ช่วยให้ Fail2ban มองเห็นตัวผู้โจมตีจริงแทนที่จะเห็นเป็น 127.0.0.1 ส่วนประกอบอื่นทั้งหมดในคู่มือนี้เหมือนกันไม่ว่าคุณจะใช้ Traefik หรือ nginx อยู่ด้านหน้าก็ตาม

ขั้นตอนที่ 2: ไฟล์ Compose

สร้างไดเรกทอรีสำหรับโปรเจกต์ก่อน คู่มือนี้ใช้ /opt/vaultwarden ซึ่งจะทำให้ชื่อโปรเจกต์ Compose และชื่อ data volume เป็น vaultwarden_vw-data ซึ่งคาดเดาได้ง่าย ขั้นตอน Fail2ban และการสำรองข้อมูลด้านล่างนี้จำเป็นต้องใช้ชื่อดังกล่าวให้ตรงกัน

sudo mkdir -p /opt/vaultwarden
cd /opt/vaultwarden

สร้างไฟล์ .env สำหรับเก็บรหัสผ่านผู้ดูแลระบบและไฟล์ Compose ไว้ในไดเรกทอรีนั้น

# .env
ADMIN_TOKEN=paste-a-strong-token-here

สร้างโทเค็นด้วย openssl rand -base64 48 แล้ววางลงไป (รูปแบบการแฮชที่ปลอดภัยกว่าจะกล่าวถึงในลำดับถัดไป สำหรับการเริ่มต้นใช้งาน สตริงสุ่มที่มีความยาวเพียงพอถือว่าใช้ได้)

# docker-compose.yml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault.example.com"
      SIGNUPS_ALLOWED: "true"          # closed in Step 4, keep true just to register
      ADMIN_TOKEN: "${ADMIN_TOKEN}"
      IP_HEADER: "X-Forwarded-For"     # X-Real-IP if your proxy sends that instead
      LOG_FILE: "/data/vaultwarden.log"
      LOG_LEVEL: "warn"
    volumes:
      - vw-data:/data
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vw.rule=Host(`vault.example.com`)"
      - "traefik.http.routers.vw.entrypoints=websecure"
      - "traefik.http.routers.vw.tls.certresolver=letsencrypt"
      - "traefik.http.services.vw.loadbalancer.server.port=80"

volumes:
  vw-data:

networks:
  proxy:
    external: true

มีสองประเด็นในไฟล์นี้ที่เป็นหัวใจสำคัญของการออกแบบ ประการแรกคือ ไม่มี การแมป ports: ดังนั้น Vaultwarden จึงเข้าถึงได้ผ่าน Traefik และ TLS เท่านั้น การเปิดพอร์ตบนโฮสต์โดยตรงเป็นสาเหตุที่ทำให้ผู้ใช้งานเผลอให้บริการ vault ผ่าน http โดยไม่ได้ตั้งใจ ประการที่สอง DOMAIN จะต้องเป็น URL แบบ HTTPS สาธารณะที่สมบูรณ์ เนื่องจากค่านี้จะถูกฝังอยู่ในลิงก์ไฟล์แนบ, WebAuthn 2FA และ endpoint ของการแจ้งเตือน หากค่านี้ผิดหรือเป็น http จะทำให้ฟังก์ชันเหล่านี้ใช้งานไม่ได้แม้ว่าหน้าเว็บจะโหลดขึ้นมาก็ตาม แท็ก latest เป็นข้อยกเว้นที่ตั้งใจไว้สำหรับกฎทั่วไปที่ว่าห้ามใช้ latest เนื่องจาก Vaultwarden ปล่อยเวอร์ชันเสถียรเป็นอิมเมจแบบ rolling เพียงอิมเมจเดียว โดยมี :testing เป็นช่องทางสำหรับเวอร์ชันทดสอบแยกต่างหาก ดังนั้นควรทำการอัปเดตอย่างตั้งใจและอ่านบันทึกการเปลี่ยนแปลง (release notes) ก่อนทำการ pull อิมเมจ อย่างไรก็ตามข้อยกเว้นนี้มีจำกัด คอนเทนเนอร์ส่วนใหญ่ที่ใช้งานระยะยาวควรระบุแท็กเวอร์ชันที่แน่นอนไว้ ซึ่งเป็นสิ่งที่ช่วยให้ เอเจนต์ที่ทำงานตลอดเวลาบน VPS เดียวกัน มีความเสถียรเมื่อมีการรีบูตหรือดึงอิมเมจใหม่

เริ่มการทำงานและตรวจสอบ log:

docker compose up -d
docker compose logs -f vaultwarden

การเริ่มต้นที่ถูกต้องจะจบลงด้วยบรรทัดข้อความเช่น Rocket has launched from http://0.0.0.0:80 ให้เวลา Traefik สักครู่เพื่อดึงใบรับรอง จากนั้นโหลด https://vault.example.com คุณควรจะเห็นหน้าเว็บ Bitwarden vault พร้อมไอคอนแม่กุญแจที่ถูกต้องและไม่มีการแจ้งเตือนเรื่องใบรับรองไม่ปลอดภัย

ขั้นตอนที่ 3, ADMIN_TOKEN ที่รัดกุม และกับดักของ $$

ADMIN_TOKEN ทำหน้าที่ปกป้อง /admin ซึ่งเป็นแผงควบคุมที่สามารถเข้าถึงผู้ใช้และการตั้งค่าทุกอย่างในอินสแตนซ์ของคุณ ดังนั้นให้ปฏิบัติกับมันเสมือนรหัสผ่าน root โดยมีรูปแบบที่ใช้งานได้ 2 วิธี

รูปแบบที่ง่ายคือการใช้สตริงสุ่มที่คุณสร้างขึ้นแล้วด้วย openssl rand -base64 48 เนื่องจาก base64 ไม่มีอักขระ $ อยู่เลย จึงสามารถนำไปใส่ใน .env ได้โดยตรงโดยไม่ต้องทำ escaping

รูปแบบที่รัดกุมกว่าคือการใช้ Argon2 PHC hash เพื่อไม่ให้มีการจัดเก็บ plaintext token ไว้บนดิสก์ ให้สร้าง hash โดยใช้ image เดียวกันดังนี้:

docker run --rm -it vaultwarden/server /vaultwarden hash --preset owasp

ระบบจะให้คุณป้อนรหัสผ่านสองครั้งและแสดงสตริงที่ขึ้นต้นด้วย $argon2id$v=19$... นี่คือกับดักที่ทำให้หลายคนเสียเวลา: Docker Compose จะตีความ $ เป็นการแทนที่ตัวแปร (variable interpolation) ดังนั้นหากคุณวาง hash ลงใน environment: ในไฟล์ Compose โดยตรง คุณต้อง เบิ้ล $ ทุกตัวให้เป็น $$ การใส่เครื่องหมายคำพูดครอบค่าดังกล่าวสามารถทำได้ แต่สิ่งที่สำคัญคือการทำ escaping:

    environment:
      ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$c29tZXNhbHQ$$RdescudvJCsgt3ub+b+dWRWJTmaaJObG

หากคุณต้องการเก็บ hash ไว้ใน .env กฎจะเปลี่ยนไป: ให้คงเครื่องหมาย $ ไว้ตัวเดียว ครอบค่าทั้งหมดด้วยเครื่องหมายคำพูดเดี่ยว (ADMIN_TOKEN='$argon2id$v=19$...') แล้วบรรทัด ${ADMIN_TOKEN} ที่มีอยู่ในไฟล์ Compose จะดึงค่าไปใช้เอง ห้ามเบิ้ล $ ใน .env หากคุณทิ้งเครื่องหมาย $ ไว้ตัวเดียวในไฟล์ Compose ระบบจะแจ้งเตือน The "argon2id" variable is not set และทำให้ token เป็นค่าว่าง ส่งผลให้ /admin ปฏิเสธรหัสผ่านที่ถูกต้องของคุณ ให้รัน docker compose up -d และเก็บ plaintext ที่คุณพิมพ์ในตอนแรกไว้ในโปรแกรมจัดการรหัสผ่านของคุณเอง

ขั้นตอนที่ 4 ลงทะเบียนบัญชีของคุณ แล้วล็อกประตู

ใช้ SIGNUPS_ALLOWED: "true" เปิด https://vault.example.com คลิก Create account และลงทะเบียนด้วยอีเมลและรหัสผ่านหลัก (master password) ที่คาดเดายาก รหัสผ่านหลักนี้ไม่สามารถกู้คืนได้และไม่มีระบบรีเซ็ต ดังนั้นควรจัดเก็บไว้ในที่ที่ปลอดภัยและถาวรก่อน

จากนั้นให้ปิดประตู โดยแก้ไขไฟล์ Compose เพื่อปิดการลงทะเบียน:

      SIGNUPS_ALLOWED: "false"

ปรับใช้การตั้งค่าอีกครั้งด้วย docker compose up -d นี่ไม่ใช่ขั้นตอนการรักษาความปลอดภัยที่คุณควรผัดวันประกันพรุ่ง หากเปิดทิ้งไว้ ใครก็ตามที่พบ URL (ซึ่งบอทค้นหาจะพบอย่างแน่นอน) สามารถสร้างบัญชีบนเซิร์ฟเวอร์ของคุณได้ แม้พวกเขาจะไม่สามารถอ่านข้อมูลใน vault ของคุณได้ แต่พวกเขาก็จะใช้ทรัพยากรและเปลี่ยน instance ส่วนตัวของคุณให้กลายเป็นบริการสาธารณะ สัญญาณที่บ่งบอกว่าคุณลืมปิดการลงทะเบียนคือ /admin จะแสดงรายการบัญชีที่คุณไม่ได้สร้างขึ้น

หากต้องการเพิ่มสมาชิกในครอบครัวหรือเพื่อนร่วมงานในภายหลังโดยไม่ต้องเปิดการลงทะเบียนสาธารณะ ให้ใช้ปุ่ม Invite User ใน /admin ซึ่งเส้นทางนี้จำเป็นต้องมีการตั้งค่า SMTP เพื่อให้ผู้ได้รับเชิญได้รับลิงก์ของตนเอง

ขั้นตอนที่ 5 การเข้าถึง /admin

เปิดเบราว์เซอร์ไปที่ https://vault.example.com/admin แล้วกรอก admin token แบบข้อความธรรมดา (สตริงสุ่ม หรือรหัสผ่านที่คุณนำไปทำ hash แล้ว ไม่ใช่ตัว hash เอง) ภายในหน้าดังกล่าว คุณสามารถแสดงรายการผู้ใช้ ปรับแต่งการตั้งค่า ส่งอีเมลทดสอบ และสำรองข้อมูลฐานข้อมูลได้

หากหน้าเว็บแสดงข้อความ The admin panel is disabled, please configure the 'ADMIN_TOKEN' variable to enable it แสดงว่า ADMIN_TOKEN ว่างเปล่าหรือไม่ได้ตั้งค่าไว้ ซึ่งจะเป็นการปิดใช้งานแผงควบคุมนี้โดยสมบูรณ์ ซึ่งเป็นทางเลือกที่ยอมรับได้หากคุณไม่จำเป็นต้องใช้งาน หากหน้าเว็บโหลดขึ้นมาแต่ปฏิเสธ token ของคุณ ให้ดูเรื่องปัญหาการ escape อักขระใน $$ ที่รายการข้อผิดพลาดด้านล่าง หากลืม token ดังกล่าว ระบบไม่มีฟังก์ชันกู้คืน ให้แก้ไขไฟล์ .env หรือไฟล์ Compose โดยกำหนดค่าใหม่ แล้วใช้คำสั่ง docker compose up -d

ขั้นตอนที่ 6 การเชื่อมต่อ Bitwarden client

Client อย่างเป็นทางการทุกตัวสามารถเชื่อมต่อไปยังเซิร์ฟเวอร์ที่คุณโฮสต์เองได้ ให้ติดตั้ง Bitwarden เวอร์ชัน desktop, mobile หรือ browser client จากแหล่งดาวน์โหลดปกติ คุณไม่จำเป็นต้องใช้ build พิเศษของ Vaultwarden

ก่อนเข้าสู่ระบบ ให้เปิดไอคอนฟันเฟืองการตั้งค่าที่หน้าจอเข้าสู่ระบบ (ระบุว่า Self-hosted หรือ Region → Self-hosted) ตั้งค่า Server URL เป็น https://vault.example.com แล้วบันทึก จากนั้นเข้าสู่ระบบด้วยอีเมลและรหัสผ่านหลักที่คุณลงทะเบียนไว้ client ควรเชื่อมต่อได้ทันทีและพร้อมใช้งานสำหรับการกรอกและบันทึกข้อมูลประจำตัว

หาก client แสดงข้อความ This is not a recognized Bitwarden server. You may need to check with your provider or update your server. แสดงว่า URL ไม่ถูกต้อง, มีการใช้ http หรือใบรับรองไม่น่าเชื่อถือ ให้ตรวจสอบอีกครั้งว่า https://vault.example.com สามารถโหลดผ่านเบราว์เซอร์ได้อย่างสมบูรณ์หรือไม่ หากการอัปเดตบนอุปกรณ์อื่นล่าช้า อาจเกิดจากปัญหา WebSocket push ซึ่งจะอธิบายไว้ในหัวข้อถัดไป

ขั้นตอนที่ 7 การสร้าง Fail2ban jail สำหรับ endpoint การเข้าสู่ระบบ

Vaultwarden จะบันทึกความพยายามเข้าสู่ระบบที่ล้มเหลวทุกครั้งลงในไฟล์ที่กำหนดโดย LOG_FILE ซึ่งเป็นข้อมูลที่จำเป็นสำหรับระบบป้องกันการโจมตีแบบ brute-force หากคุณยังไม่ได้ติดตั้ง Fail2ban สามารถดูวิธีการติดตั้งและพื้นฐานการใช้งานได้ที่ คู่มือการเพิ่มความปลอดภัย SSH ด้วย Fail2ban ในส่วนนี้เราจะเพิ่ม jail สำหรับ Vaultwarden

ขั้นแรก ให้ตรวจสอบตำแหน่งของ named volume บนโฮสต์เพื่อให้ Fail2ban สามารถอ่านไฟล์ log ได้:

docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}'

คำสั่งดังกล่าวจะแสดงผลลัพธ์ในลักษณะ /var/lib/docker/volumes/vaultwarden_vw-data/_data โดยไฟล์ log จะอยู่ที่ vaultwarden.log ภายในไดเรกทอรีนั้น จากนั้นให้สร้างตัวกรอง (filter):

# /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =

และสร้าง jail:

# /etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden]
enabled   = true
filter    = vaultwarden
logpath   = /var/lib/docker/volumes/vaultwarden_vw-data/_data/vaultwarden.log
banaction = iptables-allports
chain     = DOCKER-USER
maxretry  = 5
findtime  = 600
bantime   = 3600

โหลดการตั้งค่าใหม่ด้วย sudo systemctl restart fail2ban และตรวจสอบสถานะด้วย sudo fail2ban-client status vaultwarden

มีรายละเอียดของ Docker 3 ประการที่ส่งผลต่อประสิทธิภาพการป้องกันนี้ ประการแรก หาก log แสดงค่า IP: 127.0.0.1 หรือแสดง IP ของ proxy ในทุกความพยายามที่ล้มเหลว แสดงว่า Vaultwarden กำลังแบนตัว proxy เอง ให้ตั้งค่า IP_HEADER เป็น header ที่ proxy ของคุณส่งมาจริง (X-Forwarded-For สำหรับ Traefik, X-Real-IP สำหรับบล็อก nginx ด้านบน, CF-Connecting-IP หากอยู่หลัง Cloudflare) ตั้งแต่เวอร์ชัน 1.37.4 เป็นต้นมา ด้วยการตั้งค่า X-Forwarded-For ทำให้ Vaultwarden เลือกใช้ที่อยู่ขวาสุดที่ไม่ตรงกับ IP_HEADER_TRUSTED_PROXIES (ค่าเริ่มต้นคือ local ซึ่งครอบคลุม proxy ที่อยู่บนโฮสต์เดียวกันหรือเครือข่าย Docker เดียวกัน) ดังนั้นหากมี CDN อยู่หน้า proxy ของคุณ ให้ระบุที่อยู่ IP ของ CDN ไว้ในรายการด้วย ประการที่สอง การเลือก iptables chain ที่ถูกต้องขึ้นอยู่กับ proxy ของคุณ หากใช้ Traefik ในรูปแบบคอนเทนเนอร์ที่เปิดพอร์ตไว้ ทราฟฟิกจะผ่านเส้นทาง FORWARD ของ Docker ดังนั้นการแบนจะต้องอยู่ใน DOCKER-USER ตามที่ระบุไว้ข้างต้น แต่หากคุณเลือกใช้ตัวเลือก host-nginx จากขั้นตอนที่ 1 การเชื่อมต่อจะสิ้นสุดที่ nginx บน chain INPUT ของโฮสต์ ซึ่งการแบนแบบ DOCKER-USER จะมองไม่เห็นทราฟฟิกดังกล่าว ในกรณีนี้ให้ลบบรรทัด chain = DOCKER-USER ออกเพื่อให้ Fail2ban ใช้ chain INPUT ตามค่าเริ่มต้น ประการที่สาม ให้ใช้ banaction = iptables-allports แทนค่าเริ่มต้นที่อิงตามพอร์ต เนื่องจาก jail นี้ไม่ได้กำหนดพอร์ตไว้ และการแบนทุกพอร์ตใน DOCKER-USER จะช่วยบล็อกผู้บุกรุกออกจากทุกบริการที่เปิดใช้งานอยู่บนเครื่องได้อย่างเบ็ดเสร็จ

ขั้นตอนที่ 8 สำรองข้อมูล vault และทดสอบการกู้คืนจริง

volume vw-data คือตัวจัดการรหัสผ่านของคุณ มันเก็บไฟล์ db.sqlite3 (รายการทั้งหมด), ไดเรกทอรี attachments/ และ sends/, ไฟล์ rsa_key.* ที่ใช้ลงนามเซสชันการเข้าสู่ระบบ และ config.json จากแผงควบคุมผู้ดูแลระบบ การสำรองข้อมูลที่ข้ามไฟล์เหล่านี้ไปจะทำให้กู้คืนไม่ได้เมื่อคุณต้องการใช้งาน

การคัดลอก db.sqlite3 ในขณะที่ Vaultwarden กำลังเขียนข้อมูลอาจทำให้ได้ไฟล์ที่เขียนไม่สมบูรณ์และเสียหาย ดังนั้นให้ทำ cold snapshot ซึ่งใช้เวลาหยุดทำงานเพียงไม่กี่วินาที:

#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%F)
DEST=/root/vw-backups
VOL=$(docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}')
mkdir -p "$DEST"
docker compose -f /opt/vaultwarden/docker-compose.yml stop vaultwarden
tar czf "$DEST/vw-$STAMP.tgz" -C "$VOL" .
docker compose -f /opt/vaultwarden/docker-compose.yml start vaultwarden

ให้รันคำสั่งนี้ผ่าน cron ทุกคืนและคัดลอก .tgz ออกไปไว้ นอก เซิร์ฟเวอร์ เพราะการสำรองข้อมูลที่เก็บไว้บนเซิร์ฟเวอร์เดียวกับที่ปกป้องอยู่นั้นไม่ใช่การสำรองข้อมูลที่แท้จริง วิธีที่สะอาดในการส่งข้อมูลคือ การสำรองข้อมูลด้วย restic รายคืนไปยังเซิร์ฟเวอร์อื่นหรือ object storage ซึ่งจะเข้ารหัสไฟล์เก็บถาวรและทำ deduplication ให้กับ snapshot ที่ซ้ำซ้อน ปุ่ม Backup Database ในแผงควบคุมผู้ดูแลระบบเป็นเพียงการทำ hot snapshot ของไฟล์ SQLite เท่านั้น แต่จะไม่มีไฟล์แนบและคีย์ต่างๆ รวมอยู่ด้วย

ตอนนี้ถึงขั้นตอนสำคัญที่แยกการสำรองข้อมูลที่ใช้งานได้จริงออกจากความหวังลมๆ แล้งๆ คือการกู้คืนข้อมูลหนึ่งครั้งเพื่อพิสูจน์ว่าใช้งานได้จริง:

mkdir -p /tmp/vw-restore
tar xzf /root/vw-backups/vw-2026-07-15.tgz -C /tmp/vw-restore
docker run --rm -p 127.0.0.1:8888:80 -v /tmp/vw-restore:/data vaultwarden/server

จากแล็ปท็อปของคุณ ให้สร้าง tunnel ไปยังเซิร์ฟเวอร์ด้วย ssh -L 8888:127.0.0.1:8888 you@your-vps และเปิด http://localhost:8888 (หรือ http://127.0.0.1:8888) เนื่องจาก loopback address ถือเป็นบริบทที่ปลอดภัย crypto.subtle จึงสามารถใช้งานได้และ vault จะถอดรหัสผ่าน http ธรรมดา ซึ่งเป็นที่อยู่ประเภทเดียวที่อนุญาตให้ทำเช่นนี้ได้ ให้เข้าสู่ระบบด้วยรหัสผ่านหลักของคุณและยืนยันว่ารายการข้อมูลทั้งหมดอยู่ครบ หากข้อมูลครบถ้วน แสดงว่าฐานข้อมูล, RSA keys และรหัสผ่านหลักของคุณทำงานได้ถูกต้อง และคุณสามารถสร้างระบบใหม่บน VPS เครื่องใหม่ได้ภายในไม่กี่นาที หยุด container ด้วย Ctrl-C และลบ /tmp/vw-restore ทิ้งไป ฝึกนิสัยการใช้ tunnel สำหรับ UI ผู้ดูแลระบบอื่นๆ บนเซิร์ฟเวอร์ที่ไม่ควรเปิดเผยต่ออินเทอร์เน็ต ซึ่งเป็นวิธีเดียวกับที่คุณจะใช้เข้าถึง เครื่องมือสแกนความปลอดภัย open-kritt ที่โฮสต์เอง บนพอร์ต 5173 ด้วยเช่นกัน

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

Cannot read properties of undefined (reading 'importKey') ในคอนโซลของเบราว์เซอร์ Vault ถูกโหลดผ่าน http ทำให้ crypto.subtle ไม่ถูกกำหนดค่า ให้เข้าถึงผ่าน https:// เท่านั้น และเพิ่มการเปลี่ยนเส้นทาง HTTP-to-HTTPS ที่ตัว proxy

This is not a recognized Bitwarden server... ในไคลเอนต์ URL ของเซิร์ฟเวอร์เป็น http, พิมพ์ผิด หรือใบรับรองไม่น่าเชื่อถือ ให้ตรวจสอบว่า https://vault.example.com แสดงรูปแม่กุญแจที่ถูกต้อง จากนั้นป้อน URL ใหม่อีกครั้งในการตั้งค่า self-hosted ของไคลเอนต์

/admin ปฏิเสธรหัสผ่านที่ถูกต้อง Argon2 hash สูญเสียการ escape ไป โดยทุก $ จะต้องเป็น $$ ในไฟล์ Compose (แต่ถ้าเป็น $ ตัวเดียวต้องอยู่ในเครื่องหมายคำพูดเดี่ยวใน .env) หรือคุณอาจป้อนค่า hash แทนที่จะเป็นข้อความธรรมดาที่ hash นั้นแทนค่าอยู่

การซิงค์ข้ามอุปกรณ์ช้า; คอนโซลแสดง WebSocket connection to 'wss://vault.example.com/notifications/hub' failed Proxy ไม่ได้ส่งต่อ header Upgrade/Connection; Traefik จะทำส่วนนี้ให้อัตโนมัติ ส่วน nginx ต้องการบรรทัด upgrade สองบรรทัดจากขั้นตอนที่ 1 ตัว vault ยังคงทำงานได้ปกติ เพียงแต่จะซิงค์ข้อมูลเมื่อเปิดแอปเท่านั้น พอร์ต 3012 แบบเดิมถูกยกเลิกไปตั้งแต่ v1.31.0 จึงไม่จำเป็นต้องตั้งค่าเส้นทาง WebSocket แยกต่างหาก

Fail2ban รายงานว่าแบนแล้วแต่ผู้โจมตียังคงเชื่อมต่อได้ ระบบกำลังแบน 127.0.0.1 เนื่องจาก IP_HEADER ไม่ถูกต้อง หรือการแบนอยู่ใน iptables chain ที่ผิด ให้ตั้งค่า chain = DOCKER-USER และ banaction = iptables-allports

การอัปเกรด

ดึงอิมเมจใหม่และสร้างคอนเทนเนอร์ขึ้นมาใหม่ โดยที่ named volume และข้อมูลทั้งหมดของคุณจะยังคงอยู่:

docker compose pull
docker compose up -d

Vaultwarden มีการออกรุ่นใหม่บ่อยครั้ง ให้ติดตาม บันทึกการเปลี่ยนแปลงของโครงการ แทนการล็อกเวอร์ชันของแพตช์ เนื่องจากบางรุ่นอาจมีบันทึกเกี่ยวกับการย้ายข้อมูล (migration) ให้สำรองข้อมูลใหม่ก่อนการอัปเกรดเวอร์ชันหลักเสมอ คุณสามารถย้อนกลับได้โดยการกู้คืนไฟล์ tarball ลงใน volume ใหม่

FAQ

Vaultwarden เหมือนกับ Bitwarden หรือไม่?

มันเป็นเซิร์ฟเวอร์อิสระที่ทำงานร่วมกันได้ ไม่ใช่ซอฟต์แวร์ทางการ Vaultwarden เขียนขึ้นใหม่โดยใช้ภาษา Rust เพื่อจำลอง API ของเซิร์ฟเวอร์ Bitwarden ทำให้ไคลเอนต์ทางการทั้งบนเดสก์ท็อป มือถือ เบราว์เซอร์ และ CLI สามารถใช้งานได้โดยใช้ทรัพยากรน้อยกว่าชุดซอฟต์แวร์ทางการมาก รูปแบบของ vault นั้นเหมือนกัน คุณจึงสามารถย้ายข้อมูลไปมาระหว่างกันได้ด้วยการส่งออกและนำเข้า

ฉันจำเป็นต้องใช้ HTTPS จริงหรือ หรือสามารถรันผ่าน http บน LAN ได้?

คุณจำเป็นต้องใช้ HTTPS สำหรับทุกกรณี ยกเว้นการทดสอบแบบ loopback ตัวเว็บ vault และส่วนขยายของ Bitwarden ใช้ Web Crypto API ของเบราว์เซอร์ ซึ่งจะทำงานได้เฉพาะในบริบทที่ปลอดภัยเท่านั้น ดังนั้นหากใช้ http ธรรมดา ไคลเอนต์จะแสดงข้อผิดพลาด Cannot read properties of undefined และไม่สามารถล็อกอินได้ ที่อยู่ http เดียวที่ใช้งานได้คือที่อยู่แบบ loopback เช่น http://localhost หรือ http://127.0.0.1 ซึ่งเป็นเหตุผลว่าทำไมการทดสอบการกู้คืนในขั้นตอนที่ 8 จึงใช้ SSH tunnel

ฉันจะป้องกันไม่ให้คนแปลกหน้าลงทะเบียนบนเซิร์ฟเวอร์ของฉันได้อย่างไร?

ตั้งค่า SIGNUPS_ALLOWED: "false" ในไฟล์ Compose และรัน docker compose up -d ทันทีหลังจากสร้างบัญชีของคุณเอง หลังจากนั้นให้เพิ่มผู้ใช้ใหม่ผ่านปุ่ม Invite User ใน /admin ซึ่งจำเป็นต้องตั้งค่า SMTP เพื่อให้ผู้ใช้ได้รับลิงก์คำเชิญ ตรวจสอบรายชื่อผู้ใช้ในระบบเป็นระยะเพื่อยืนยันว่าไม่มีบัญชีที่ไม่คาดคิดปรากฏขึ้น

ฉันจะสำรองข้อมูล Vaultwarden vault ของฉันได้อย่างไร?

หยุดคอนเทนเนอร์ชั่วคราวและทำไฟล์เก็บถาวร (archive) ของ volume vw-data ทั้งหมด รวมถึงไฟล์ db.sqlite3, attachments/, sends/, config.json และ rsa_key.* จากนั้นคัดลอกไฟล์เก็บถาวรออกจากเซิร์ฟเวอร์ โดยควรทำผ่าน cron เป็นประจำทุกคืน การคัดลอกไฟล์ SQLite ขณะที่เซิร์ฟเวอร์กำลังทำงานมีความเสี่ยงที่จะได้ไฟล์ที่เสียหาย ดังนั้นควรทำในขณะที่หยุดบริการไว้ สิ่งสำคัญที่สุดคือให้ทดลองกู้คืนข้อมูลไปยังคอนเทนเนอร์สำรองและลองล็อกอิน เพื่อให้มั่นใจว่าข้อมูลสำรองนั้นใช้งานได้จริงก่อนที่คุณจะพึ่งพามัน

การโฮสต์รหัสผ่านด้วยตนเองปลอดภัยจริงหรือ?

ปลอดภัย หากคุณทำตามสามสิ่งที่คู่มือนี้ครอบคลุม ได้แก่ การใช้ HTTPS จริง, การปิดการลงทะเบียนพร้อมตั้งค่า admin token ที่รัดกุม และการทดสอบการสำรองข้อมูล vault ของคุณจะถูกเข้ารหัสจากฝั่งไคลเอนต์ด้วยรหัสผ่านหลักของคุณ ดังนั้นแม้แต่เซิร์ฟเวอร์ก็ไม่สามารถเห็นรหัสผ่านของคุณในรูปแบบข้อความปกติได้ และหากไฟล์ db.sqlite3 ถูกขโมยไป ก็ไม่สามารถเปิดเผยข้อมูลของคุณได้หากไม่มีรหัสผ่านหลัก อย่างไรก็ตาม เซิร์ฟเวอร์ยังคงเก็บอีเมลบัญชีและค่า salted hash ที่ได้จากรหัสผ่านหลัก ซึ่งผู้โจมตีอาจพยายามถอดรหัสแบบออฟไลน์ได้ ดังนั้นควรปกป้องไฟล์ฐานข้อมูลและสำเนาทุกชุดให้ดี สิ่งที่ต้องแลกคือการอัปเดตแพตช์และการสำรองข้อมูลกลายเป็นความรับผิดชอบของคุณ ซึ่งเป็นเหตุผลว่าทำไม Fail2ban และขั้นตอนการกู้คืนข้อมูลจึงเป็นสิ่งที่ขาดไม่ได้ เมื่อตั้งค่าสิ่งเหล่านี้เรียบร้อยแล้ว การพิจารณาเจาะลึกถึงจุดที่ vault แบบ self-hosted อาจถูกโจมตี คือขั้นตอนถัดไปที่มีประโยชน์ เพราะเมื่อข้อมูลถูกเข้ารหัสจากฝั่งไคลเอนต์ สิ่งที่เหลือที่ต้องป้องกันคือ admin token และไฟล์สำรองข้อมูล