วิธีติดตั้ง Vaultwarden บน VPS ด้วย Docker แบบปลอดภัย
เรียนรู้วิธีติดตั้ง Vaultwarden บน VPS เพื่อจัดการรหัสผ่านด้วยตนเองผ่าน Docker รองรับแอป Bitwarden พร้อมตั้งค่า HTTPS, Admin Token, Fail2ban และวิธีสำรองข้อมูลที่ถูกต้อง
สิ่งที่คุณกำลังสร้าง
โปรแกรมจัดการรหัสผ่านที่คุณเป็นเจ้าของโดยสมบูรณ์: Vaultwarden ที่ทำงานในคอนเทนเนอร์ขนาดเล็กหนึ่งตัวหลัง reverse proxy ซึ่งทำหน้าที่จัดการ TLS termination โดยใช้แอป Bitwarden อย่างเป็นทางการบนโทรศัพท์ แล็ปท็อป และเบราว์เซอร์ของคุณชี้มาที่เซิร์ฟเวอร์นี้ Vaultwarden เป็นการเขียน Bitwarden server API ขึ้นใหม่ด้วยภาษา Rust และใช้โปรโตคอลเดียวกับ bitwarden.com ดังนั้นไคลเอนต์ที่เป็นทางการทุกตัวจึงทำงานร่วมกับมันได้โดยไม่ต้องแก้ไข แต่ใช้ RAM เพียงประมาณ 100 MB แทนที่จะต้องใช้ stack แบบหลายคอนเทนเนอร์เหมือนของทางการ
การติดตั้งใช้เพียง 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, Nginx, Identity, Api, Admin และอื่นๆ) ซึ่งต้องการ RAM ประมาณ 2 GB ในขณะที่ Vaultwarden เป็นไฟล์ binary เดียวที่จัดเก็บข้อมูลทั้งหมดไว้ในฐานข้อมูล SQLite โดยค่าเริ่มต้น และใช้ RAM เพียงไม่กี่สิบเมกะไบต์เท่านั้น สำหรับการใช้งานคนเดียว ครอบครัว หรือทีมขนาดเล็ก นี่คือตัวเลือกที่เหมาะสมที่สุด และเนื่องจากมีการนำ Bitwarden API มาใช้งานอย่างครบถ้วน ข้อมูลของคุณจึงสามารถย้ายไปมาระหว่าง Vaultwarden และ bitwarden.com ได้อย่างอิสระ
สิ่งที่คุณต้องแลกคือฟีเจอร์ระดับองค์กรส่วนใหญ่ เช่น ไม่มีการทำ SCIM provisioning (แม้ว่า OpenID Connect SSO แบบทดลองจะถูกเพิ่มเข้ามาในเวอร์ชัน 1.35.0 แล้วก็ตาม) และเนื่องจากคุณเป็นผู้ดูแลระบบเอง งานด้านการแพตช์ระบบ, การจัดการ HTTPS และการสำรองข้อมูลจึงเป็นหน้าที่ของคุณ คู่มือนี้จะครอบคลุมงานทั้ง 3 ส่วนดังกล่าว
เหตุผลที่ HTTPS ไม่ใช่ทางเลือกเสริม
Bitwarden web vault และส่วนขยายเบราว์เซอร์จะทำการสร้างคีย์เข้ารหัสของคุณภายในเบราว์เซอร์โดยใช้ Web Crypto API (window.crypto.subtle) เบราว์เซอร์จะเปิดใช้งาน crypto.subtle เฉพาะใน บริบทที่ปลอดภัย (secure context) ซึ่งหมายถึง HTTPS หรือกรณีพิเศษอย่าง http://localhost เท่านั้น หากใช้งานผ่าน http://vault.example.com ธรรมดา ฟังก์ชันนี้จะถูก undefined ดังนั้นทันทีที่แอปพยายามสร้างคีย์ ระบบจะแจ้งข้อผิดพลาดและแสดงผลในคอนโซลดังนี้:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'importKey')หน้าเว็บจะค้างหรือแสดงข้อผิดพลาดด้านการเข้ารหัสทั่วไป และไม่สามารถล็อกอินได้ ทั้งไคลเอนต์บนเดสก์ท็อป มือถือ และเบราว์เซอร์จะตรวจสอบ 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 ได้ทันทีโดยไม่ต้องทำการ escape
รูปแบบที่ปลอดภัยกว่าคือการใช้ 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 ลงในไฟล์ Compose ให้ใส่ค่านี้ไว้ใต้ environment: โดยตรง ไม่ต้องผ่าน .env และไม่ต้องใส่เครื่องหมายคำพูดครอบ:
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$c29tZXNhbHQ$$RdescudvJCsgt3ub+b+dWRWJTmaaJObGหากคุณปล่อยให้มีเครื่องหมาย $ เพียงตัวเดียว 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 เอง) ภายในหน้าดังกล่าว คุณสามารถแสดงรายการผู้ใช้ ปรับแต่งการตั้งค่า ส่งอีเมลทดสอบ และสำรองข้อมูลฐานข้อมูลได้
หากหน้าเว็บแสดงผล 404 Not Found แสดงว่า 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 สำหรับ vault
ขั้นแรก ให้ค้นหาตำแหน่งของ 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) ประการที่สอง 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 ซึ่งจะทำการเข้ารหัสไฟล์เก็บถาวรและทำ deduplicate ข้อมูลที่ซ้ำกันให้คุณ ปุ่ม 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 เนื่องจาก localhost เป็นบริบทที่ปลอดภัย crypto.subtle จึงพร้อมใช้งานและ vault จะถอดรหัสผ่าน http ธรรมดาในจุดนี้ ซึ่งเป็นที่เดียวที่อนุญาตให้ทำได้ ให้เข้าสู่ระบบด้วยรหัสผ่านหลักของคุณและตรวจสอบว่ารายการข้อมูลทั้งหมดอยู่ครบ หากข้อมูลอยู่ครบ แสดงว่าฐานข้อมูล, RSA keys และรหัสผ่านหลักของคุณสามารถใช้งานได้สมบูรณ์ และคุณสามารถสร้างระบบใหม่บน VPS เครื่องใหม่ได้ภายในไม่กี่นาที หยุดคอนเทนเนอร์ด้วย 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 ที่พร็อกซี
This is not a recognized Bitwarden server... ในไคลเอนต์ URL ของเซิร์ฟเวอร์เป็น http, พิมพ์ผิด หรือใบรับรองไม่น่าเชื่อถือ ให้ตรวจสอบว่า https://vault.example.com แสดงไอคอนแม่กุญแจที่ถูกต้อง จากนั้นกรอก URL ใหม่อีกครั้งในการตั้งค่า self-hosted ของไคลเอนต์
/admin ปฏิเสธรหัสผ่านที่ถูกต้อง Argon2 hash สูญเสียการ escape ไป โดย $ ทุกตัวต้องเป็น $$ ใน Compose หรือคุณอาจใส่ค่า hash แทนที่จะเป็นข้อความรหัสผ่านปกติ (plaintext)
การซิงค์ข้ามอุปกรณ์ช้า; คอนโซลแสดง WebSocket connection to 'wss://vault.example.com/notifications/hub' failed พร็อกซีไม่ได้ส่งต่อ header Upgrade/Connection โดย Traefik จะจัดการส่วนนี้โดยอัตโนมัติ แต่ nginx จำเป็นต้องมีบรรทัด upgrade สองบรรทัดจากขั้นตอนที่ 1 ตัว Vault ยังคงทำงานได้ปกติ เพียงแต่จะซิงค์ข้อมูลเมื่อเปิดแอปเท่านั้น พอร์ต 3012 แบบเดิมถูกยกเลิกไปตั้งแต่ v1.31.0 จึงไม่จำเป็นต้องตั้งค่าเส้นทาง WebSocket แยกต่างหาก
Fail2ban รายงานการแบนแต่ผู้โจมตียังคงเชื่อมต่อได้ ระบบกำลังแบน 127.0.0.1 เนื่องจาก IP_HEADER ไม่ถูกต้อง หรือการแบนอยู่ใน chain ของ iptables ที่ไม่ถูกต้อง ให้ตั้งค่า chain = DOCKER-USER และ banaction = iptables-allports
การอัปเกรด
ดึงอิมเมจใหม่และสร้างคอนเทนเนอร์ขึ้นมาใหม่ โดยที่ named volume และข้อมูลทั้งหมดของคุณจะยังคงอยู่:
docker compose pull
docker compose up -dVaultwarden มีการออกรุ่นใหม่บ่อยครั้ง ให้ติดตาม บันทึกการเปลี่ยนแปลงของโครงการ แทนการล็อกเวอร์ชันของแพตช์ เนื่องจากบางรุ่นอาจมีบันทึกเกี่ยวกับการย้ายข้อมูล (migration) ให้สำรองข้อมูลใหม่ก่อนการอัปเกรดเวอร์ชันหลักเสมอ คุณสามารถย้อนกลับได้โดยการกู้คืนไฟล์ tarball ลงใน volume ใหม่
FAQ
Vaultwarden เหมือนกับ Bitwarden หรือไม่?
Vaultwarden เป็นเซิร์ฟเวอร์อิสระที่ทำงานร่วมกันได้ ไม่ใช่เซิร์ฟเวอร์อย่างเป็นทางการ Vaultwarden เขียนขึ้นใหม่โดยใช้ภาษา Rust เพื่อรองรับ API ของเซิร์ฟเวอร์ Bitwarden ทำให้แอปพลิเคชันไคลเอนต์อย่างเป็นทางการ ทั้งบนเดสก์ท็อป มือถือ เบราว์เซอร์ และ CLI สามารถใช้งานร่วมกันได้โดยใช้ทรัพยากรน้อยกว่า stack ของทางการมาก รูปแบบของ vault นั้นเหมือนกัน คุณจึงสามารถย้ายข้อมูลไปมาระหว่างกันได้ด้วยการส่งออกและนำเข้าข้อมูล
ฉันจำเป็นต้องใช้ HTTPS จริงหรือ หรือสามารถรันผ่าน http บน LAN ได้?
คุณจำเป็นต้องใช้ HTTPS สำหรับทุกกรณี ยกเว้นการทดสอบ localhost เท่านั้น Web vault และส่วนขยายของ Bitwarden ใช้ Web Crypto API ของเบราว์เซอร์ ซึ่งจะทำงานได้เฉพาะในบริบทที่ปลอดภัยเท่านั้น ดังนั้นหากใช้ http ธรรมดา ไคลเอนต์จะแสดงข้อผิดพลาด Cannot read properties of undefined และไม่สามารถล็อกอินได้ ที่อยู่ http เดียวที่ใช้งานได้คือ http://localhost ซึ่งเป็นเหตุผลว่าทำไมการทดสอบการกู้คืนในขั้นตอนที่ 8 จึงต้องใช้ SSH tunnel
ฉันจะป้องกันไม่ให้คนแปลกหน้าลงทะเบียนบนเซิร์ฟเวอร์ของฉันได้อย่างไร?
ให้ตั้งค่า SIGNUPS_ALLOWED: "false" ในไฟล์ Compose และรันคำสั่ง docker compose up -d ทันทีหลังจากสร้างบัญชีของคุณเอง หลังจากนั้นให้เพิ่มผู้ใช้ใหม่ผ่านปุ่ม Invite User ใน /admin ซึ่งจำเป็นต้องตั้งค่า SMTP เพื่อให้ผู้ใช้ได้รับลิงก์คำเชิญ ตรวจสอบรายชื่อผู้ใช้ในระบบเป็นระยะเพื่อยืนยันว่าไม่มีบัญชีที่ไม่คาดคิดปรากฏขึ้น
ฉันจะสำรองข้อมูล Vaultwarden vault ของฉันได้อย่างไร?
ให้หยุดคอนเทนเนอร์ชั่วคราวแล้วทำสำเนาไฟล์ทั้งหมดใน volume vw-data รวมถึง db.sqlite3, attachments/, sends/, config.json และไฟล์ rsa_key.* จากนั้นคัดลอกไฟล์สำรองออกจากเซิร์ฟเวอร์ โดยแนะนำให้ทำผ่าน cron ทุกคืน การคัดลอกไฟล์ SQLite ขณะที่เซิร์ฟเวอร์กำลังทำงานมีความเสี่ยงที่ข้อมูลจะเสียหาย ดังนั้นควรสำรองข้อมูลในขณะที่หยุดบริการไว้ สิ่งสำคัญที่สุดคือ ให้ทดสอบกู้คืนข้อมูลไปยังคอนเทนเนอร์สำรองและลองล็อกอิน เพื่อให้มั่นใจว่าข้อมูลสำรองนั้นใช้งานได้จริงก่อนที่คุณจะพึ่งพามัน
การโฮสต์รหัสผ่านด้วยตนเองปลอดภัยจริงหรือ?
ปลอดภัย หากคุณทำตามสามสิ่งที่คู่มือนี้ครอบคลุม ได้แก่ การใช้ HTTPS จริง, การปิดการลงทะเบียนพร้อมตั้งค่า admin token ที่รัดกุม และการทดสอบการสำรองข้อมูล vault ของคุณจะถูกเข้ารหัสจากฝั่งไคลเอนต์ด้วยรหัสผ่านหลักของคุณ ดังนั้นแม้แต่เซิร์ฟเวอร์ก็ไม่เห็นรหัสผ่านของคุณในรูปแบบปกติ และไฟล์ db.sqlite3 ที่ถูกขโมยไปก็ไร้ประโยชน์หากไม่มีรหัสผ่านหลัก สิ่งที่ต้องแลกคือการอัปเดตแพตช์และการสำรองข้อมูลกลายเป็นความรับผิดชอบของคุณ ซึ่งเป็นเหตุผลว่าทำไม Fail2ban และขั้นตอนการกู้คืนข้อมูลจึงเป็นสิ่งที่ขาดไม่ได้ เมื่อตั้งค่าสิ่งเหล่านี้เรียบร้อยแล้ว การศึกษาเพิ่มเติมเกี่ยวกับจุดที่ vault แบบ self-hosted อาจถูกโจมตี จะเป็นขั้นตอนถัดไปที่มีประโยชน์ เพราะเมื่อข้อมูลถูกเข้ารหัสจากฝั่งไคลเอนต์แล้ว สิ่งที่เหลือที่ต้องป้องกันคือ admin token และไฟล์สำรองข้อมูลนั่นเอง