restic/restic · error
unable to read password: %w
Error message
unable to read password: %w
What it means
When no password was supplied via the CLI option, environment variables, or a password file, restic prompts on the terminal (gopts.Term.ReadPassword). This error wraps a failure of that terminal read itself - no usable TTY, EOF on input, or a canceled context - and is unrelated to whether the password is correct.
Source
Thrown at internal/global/global.go:240
// readPassword reads the password from a password file, the environment
// variable RESTIC_PASSWORD or prompts the user. If the context is canceled,
// the function leaks the password reading goroutine.
func readPassword(ctx context.Context, gopts Options, prompt string) (string, error) {
if gopts.InsecureNoPassword {
if gopts.Password != "" {
return "", errors.Fatal("--insecure-no-password must not be specified together with providing a password via a cli option or environment variable")
}
return "", nil
}
if gopts.Password != "" {
return gopts.Password, nil
}
password, err := gopts.Term.ReadPassword(ctx, prompt)
if err != nil {
return "", fmt.Errorf("unable to read password: %w", err)
}
if len(password) == 0 {
return "", errors.Fatal("an empty password is not allowed by default. Pass the flag `--insecure-no-password` to restic to disable this check")
}
return password, nil
}
// ReadPasswordTwice calls ReadPassword two times and returns an error when the
// passwords don't match. If the context is canceled, the function leaks the
// password reading goroutine.
func ReadPasswordTwice(ctx context.Context, gopts Options, prompt1, prompt2 string) (string, error) {
pw1, err := readPassword(ctx, gopts, prompt1)
if err != nil {
return "", err
}
if gopts.Term.InputIsTerminal() {View on GitHub (pinned to a80be1478a)
Solutions
- Set RESTIC_PASSWORD_FILE (file mode 600) or pass --password-file for every non-interactive run
- Use RESTIC_PASSWORD_COMMAND with a secret manager when storing a file is not an option
- Provide a real terminal for interactive use (docker run -t, ssh -tt)
- Check that the job's context is not canceled while credentials are being read
Example fix
# before (cron job, no TTY) restic -r s3:s3.amazonaws.com/bucket backup /data # fails: unable to read password # after printf 's3cret' > /etc/restic/pass && chmod 600 /etc/restic/pass RESTIC_PASSWORD_FILE=/etc/restic/pass restic -r s3:s3.amazonaws.com/bucket backup /data
Defensive patterns
Strategy: validation
Validate before calling
// decide before prompting whether a password source exists
if gopts.Password == "" &&
os.Getenv("RESTIC_PASSWORD_FILE") == "" &&
os.Getenv("RESTIC_PASSWORD_COMMAND") == "" &&
!isTerminal(os.Stdin) {
return errors.New("no TTY available; provide --password-file or RESTIC_PASSWORD_FILE")
} Type guard
func isTerminal(f *os.File) bool {
fi, err := f.Stat()
return err == nil && fi.Mode()&os.ModeCharDevice != 0
} Try / catch
password, err := resolvePassword(ctx, gopts, "enter password")
if err != nil {
if errors.Is(err, context.Canceled) {
return err
}
return fmt.Errorf("non-interactive run without a password source: %w", err)
} Prevention
- Configure RESTIC_PASSWORD_FILE with mode 600 for every automated run
- Never rely on interactive prompts in cron, systemd, or containers
- Use RESTIC_PASSWORD_COMMAND with a secret manager for short-lived credentials
- Fail fast in scripts by testing credentials with a cheap command (restic snapshots) first
When it happens
Trigger: Running restic where stdin/stdout is not a terminal: cron jobs, systemd services, docker without -t, CI pipelines, ssh without TTY allocation; the surrounding context being canceled while the prompt is open; a closed or permission-denied /dev/tty; piped input ending before the password is entered.
Common situations: Automated nightly backups without RESTIC_PASSWORD_FILE configured; restic inside Docker or Kubernetes CronJobs; scripts that redirect stdin and still expect interactive prompting to work.
Related errors
- ErrNoKeyFound
- ReadPassword: %w
- ErrMaxKeysReached
- stdout is the terminal, please redirect output
- an empty password is not allowed by default. Pass the flag `
AI-assisted analysis of restic/restic@a80be1478a (2026-08-15).
Data as JSON: /api/errors/83e0c30aedb15c52.
Report an issue: GitHub.