Securing the Vaultwarden ADMIN_TOKEN with an Argon2 Hash in Docker

Run Vaultwarden with Argon2-Hashed ADMIN_TOKEN in Docker

* This page contains promotional content

Making the Vaultwarden ADMIN_TOKEN secure with an Argon2 hash

Introduction

When you open the Vaultwarden admin page, the following warning is sometimes displayed.

You are using a plain text `ADMIN_TOKEN` which is insecure.
Please generate a secure Argon2 PHC string by using `vaultwarden hash` or `argon2`.

Since Vaultwarden 1.28.0, hashing ADMIN_TOKEN with Argon2 is recommended. In this article I explain how to configure ADMIN_TOKEN securely in a Docker Compose environment.

Configuration before the change

My environment uses Traefik

docker-compose.yml (before)

version: "3.6"
services:
  bitwarden:
    environment:
      - DOMAIN=https://vault.example.com
      - TZ=Asia/Tokyo
      - SIGNUPS_ALLOWED=false
      - INVITATIONS_ALLOWED=false
      - ADMIN_TOKEN=your_plain_text_admin_token_here
      - WEBSOCKET_ENABLED=true
    image: vaultwarden/server:latest
    restart: always
    volumes:
      - ./bw_data:/data
    networks:
      - traefik-network

networks:
  traefik-network:
    external: true

With this setup, ADMIN_TOKEN is written directly in docker-compose.yml as plain text, which is a security problem.

Configuration after the change

1. docker-compose.yml (after)

Change it so that the environment variables are managed in a .env file.

version: "3.6"
services:
  bitwarden:
    env_file:
      - .env
    image: vaultwarden/server:latest
    restart: always
    volumes:
      - ./bw_data:/data
    networks:
      - traefik-network

networks:
  traefik-network:
    external: true

2. Creating the .env file

# Vaultwarden environment variables

DOMAIN=https://vault.example.com
TZ=Asia/Tokyo
SIGNUPS_ALLOWED=false
INVITATIONS_ALLOWED=false
ADMIN_TOKEN=$$argon2id$$v=19$$m=65540,t=3,p=4$$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$$YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
DISABLE_ADMIN_TOKEN=false
WEBSOCKET_ENABLED=true

Important: In a Docker Compose .env file, $ has to be escaped as $$.

3. Setting up .gitignore

The .env file contains sensitive information, so configure Git not to track it.

*
!docker-compose.yml
!.env

How to generate the Argon2 hash

Method 1: Use the vaultwarden hash command

# Run it inside the running container
docker exec -it bitwarden-bitwarden-1 /vaultwarden hash

# Or run it in a temporary container
docker run --rm -it vaultwarden/server:latest /vaultwarden hash

When you run the command, a prompt asks you to enter the password twice.

Generate an Argon2id PHC string using the 'bitwarden' preset:

Password: 
Confirm password: 

ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'

To generate a stronger hash, use the --preset owasp option.

docker exec -it bitwarden-bitwarden-1 /vaultwarden hash --preset owasp

Writing it into the .env file

When you put the generated PHC string into the .env file, replace $ with $$.

Generated hash (example):

$argon2id$v=19$m=65540,t=3,p=4$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

What you write in the .env file:

ADMIN_TOKEN=$$argon2id$$v=19$$m=65540,t=3,p=4$$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$$YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Checking config.json

When you save settings from the Vaultwarden admin page, bw_data/config.json is generated. If admin_token exists in that file it takes precedence over the environment variable, so this needs attention.

Removing admin_token from config.json

# Check the current setting
cat bw_data/config.json | jq '.admin_token // "not set"'

# Remove admin_token
jq 'del(.admin_token)' bw_data/config.json > config.json.tmp && mv config.json.tmp bw_data/config.json

Restarting the container

Important: When you change environment variables, docker compose restart may not apply them. You need to recreate the container.

# Recreate the container (recommended)
docker compose down && docker compose up -d

# Check that the environment variable is set correctly
docker compose exec bitwarden env | grep ADMIN_TOKEN

If you forget the password, or want to reset it

If you forget the admin page password or want to set it again, you can deal with it in the following steps.

Step 1: Temporarily change the .env file back to plain text

# Edit the .env file
ADMIN_TOKEN=new_temporary_password_here

Step 2: Remove admin_token from config.json

If an old hash remains in config.json it takes precedence over the environment variable, so remove it.

jq 'del(.admin_token)' bw_data/config.json > config.json.tmp && mv config.json.tmp bw_data/config.json

Step 3: Recreate the container

docker compose down && docker compose up -d

Step 4: Log in to the admin page with the new password

Open the admin page (https://vault.example.com/admin) in a browser and log in with the password you set in step 1.

Step 5: Generate a new hash

docker exec -it bitwarden-bitwarden-1 /vaultwarden hash

Enter the new password and copy the generated PHC string.

Step 6: Update the .env file

Write the generated hash into the .env file (replacing $ with $$).

ADMIN_TOKEN=$$argon2id$$v=19$$m=65540,t=3,p=4$$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$$YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

Step 7: Recreate the container

docker compose down && docker compose up -d

That sets an ADMIN_TOKEN hashed from the new password.

Troubleshooting

If the warning does not disappear

  1. Check config.json: check whether admin_token is still there in plain text
  2. Check the escaping: check whether $ is escaped as $$ in the .env file
  3. Recreate the container: run docker compose down && docker compose up -d

If you cannot log in

  1. Check the environment variable:

    docker compose exec bitwarden env | grep ADMIN_TOKEN
  2. Recreate the container:

    docker compose down && docker compose up -d
  3. Check the logs:

    docker compose logs bitwarden --tail 30 | grep -i "admin\|token"

Summary

ItemBeforeAfter
Managing environment variablesWritten directly in docker-compose.ymlManaged in a .env file
ADMIN_TOKENPlain textArgon2 hash
SecurityLowHigh

With the Argon2 hash, even if ADMIN_TOKEN were to leak, recovering the original password becomes difficult.

An operational best practice: enable the admin page only when you need it

The Admin Page allows powerful configuration and operational actions, but by the same token it is a place attackers are likely to target. If you do not normally do admin work, I recommend disabling the Admin page and enabling it temporarily only when you really need it.

Flow for temporarily enabling the admin page

  1. Set ADMIN_TOKEN temporarily in the .env file
  2. Once the admin work is finished, comment out ADMIN_TOKEN or leave it empty to disable it
  3. Apply it with docker compose down && docker compose up -d if necessary

Example: a .env that temporarily enables the admin page

# Enabled only while doing admin work
ADMIN_TOKEN=$$argon2id$$v=19$$m=65540,t=3,p=4$$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$$YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

After the admin work (disabled)

# ADMIN_TOKEN=   ← comment it out or leave it blank

Points to keep in mind

  • If you remove ADMIN_TOKEN or leave it blank and restart, the admin page becomes inaccessible
  • Enabling it only when you need it keeps the risk to a minimum
  • For real operation, managing the permissions of the .env file itself and controlling access to it are also strongly recommended

This kind of “enable it only when you need it” practice is an effective security measure for all sorts of services, not just Vaultwarden.

References