SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor

VPS에 Vaultwarden으로 비밀번호 관리자 구축하기

Docker를 활용해 Vaultwarden을 VPS에 설치하는 방법을 안내합니다. HTTPS 설정, 관리자 토큰 구성, Fail2ban 적용 및 데이터 백업 등 안전한 운영을 위한 필수 가이드를 확인하여 나만의 비밀번호 관리자를 직접 호스팅해 보십시오.

구축할 시스템

사용자가 완전히 소유하는 비밀번호 관리자입니다. HTTPS를 종료하는 리버스 프록시 뒤에서 소형 컨테이너로 실행되는 Vaultwarden을 구축하고, 휴대폰, 노트북, 브라우저의 공식 Bitwarden 앱을 이 서버에 연결합니다. Vaultwarden은 Rust로 Bitwarden 서버 API를 재구현한 것이며 bitwarden.com과 동일한 프로토콜을 사용합니다. 따라서 서버를 최신 상태로 유지하기만 하면 공식 클라이언트를 그대로 사용할 수 있습니다(새 클라이언트 릴리스는 그에 맞는 Vaultwarden 릴리스가 필요할 때가 있습니다. 2026.8.0 이상의 클라이언트에는 1.37.2 버전이 필요합니다). 하지만 공식 스택과 달리 여러 컨테이너를 띄울 필요 없이 약 100 MB의 RAM만으로 실행 가능합니다.

설치 과정은 12줄 정도의 Compose 파일로 충분합니다. 실제로 중요하며 문제가 발생하기 쉬운 세 가지 사항은 다음과 같습니다. 웹 볼트를 로드하기 전에 반드시 TLS가 설정되어 있어야 하며, 본인 계정을 생성한 즉시 공개 가입 기능을 닫아야 합니다. 또한 데이터 볼륨은 반드시 백업하고 복구 테스트를 거쳐야 합니다. 해당 디렉터리 하나에 모든 비밀번호가 저장되기 때문입니다.

사전 요구 사항 및 주의 사항

  • Docker Engine과 Compose 플러그인이 설치된 Ubuntu 24.04 KVM 기반의 VPS가 필요하며, root 또는 sudo 권한이 있어야 합니다. 512 MB RAM으로도 충분히 운영 가능하며, 1 GB라면 여유롭습니다. 이 서비스는 가장 가벼운 서비스 중 하나로, 직접 호스팅할 가치가 있는 서비스 목록 상단에 위치합니다. 다만, 다른 서비스를 함께 운영한다면 그에 맞춰 서버 사양을 결정하십시오. PhotoPrism이나 Immich와 같은 사진 라이브러리를 같은 VPS에 올리면 RAM 사용량이 기가바이트 단위로 늘어나지만, Vaultwarden은 거의 영향을 주지 않습니다. 나중에 추가할 미디어 프론트엔드도 마찬가지입니다. Jellyfin 라이브러리를 90년대 비디오 대여점처럼 꾸미는 것은 상시 가동되는 컨테이너를 하나 더 추가하는 것이며, 트랜스코딩을 위한 여유 자원도 고려해야 합니다.
  • VPS를 가리키는 A 레코드(IPv6를 사용한다면 AAAA 레코드)가 설정된 도메인이 필요합니다. vault.example.com TLS 인증서는 해당 도메인 이름으로 발급되므로, 시작하기 전에 DNS 설정이 완료되어야 합니다.
  • 80번과 443번 포트는 인터넷에 개방되어야 하며, 반드시 리버스 프록시에서 종료되어야 합니다. Vaultwarden이 직접 처리하게 해서는 안 됩니다. 80번 포트는 ACME 인증서 챌린지와 HTTP를 HTTPS로 리다이렉트하는 용도로만 사용됩니다.
  • 가장 중요한 주의 사항: Bitwarden 클라이언트는 HTTPS가 아닌 서버와는 통신하지 않습니다. "먼저 HTTP로 테스트"하는 방법은 없으며, 그 이유는 다음 섹션에서 다루는 구체적인 사유 때문입니다.

공식 Bitwarden 스택 대신 Vaultwarden을 사용하는 이유

