Published on

How to Reset Your CapRover Password (Without Breaking Your Config)

You forgot your CapRover dashboard password. You SSH into the server and look for a way to reset it. You find the hashedPassword field in /captain/data/config-captain.json and think: generate a bcrypt hash, paste it in, restart. Done.

That won't work. CapRover doesn't hash your password directly. It hashes encryptionKey + password, where encryptionKey is derived from a Docker secret unique to your server. A hash generated anywhere outside the container will never match.

This guide explains exactly how CapRover hashes passwords, why the standard approach fails, and how to reset your password with a single command that runs entirely inside the container.

Prerequisites

  • SSH access to your CapRover server as root
  • CapRover running as a Docker Swarm service (the default setup)

Why a Plain bcrypt Hash Won't Work

When CapRover stores your password, it doesn't call bcrypt(password). It calls:

bcrypt(encryptionKey + password)

The encryptionKey is built from two parts:

encryptionKey = captain-salt + "captain"

captain-salt is a random UUID generated when CapRover first installs. It lives in Docker Swarm as a secret named captain-salt and is only readable from inside the captain container at /run/secrets/captain-salt.

This is confirmed in CapRover's Authenticator.ts, which hashes passwords like this:

bcrypt.hashSync(self.encryptionKey + newPass, bcrypt.genSaltSync(10))

And compares them like this:

bcrypt.compareSync(self.encryptionKey + password, savedHashedPassword)

If you generate a bcrypt hash of your password using an online tool or a local script, captain-salt is missing from the input. CapRover will hash captain-salt + "captain" + password during login and compare it against your hash of just password. They will never match.

Why the Official Reset Method Can Go Wrong

The official troubleshooting page recommends a different approach: scale CapRover down to zero, delete hashedPassword from the config using jq, set a DEFAULT_PASSWORD environment variable, and scale back up.

The problem: if jq isn't installed on your server, the command doesn't fail cleanly. It produces a corrupted or empty JSON file. When CapRover restarts with a blank config, it can wipe your configuration entirely — a much worse situation than a forgotten password.

The approach below avoids both risks. It runs entirely inside the captain container (so it has access to captain-salt), constructs the hash the same way CapRover would, and verifies it before writing anything.

Reset Your Password

Step 1: SSH into the server

ssh root@YOUR_SERVER_IP

Step 2: Get the captain container name

CapRover runs as a Docker Swarm service. The underlying container name changes on each restart, so fetch it dynamically:

docker ps --filter name=captain-captain --format "{{.Names}}"

You should see something like captain-captain.1.abc123xyz. If nothing is returned, CapRover isn't running — start it first with docker service scale captain-captain=1.

Step 3: Generate the hash and write it to the config

Run this command, replacing your-new-password with the password you want:

CONTAINER=$(docker ps --filter name=captain-captain --format "{{.Names}}")
docker exec $CONTAINER node -e "
const bcrypt = require('bcryptjs');
const fs = require('fs');
const salt = fs.readFileSync('/run/secrets/captain-salt', 'utf8').trim();
const encryptionKey = salt + 'captain';
const password = 'your-new-password';
bcrypt.hash(encryptionKey + password, 10).then(hash => {
  const cfg = JSON.parse(fs.readFileSync('/captain/data/config-captain.json', 'utf8'));
  cfg.hashedPassword = hash;
  fs.writeFileSync('/captain/data/config-captain.json', JSON.stringify(cfg, null, 2));
  bcrypt.compare(encryptionKey + password, hash).then(ok => console.log('verified:', ok));
});
"

You should see:

verified: true

If you see verified: false, do not proceed. Check that you haven't accidentally introduced extra whitespace around your password string.

Step 4: Restart CapRover

docker service update captain-captain --force

Wait about 15 seconds for the service to come back up, then open your dashboard and log in with the new password.

What the Script Actually Does

Here's what each part of the Node.js script does:

const salt = fs.readFileSync('/run/secrets/captain-salt', 'utf8').trim();

Reads the captain-salt Docker secret from inside the container. This file is only accessible from within the captain container — it cannot be read from the host.

const encryptionKey = salt + 'captain';

Constructs the encryption key exactly the way CapRover's Authenticator.ts does: the secret value followed by the literal string "captain" (the namespace).

bcrypt.hash(encryptionKey + password, 10)

Hashes the combined string with 10 salt rounds, matching CapRover's own hashing call.

const cfg = JSON.parse(fs.readFileSync('/captain/data/config-captain.json', 'utf8'));
cfg.hashedPassword = hash;
fs.writeFileSync('/captain/data/config-captain.json', JSON.stringify(cfg, null, 2));

Reads the existing config, updates only hashedPassword, and writes the full config back. This preserves all other settings.

bcrypt.compare(encryptionKey + password, hash).then(ok => console.log('verified:', ok));

Runs the same comparison CapRover will run at login time to confirm the hash is correct before you restart.

Common Pitfalls

Don't use jq to delete hashedPassword unless you know it's installed. Run which jq first. If it isn't installed, the official method can produce an empty config file that wipes your CapRover settings on restart.

Don't generate the hash outside the container. Online bcrypt generators and local scripts don't have access to captain-salt. The hash they produce will always fail authentication.

Back up the config before editing. If you want an extra safety net before running the script:

CONTAINER=$(docker ps --filter name=captain-captain --format "{{.Names}}")
docker exec $CONTAINER cp /captain/data/config-captain.json /captain/data/config-captain.json.backup

Wait for the service to fully restart. After docker service update --force, CapRover takes around 15 seconds to come back. Trying to log in immediately will result in a connection error.

Conclusion

CapRover hashes passwords as bcrypt(captain-salt + "captain" + password), not as bcrypt(password). Any reset method that doesn't account for captain-salt will produce a hash that fails authentication. The one-command approach above runs inside the container, reads the salt directly from the Docker secret, generates the correct hash, and verifies it before writing — no external tools or risky config deletions required.

Further Reading