"비밀번호 뭐로 했지?"를 검색창에 쳐본 적이 있다면 이 글의 독자다. 비밀번호 관리자는 이 문제의 정답인데, 좋은 제품일수록 구독료를 요구한다. Vaultwarden은 그 답을 내 서버에 두는 방법이다 — 유명한 Bitwarden의 경량 호환 서버로, 공식 앱과 확장 프로그램을 그대로 쓰면서 구독료는 없다.
필요한 것은 서버 한 대와, 딱 하나 기억할 마스터 비밀번호뿐이다.
Bitwarden은 오픈소스 비밀번호 관리자 중 가장 널리 쓰이는 제품이고, 공식 클라우드와 셀프호스팅 서버를 제공한다. Vaultwarden은 Bitwarden 서버를 Rust로 가볍게 재구현한 호환 서버다. 공식 서버가 무거운 .NET 기반인 반면, Vaultwarden은 라즈베리파이에서도 돌아간다.
중요한 포인트: 클라이언트는 공식 Bitwarden 것을 그대로 쓴다. 브라우저 확장, 모바일 앱, 데스크톱 앱 — 전부 Bitwarden 공식 제품에 서버 주소만 내 것으로 바꾸면 된다. Vaultwarden 전용 앱 같은 것은 없다.
| 항목 | Bitwarden 공식 클라우드 | Vaultwarden |
|---|---|---|
| 비용 | 무료(기능 제한) / 월 구독 | 무료 (서버 비용만) |
| 유료 기능 | 구독 필요 (TOTP 저장, 첨부파일 등) | 대부분 기본 제공 |
| 클라이언트 | 공식 앱·확장 | 동일한 공식 앱·확장 |
| 데이터 위치 | Bitwarden 클라우드 | 내 서버 |
| 관리 책임 | 없음 | 내 몫 (백업·업데이트) |
볼트의 보안 구조는 Bitwarden과 동일하다. 모든 항목은 마스터 비밀번호에서 파생된 키로 클라이언트에서 암호화된 뒤 서버에 저장된다. 서버 관리자라 해도 볼트 내용을 볼 수 없는 구조다. 다만 마스터 비밀번호를 잃으면 복구가 불가능하므로, 이것만큼은 신중하게 관리해야 한다.
구성이 아주 단순한 게 장점이다. 컨테이너 하나, 데이터 폴더 하나.
flowchart LR
subgraph DEV["기기"]
BR["브라우저 확장 - 자동완성"]
MA["모바일 앱 - 자동완성"]
WB["웹 볼트 - 관리"]
end
subgraph SRV["Docker 서버"]
VW["Vaultwarden 컨테이너 - Bitwarden 호환 API"]
end
DT[("data 폴더 - DB·첨부파일")]
BR -->|HTTPS| VW
MA -->|HTTPS| VW
WB -->|HTTPS| VW
VW --> DT
리버스 프록시 뒤에 붙여 HTTPS로 서비스하는 것이 정석이다. Bitwarden 클라이언트들은 암호화 연산을 위해 HTTPS를 필수로 요구하기 때문에, HTTP로만 띄우면 앱·확장 연결 자체가 안 된다.
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://vault.example.com" # 실제 접속 도메인 (HTTPS 필수)
SIGNUPS_ALLOWED: "true" # 내 계정을 만든 뒤 false로 바꾼다
volumes:
- /opt/docker/vaultwarden/data:/data
ports:
- "8222:80"
기동 후 프록시로 도메인을 연결하고, 웹 볼트에서 내 계정을 만든다. 그리고 바로 SIGNUPS_ALLOWED를 false로 바꿔 재시작한다. 이걸 방치하면 도메인을 아는 누구나 내 서버에 가입할 수 있다.
/admin 경로에 관리자 설정 화면이 있는데, ADMIN_TOKEN을 설정해두지 않으면 위험하다. 토큰은 평문보다 해시로 넣는 방식이 권장된다:
docker exec -it vaultwarden /vaultwarden hash
# 토큰을 입력하면 argon2 해시가 출력된다
출력된 해시를 ADMIN_TOKEN 환경변수에 넣고 재시작하면, /admin에서는 해시의 원문(내가 정한 문자열)으로만 접근할 수 있다.
웹 볼트 설정에서 2FA(TOTP 앱, 이메일 등)를 켠다. 볼트는 모든 계정의 열쇠인 만큼 마스터 비밀번호가 새더라도 2FA가 버티는 구조를 만들어두는 게 맞다.
data 폴더 안의 db.sqlite3(볼트 본체)와 attachments(첨부)만 있으면 전부 복구된다. cron으로 주기적으로 다른 디스크에 복사하자. SQLite라 파일 복사로도 되지만, 안전하게 하려면 컨테이너를 짧게 멈추고 복사하는 방식이 가장 확실하다.
가입 차단은 첫날 할 것. 도메인이 검색엔진에 노출되면 사설 서버임을 알고 가입을 시도하는 봇이 실제로 온다. SIGNUPS_ALLOWED=false 후에도 관리자 화면에서 초대 방식으로 원하는 사람만 넣을 수 있으니 불편함은 없다.
비밀번호 이전은 확장이 다 해준다. 브라우저에 저장돼 있던 기존 비밀번호는 확장 메뉴의 내보내기 → 볼트 가져오기로 옮긴다. 옮긴 뒤 사이트별로 비밀번호 교체를 시작하면, 자동생성+자동저장 루프에 빠져 며칠 안에 주요 계정이 전부 강한 고유 비밀번호로 바뀐다.
보내기(Send) 기능이 은근히 유용하다. 파일이나 텍스트를 링크로 안전하게 전달하는 기능인데, "비밀번호를 메신저로 알려줘야 하는" 상황에서 만료일 걸린 링크로 대체할 수 있다.
| 증상 | 원인 | 해결 |
|---|---|---|
| 확장·앱이 로그인 안 된다 | DOMAIN이 HTTP이거나 불일치 | HTTPS 적용 + DOMAIN 값 일치 |
| 모르는 계정이 가입돼 있다 | 가입 차단 미설정 | SIGNUPS_ALLOWED=false |
| /admin 접근이 열려 있다 | ADMIN_TOKEN 미설정 | 해시 토큰 설정 |
| 큰 첨부파일이 실패한다 | 프록시 업로드 제한 | 프록시 body size 상향 |
| 백업이 막막하다 | data 폴더 구조 미숙지 | db.sqlite3 + attachments 복사 |
볼트 항목은 클라이언트에서 마스터 비밀번호 기반 키로 암호화된 상태로 저장된다. 서버 DB를 통째로 가져가도 마스터 비밀번호 없이 복호화할 수 없는 구조다. 서버 관리자가 해야 할 일은 HTTPS 유지, 2FA 권장, 백업 — 이 세 가지다.
개인·소규모 팀 기준으로 충분하다. 공식 클라이언트를 그대로 쓰므로 사용 경험은 같고, 나중에 공식 서버로 옮길 때도 JSON 내보내기/가져오기로 이동 가능하다. 대규모 조직이라면 공식 서버나 클라우드 쪽이 관리 포인트가 적다.
data 폴더의 db.sqlite3와 attachments를 주기적으로 복사하면 전부 보존된다. 복원은 폴더를 원위치하고 컨테이너를 띄우는 것으로 끝난다.
카드 정보, 신분증·계좌 같은 식별 정보, 보안 노트, 그리고 Send(링크 전달)까지 지원한다. 첨부파일도 볼트 항목에 붙일 수 있다.
서버 한 대로 충분하다 연재 목차 — 시리즈 소개 · 1화. Home Assistant · 2화. Frigate + go2rtc · 3화. Seafile 13 · 4화. Immich
실제 운영 서버에서 브라우저·모바일·가족 공유까지 사용 중인 구성입니다.
| 이전 | 4화. Immich — 구글 포토 대신 내 서버에 사진 백업하기 (셀프호스팅) | 2026-09-30 | |
|---|---|---|---|
| 다음 | 6화. Mailcow — 우리 도메인 이메일을 직접 발급하는 메일 서버 | 2026-09-30 |