동일한 클라이언트를 사용하면서 훨씬 가벼운 리소스를 소모합니다. 공식 Bitwarden 셀프 호스팅 배포판은 여러 컨테이너(MSSQL Express, Identity, Api, Admin 등)의 묶음으로 제공되며 최소 2 GB의 RAM을 요구합니다. Bitwarden은 최소 200 MB의 RAM을 사용하는 단일 Docker 이미지 형태의 Bitwarden lite도 제공하지만, 이는 개인용 및 홈랩 환경을 위한 것이며 기업용은 아닙니다. 또한 데이터베이스와 백업은 전적으로 사용자가 관리해야 합니다. Vaultwarden은 기본적으로 모든 데이터를 SQLite 데이터베이스에 저장하는 단일 바이너리이며, 유휴 상태에서 수십 MB 정도의 메모리만 점유합니다. 개인, 가족 또는 소규모 팀에게는 명확한 선택지이며, Bitwarden API를 충실히 구현했기 때문에 bitwarden.com과 데이터 호환성이 유지됩니다.

포기해야 할 부분은 엔터프라이즈 기능의 대부분입니다. SCIM 프로비저닝은 지원하지 않으며(1.35.0 버전부터 OpenID Connect SSO 지원이 추가됨), 운영자가 직접 패치, HTTPS 설정, 백업을 수행해야 합니다. 이 가이드는 바로 그 세 가지 작업에 대해 다룹니다.

HTTPS가 선택 사항이 아닌 이유

Bitwarden 웹 볼트와 브라우저 확장 프로그램은 Web Crypto API(window.crypto.subtle)를 사용하여 브라우저에서 암호화 키를 생성합니다. 브라우저는 보안 컨텍스트(HTTPS) 또는 http://localhost 및 http://127.0.0.1와 같은 루프백 주소의 특수한 경우에만 crypto.subtle을 노출합니다. 일반 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를 먼저 설정해야 하며, 잠시 확인하는 경우라도 http를 통해 볼트를 열어서는 안 됩니다.

1단계, DNS 및 리버스 프록시 (TLS 우선)

레코드를 VPS로 지정하고 올바른 주소로 해석되는지 확인합니다.

dig +short vault.example.com

출력되는 줄은 반드시 VPS IP여야 합니다. 비어 있거나 잘못된 경우 DNS를 수정하고 TTL이 만료될 때까지 기다려야 합니다. 이름이 해석되지 않으면 인증서 발급이 실패합니다.

HTTPS 프런트엔드를 위해 이 가이드에서는 Traefik을 사용합니다. Traefik은 Let's Encrypt 인증서를 자동으로 발급 및 갱신하며 Compose에 바로 통합됩니다. 아직 실행 중이 아니라면 먼저 Traefik 리버스 프록시 및 자동 TLS 설정을 따르십시오. 이 과정에서 외부 Docker 네트워크(아래 proxy)와 ACME 리졸버(letsencrypt)가 생성되며, Vaultwarden 서비스가 여기에 연결됩니다. 수동으로 발급받은 인증서를 사용하는 일반 nginx도 Vaultwarden 측면에서는 동일하게 작동합니다.

Traefik 대신 nginx와 Certbot을 선호하십니까? Vaultwarden을 127.0.0.1:8080에 배치하고(서비스에 ports: ["127.0.0.1:8080:80"]을 추가하고 Traefik 레이블을 제거), 인증서를 발급받아 프록시를 설정하십시오. 인증서 관련 내용은 Certbot과 nginx를 이용한 Let's Encrypt 인증서 발급에서 다룹니다. 중요한 추가 사항은 알림 경로에 대한 WebSocket 업그레이드입니다.

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 프로젝트 이름을 지정하며, 결과적으로 데이터 볼륨 이름인 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를 통해서만 접근할 수 있습니다. 호스트에서 포트를 공개하는 것은 사용자가 실수로 HTTP를 통해 볼트를 서비스하게 만드는 원인이 됩니다. 또한 DOMAIN는 반드시 전체 공개 HTTPS URL이어야 합니다. 이 값은 첨부 파일 링크, WebAuthn 2FA, 알림 엔드포인트에 포함됩니다. 따라서 잘못된 값이나 http 값을 사용하면 사이트가 로드되더라도 해당 기능들이 작동하지 않습니다. latest 태그는 일반적으로 권장되는 '절대 latest하지 마라'는 규칙의 의도적인 예외입니다. Vaultwarden은 안정적인 릴리스를 단일 롤링 이미지로 배포하며, :testing은 별도의 사전 릴리스 채널입니다. 따라서 의도적으로 업데이트를 수행하고 이미지를 가져오기(pull) 전에 릴리스 노트를 훑어보아야 합니다. 하지만 이 예외는 제한적입니다. 대부분의 장기 실행 컨테이너는 정확한 태그로 고정하는 것이 좋습니다. 그래야 동일한 VPS에서 호스팅되는 상시 가동 에이전트가 재부팅이나 이미지 업데이트 시에도 예측 가능하게 유지됩니다.

서비스를 시작하고 로그를 확인합니다.

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 웹 볼트가 나타나야 합니다.

3단계, 강력한 ADMIN_TOKEN 및 $$ 함정

ADMIN_TOKEN는 인스턴스의 모든 사용자와 설정을 읽을 수 있는 패널인 /admin을 보호하므로, root 암호처럼 취급해야 합니다. 두 가지 형식을 사용할 수 있습니다.

단순한 형식은 이미 openssl rand -base64 48로 생성한 임의의 문자열입니다. base64에는 $이 포함되지 않으므로, 이스케이프 처리 없이 .env에 바로 입력할 수 있습니다.

강화된 형식은 Argon2 PHC 해시를 사용하는 것으로, 일반 텍스트 토큰이 디스크에 저장되지 않습니다. 동일한 이미지에서 하나를 생성하십시오.

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

두 번 입력을 요청한 뒤 $argon2id$v=19$...으로 시작하는 문자열을 출력합니다. 여기서 많은 사용자가 한 시간씩 허비하는 함정이 있습니다. Docker Compose는 $을 변수 보간(variable interpolation)으로 처리하므로, Compose 파일의 environment: 아래에 해시를 직접 붙여넣을 경우 모든 $을 $$로 두 번 입력해야 합니다. 해당 값을 따옴표로 감싸는 것은 괜찮지만, 이스케이프 처리가 핵심입니다.

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

해시를 .env에 보관하려면 규칙이 바뀝니다. 단일 $ 기호를 그대로 두고 전체 값을 작은따옴표(ADMIN_TOKEN='$argon2id$v=19$...')로 감싸면, Compose 파일의 기존 ${ADMIN_TOKEN} 줄이 이를 가져옵니다. .env 내의 $는 절대 두 번 입력하지 마십시오. Compose 파일 자체에 단일 $ 기호를 남겨두면, Compose는 The "argon2id" variable is not set 경고를 표시하고 토큰을 비워버리며, 이후 /admin은 올바른 암호를 거부하게 됩니다. docker compose up -d를 실행하고, 프롬프트에 입력했던 일반 텍스트는 본인의 암호 관리자에 보관하십시오.

4단계, 계정 등록 및 출입문 잠금

SIGNUPS_ALLOWED: "true"를 사용하여 https://vault.example.com을 열고 Create account를 클릭한 뒤, 이메일과 강력한 마스터 비밀번호로 계정을 등록합니다. 이 마스터 비밀번호는 복구할 수 없으며 재설정 기능도 없으므로, 먼저 안전하고 영구적인 곳에 보관하십시오.

이제 출입문을 잠급니다. Compose 파일을 편집하여 회원가입 기능을 끕니다.

      SIGNUPS_ALLOWED: "false"

docker compose up -d을 사용하여 설정을 다시 적용합니다. 이는 미룰 수 없는 보안 강화 작업입니다. 회원가입 기능을 열어두면 URL을 찾아낸 누구나(크롤러 포함) 서버에 계정을 생성할 수 있습니다. 이들이 사용자의 볼트를 읽을 수는 없지만, 서버 자원을 소모하게 되며 개인 인스턴스가 공개 서비스로 변질됩니다. 회원가입 기능을 끄지 않았다는 신호는 /admin 명령을 실행했을 때 본인이 생성하지 않은 계정들이 목록에 나타나는 것으로 확인할 수 있습니다.

나중에 가족이나 팀원을 추가하려면 공개 회원가입을 다시 열지 말고 /admin의 Invite User 버튼을 사용하십시오. 해당 기능을 사용하려면 초대받는 사람이 링크를 받을 수 있도록 SMTP 설정이 필요합니다.

5단계, /admin 접속

https://vault.example.com/admin로 접속하여 일반 텍스트 관리자 토큰(해시값이 아닌, 무작위 문자열 또는 직접 설정한 비밀번호)을 입력합니다. 관리자 페이지 내에서 사용자 목록 확인, 설정 조정, 테스트 이메일 발송, 데이터베이스 스냅샷 생성이 가능합니다.

페이지에 The admin panel is disabled, please configure the 'ADMIN_TOKEN' variable to enable it이라는 메시지가 표시된다면 ADMIN_TOKEN이 비어 있거나 설정되지 않은 상태입니다. 이 경우 관리자 패널이 완전히 비활성화되며, 패널을 사용할 필요가 없다면 이는 정상적인 설정입니다. 페이지는 로드되지만 토큰이 거부된다면 아래 실패 목록의 $$ 이스케이프 문제를 확인하십시오. 토큰을 잊어버렸다면 복구 프롬프트가 없으므로 .env 또는 Compose 파일을 수정하여 새로운 토큰을 설정한 뒤 docker compose up -d을 수행하십시오.

6단계, Bitwarden 클라이언트 연결

모든 공식 클라이언트는 자체 호스팅 서버를 가리키도록 설정할 수 있습니다. 일반 앱 스토어에서 Bitwarden 데스크톱, 모바일 또는 브라우저 클라이언트를 설치하십시오. 별도의 Vaultwarden 빌드는 필요하지 않습니다.

로그인하기 전에 로그인 화면의 설정 톱니바퀴 아이콘(Self-hosted 또는 Region → Self-hosted로 표시됨)을 열고, Server URL을 https://vault.example.com으로 설정한 뒤 저장하십시오. 그 후 등록한 이메일과 마스터 비밀번호로 로그인하면 클라이언트가 즉시 연결되어 자격 증명을 채우거나 저장할지 묻습니다.

클라이언트에 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 푸시 문제이며, 아래에서 다룹니다.

7단계, 로그인 엔드포인트를 위한 Fail2ban jail 설정

Vaultwarden은 모든 로그인 실패 기록을 LOG_FILE에 설정된 파일에 남기며, 이는 무차별 대입 공격 방어에 필요한 핵심 정보입니다. Fail2ban을 아직 사용하지 않는다면 Fail2ban SSH 보안 가이드에서 설치 및 기본 설정을 확인하십시오. 여기서는 Vault를 위한 jail을 하나 추가합니다.

먼저 Fail2ban이 로그를 읽을 수 있도록 호스트에서 명명된 볼륨(named volume)의 위치를 찾습니다.

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

이 명령은 /var/lib/docker/volumes/vaultwarden_vw-data/_data와 같은 경로를 출력하며, 로그 파일은 그 내부의 vaultwarden.log에 위치합니다. 필터를 생성합니다.

# /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 관련 세부 사항에 달려 있습니다. 첫째, 로그에 모든 실패 시도마다 IP: 127.0.0.1 또는 프록시의 주소가 기록된다면 Vaultwarden이 프록시 자체를 차단하고 있는 것입니다. 이때 IP_HEADER을 프록시가 실제로 전달하는 헤더로 설정하십시오(Traefik은 X-Forwarded-For, 위에서 설정한 nginx 블록은 X-Real-IP, Cloudflare 뒤에 있다면 CF-Connecting-IP). 1.37.4 버전부터는 X-Forwarded-For을 통해 Vaultwarden이 IP_HEADER_TRUSTED_PROXIES(기본값 local, 동일 호스트나 Docker 네트워크상의 프록시를 포함)에 속하지 않는 가장 오른쪽 주소를 가져옵니다. 따라서 프록시 앞에 CDN이 있다면 해당 주소도 목록에 추가해야 합니다. 둘째, 올바른 iptables 체인은 프록시에 따라 다릅니다. 포트를 노출한 컨테이너로 Traefik을 실행 중이라면 트래픽이 Docker의 FORWARD 경로를 통과하므로 위와 같이 DOCKER-USER에 차단 규칙을 적용해야 합니다. 하지만 1단계에서 호스트 nginx 옵션을 선택했다면 연결이 호스트의 INPUT 체인에서 nginx로 종료되므로 DOCKER-USER 차단 규칙은 트래픽을 감지하지 못합니다. 이 경우 chain = DOCKER-USER 줄을 삭제하여 Fail2ban이 기본값인 INPUT 체인을 사용하도록 하십시오. 셋째, 포트 기반의 기본값 대신 banaction = iptables-allports을 사용하십시오. 이 jail은 포트를 지정하지 않으며, DOCKER-USER에서 모든 포트를 차단함으로써 공격자가 서버의 모든 공개 서비스에 접근하지 못하도록 확실히 차단합니다.

8단계, 볼트 백업 및 복구 수행

vw-data 볼륨은 곧 비밀번호 관리자 그 자체입니다. 이 볼륨에는 db.sqlite3(모든 항목), attachments/ 및 sends/ 디렉터리, 로그인 세션을 서명하는 rsa_key.* 파일, 관리자 패널의 config.json이 포함되어 있습니다. 이 중 하나라도 누락된 백업은 정작 필요할 때 사용할 수 없습니다.

Vaultwarden이 데이터를 쓰는 도중에 db.sqlite3를 복사하면 파일이 절반만 기록되거나 손상될 수 있습니다. 따라서 몇 초간의 서비스 중단을 감수하고 콜드 스냅샷을 생성하십시오.

#!/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 백업을 수행하는 것입니다. 이 방식은 아카이브를 암호화하고 중복된 스냅샷을 제거합니다. 관리자 패널의 Backup Database 버튼은 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

노트북에서 ssh -L 8888:127.0.0.1:8888 you@your-vps를 사용하여 터널링을 설정하고 http://localhost:8888(또는 http://127.0.0.1:8888)에 접속하십시오. 루프백 주소는 보안 컨텍스트로 간주되므로 crypto.subtle를 사용할 수 있으며, 이 경우 예외적으로 일반 http 환경에서도 볼트 복호화가 가능합니다. 마스터 비밀번호로 로그인하여 항목이 모두 존재하는지 확인하십시오. 데이터베이스, RSA 키, 마스터 비밀번호가 모두 정상적으로 복구되었다면 새로운 VPS에서 몇 분 안에 서비스를 재구축할 수 있습니다. Ctrl-C를 눌러 컨테이너를 중지하고 /tmp/vw-restore를 삭제하십시오. 인터넷에 직접 노출해서는 안 되는 다른 관리자 UI에 접근할 때도 이 터널링 방식을 사용하십시오. 포트 5173에서 실행되는 자체 호스팅 open-kritt 보안 스캐너에 접근할 때도 같은 방식을 적용할 수 있습니다.

실패 유형 및 확인되는 메시지

브라우저 콘솔에 Cannot read properties of undefined (reading 'importKey')가 표시됩니다. 볼트가 http를 통해 로드되었으므로 crypto.subtle이 정의되지 않은 상태입니다. 반드시 https://을 통해서만 접근하고, 프록시에서 HTTP-to-HTTPS 리다이렉트를 추가하십시오.

클라이언트에 This is not a recognized Bitwarden server...이 표시됩니다. 서버 URL이 http이거나 오타가 있거나, 인증서를 신뢰할 수 없는 경우입니다. https://vault.example.com에서 유효한 자물쇠 아이콘이 표시되는지 확인한 뒤, 클라이언트의 자체 호스팅 설정에 다시 입력하십시오.

/admin에서 올바른 비밀번호를 거부합니다. Argon2 해시의 이스케이프 처리가 누락된 경우입니다. Compose 파일 내의 모든 $은 $$로 작성해야 합니다(단, .env 내의 작은따옴표 안에서는 $ 하나만 사용). 혹은 평문 비밀번호 대신 해시 값을 입력했는지 확인하십시오.

기기 간 동기화가 느리고 콘솔에 WebSocket connection to 'wss://vault.example.com/notifications/hub' failed가 표시됩니다. 프록시가 Upgrade/Connection 헤더를 전달하지 못하는 경우입니다. Traefik은 이를 자동으로 처리하지만, nginx는 1단계에서 언급한 두 개의 upgrade 라인이 필요합니다. 볼트는 정상 작동하지만 앱을 열 때만 동기화가 수행됩니다. v1.31.0부터 기존의 전용 포트 3012는 제거되었으므로 별도의 WebSocket 경로는 필요하지 않습니다.

Fail2ban이 차단을 보고하지만 공격자가 계속 연결을 시도합니다. IP_HEADER 설정이 잘못되어 127.0.0.1을 차단하고 있거나, 차단 규칙이 잘못된 iptables 체인에 위치한 경우입니다. chain = DOCKER-USER 및 banaction = iptables-allports을 설정하십시오.

업그레이드

새 이미지를 가져와 컨테이너를 다시 생성합니다. 명명된 볼륨과 모든 데이터는 유지됩니다.

docker compose pull
docker compose up -d

Vaultwarden은 잦은 주기로 릴리스됩니다. 특정 패치 버전을 고정하기보다는 프로젝트 릴리스 노트를 주기적으로 확인하십시오. 일부 릴리스에는 마이그레이션 관련 주의 사항이 포함되어 있기 때문입니다. 메이저 버전 업데이트 전에는 반드시 최신 백업을 수행하십시오. 문제가 발생하면 tarball을 새 볼륨에 복원하여 이전 상태로 되돌릴 수 있습니다.

FAQ

Vaultwarden은 Bitwarden과 같은 것인가요?

Vaultwarden은 공식 서버가 아니며, 호환되는 독립적인 서버입니다. Vaultwarden은 Rust로 Bitwarden 서버 API를 재구현했으므로, 공식 데스크톱, 모바일, 브라우저 및 CLI 클라이언트가 모두 정상적으로 작동하며 공식 스택보다 훨씬 적은 리소스를 사용합니다. 볼트 형식은 동일하므로 내보내기 및 가져오기를 통해 양방향으로 마이그레이션할 수 있습니다.

정말로 HTTPS가 필요한가요, 아니면 LAN에서 http로 실행해도 되나요?

루프백 테스트를 제외한 모든 환경에서는 HTTPS가 필요합니다. Bitwarden 웹 볼트와 확장 프로그램은 브라우저의 Web Crypto API를 사용하는데, 이는 보안 컨텍스트에서만 사용할 수 있습니다. 따라서 일반 http를 사용하면 클라이언트에서 Cannot read properties of undefined 오류가 발생하며 로그인할 수 없습니다. 작동하는 유일한 http 주소는 http://localhost 또는 http://127.0.0.1와 같은 루프백 주소뿐이며, 이것이 8단계의 복원 테스트에서 SSH 터널을 사용하는 이유입니다.

서버에 낯선 사람이 등록하지 못하게 하려면 어떻게 해야 하나요?

본인의 계정을 생성한 직후 Compose 파일에서 SIGNUPS_ALLOWED: "false"를 설정하고 docker compose up -d을 실행하십시오. 그 이후부터는 /admin의 사용자 초대(Invite User) 버튼을 통해 새로운 사람을 추가하십시오. 이 기능을 사용하려면 초대 링크를 받을 수 있도록 SMTP 설정이 필요합니다. 예상치 못한 계정이 생성되지 않았는지 관리자 사용자 목록을 주기적으로 확인하십시오.

Vaultwarden 볼트는 어떻게 백업하나요?

컨테이너를 잠시 중지하고 vw-data 볼륨 전체, db.sqlite3, attachments/, sends/, config.json 및 rsa_key.* 파일을 아카이브한 다음, 이상적으로는 매일 밤 cron 작업을 통해 해당 아카이브를 서버 외부로 복사하십시오. 서버가 실행 중일 때 SQLite 파일을 직접 복사하면 스냅샷이 손상될 위험이 있으므로 반드시 중지된 상태에서 백업하십시오. 가장 중요한 것은, 백업을 신뢰하기 전에 임시 컨테이너에 복원하여 로그인이 정상적으로 되는지 확인하는 것입니다.

비밀번호를 직접 호스팅하는 것이 실제로 안전한가요?

이 가이드에서 다루는 세 가지 사항인 실제 HTTPS 적용, 회원가입 차단 및 강력한 관리자 토큰 설정, 테스트된 백업을 수행한다면 안전합니다. 볼트는 마스터 비밀번호를 사용하여 클라이언트 측에서 암호화되므로 서버조차도 비밀번호를 평문으로 볼 수 없으며, db.sqlite3를 탈취당하더라도 마스터 비밀번호 없이는 항목을 확인할 수 없습니다. 다만 계정 이메일과 마스터 비밀번호에서 파생된 솔트 해시(salted hash)는 서버에 저장되므로, 공격자가 오프라인에서 이를 크래킹하려고 시도할 수 있습니다. 따라서 데이터베이스 파일과 그 모든 복사본을 안전하게 보호하십시오. 패치와 백업은 이제 사용자의 책임이 되며, 이것이 바로 이 가이드에서 Fail2ban과 복원 절차를 선택 사항이 아닌 필수 사항으로 다루는 이유입니다. 이러한 설정이 완료되었다면, 자체 호스팅 볼트의 공격 지점에 대한 상세 분석을 확인하는 것이 유용한 다음 단계입니다. 항목 자체가 클라이언트에서 암호화되므로, 방어해야 할 대상은 관리자 토큰과 백업 아카이브로 좁혀지기 때문입니다.