passwd

Set, lock and expire an account's password

Updated 2026-10-02

Run with no arguments, passwd changes your own password, which requires the current one first. Root can name any account and is never asked for the old password, which is how every example here runs. The password itself never appears anywhere: /etc/shadow holds a one-way hash of it, readable only by root, and passwd is one of the few programs allowed to write that file.

Most of the flags are about the state of the password rather than its value:

  • -l locks it.
  • -e forces a new one at the next login.
  • -d removes it.
  • -S prints a one-line summary of all of that.
  • -n, -x, -w and -i set its ageing. They are the same settings chage changes, which reads and explains them better.

Locking is the part most often misunderstood. All passwd -l does is make the password impossible to match. A login that does not use the password, such as an SSH key or su from root, carries on working. To stop an account from being used at all, expire the account, which the examples on this page show next to the lock. Managing users covers creating and removing the accounts themselves.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

the accounts, and an SSH server to log in to The same two accounts as managing-users, id and chage, rebuilt before every example. Both start with a locked password without any other changes, and each one's last password change is pinned to 1 September 2026, so the dates printed here are the same on every run. An SSH server listens on 127.0.0.1 port 2222. Root's SSH config gives it the name tips-server and logs in as tips-dev with a key in that account's authorized_keys.

tips-dev:x:1001:1001:Deployment account:/home/tips-dev:/bin/bash
tips-ops:x:1002:1002:Operations account:/home/tips-ops:/bin/bash
tips-dev L 2026-09-01 0 99999 7 -1
tips-ops L 2026-09-01 0 99999 7 -1
Host tips-server
    HostName 127.0.0.1
    Port 2222
    User tips-dev
    IdentityFile ~/.ssh/tips-key
    BatchMode yes
17 outputs, collapsed by default

Setting a password

Root sets a password without knowing the old one. passwd asks for the new one twice, and from a terminal it does not echo what you type.

Set a password from a script

printf 'Correct-Horse-1\nCorrect-Horse-1\n' | passwd tips-dev

With no terminal attached, passwd reads both answers from standard input, so its two prompts land on one line with the result after them. The password is now in your shell history, so a real script reads it from a file or a variable instead. chpasswd, below, does the same job without the prompts, for as many accounts as you give it.

Show output
New password: Retype new password: passwd: password updated successfully

Set passwords in bulk

echo 'tips-dev:Correct-Horse-1' | chpasswd; passwd -S tips-dev | cut -d' ' -f1,2

chpasswd takes name:password lines on standard input, as many as you like, and prints nothing on success. It is the tool for provisioning scripts. passwd -S prints a one-line status for the account, explained in the next section, and P in its second field means a usable password is set.

Show output
tips-dev P

See which algorithm hashed the password

echo 'tips-dev:Correct-Horse-1' | chpasswd; getent shadow tips-dev | cut -d: -f2 | cut -c1-3

The stored hash starts with an identifier for the method that made it. $y$ is yescrypt, Debian's default. $6$ is SHA-512, which older installations used, and an account keeps whichever its password was last set with.

Show output
$y$

Check that one password is never stored the same way twice

echo 'tips-dev:Correct-Horse-1' | chpasswd; a=$(getent shadow tips-dev | cut -d: -f2); echo 'tips-dev:Correct-Horse-1' | chpasswd; b=$(getent shadow tips-dev | cut -d: -f2); [ "$a" != "$b" ] && echo "the same password stored twice gives two different hashes"

Each hash is made with a random salt, stored alongside it. Two accounts with the same password have different hashes, so a copy of /etc/shadow does not show who shares a password. It also defeats a precomputed table of the hashes of common passwords: a table like that would have to be built again for every possible salt, so an attacker has to try each guess against each account's hash separately.

Show output
the same password stored twice gives two different hashes

Try to change someone else's password as an ordinary user

runuser -u user -- passwd tips-dev

Only root can name another account. An ordinary user running passwd with no argument changes their own, and is asked for the current one first.

Show output
passwd: You may not view or modify password information for tips-dev.

Reading the status

Read an account's password status

passwd -S tips-dev

Seven fields: the name, the state, the date of the last change, then the minimum and maximum days between changes, the days of warning before expiry, and the days of inactivity allowed after it, where -1 means no limit. The state is P for a usable password, L for locked and NP for none at all.

Show output
tips-dev L 2026-09-01 0 99999 7 -1

Read the status of every account

passwd -S -a | grep '^tips-'

-a goes with -S only, and lists every account in /etc/shadow, system accounts included, which is why grep keeps only the two accounts this page is about.

Show output
tips-dev L 2026-09-01 0 99999 7 -1
tips-ops L 2026-09-01 0 99999 7 -1

List the accounts that have a usable password

echo 'tips-dev:Correct-Horse-1' | chpasswd; passwd -S -a | awk '$2 == "P" {print $1}'

Worth running on an unfamiliar machine to see which accounts a password can log in to at all. user is this machine's ordinary login account, and every system account is locked.

Show output
user
tips-dev

List the accounts that have no password at all

passwd -d tips-ops >/dev/null; passwd -S -a | awk '$2 == "NP" {print $1}'

NP means the password field is empty, which on Debian lets any user su to the account without a password, as the last section shows. On a healthy machine this prints nothing, so the passwd -d at the start removes tips-ops's password to give it something to find. Run only the part after the ; on a real machine.

Show output
tips-ops

Locking, and what a lock does not stop

A locked password stops the password from working. It does nothing to the other ways into an account, which is what the examples below test against a real SSH server.

Lock a password, then unlock it

echo 'tips-dev:Correct-Horse-1' | chpasswd; passwd -l tips-dev; passwd -S tips-dev | cut -d' ' -f1,2; passwd -u tips-dev; passwd -S tips-dev | cut -d' ' -f1,2

-l puts a ! in front of the stored hash and -u takes it off again, so the password that worked before the lock works after it. managing-users shows -u refusing an account that has no password behind its !.

Show output
passwd: password changed.
tips-dev L
passwd: password changed.
tips-dev P

Log in with a key to an account whose password is locked

passwd -S tips-dev | cut -d' ' -f1,2; ssh tips-server id -un

The password is locked and the key still gets in. ssh tips-server id -un runs id -un on the server, which prints the name of the account the login landed in, so tips-dev on the second line shows the login succeeded. Debian's SSH server checks the account through PAM, which looks at whether the account has expired rather than at the password, so a lock on the password does not reach key logins.

Show output
tips-dev L
tips-dev

Switch to a locked account as root

runuser -u tips-dev -- id -un

This is run as root, and root does not need the account's password to become it, so it works whatever the lock says. The same goes for su run as root, for sudo -u, and for a cron job or a service configured to run as the account. Run by an ordinary user, runuser refuses outright with may not be used by non-root users.

Show output
tips-dev

Expire the account to stop every login

usermod --expiredate 1 tips-dev; ssh tips-server id -un

An expiry date of day 1, 2 January 1970, is in the past, so the account has expired. This is what stops the key, because it is the account rather than the password that PAM refuses. usermod -e '' puts it back, as does chage -E -1, which sets and reads the same date. Managing users covers usermod's other changes.

Show output
Your account has expired; please contact your system administrator.
Connection closed by 127.0.0.1 port 2222

Give an account a shell that refuses logins

usermod -s /usr/sbin/nologin tips-dev; ssh tips-server id -un

The login itself succeeds, and the shell it starts prints a message and exits. It stops an interactive session and a remote command, but not a service that runs as the account, which is the usual reason for giving one this shell.

Show output
This account is currently not available.

Forcing a change, and removing a password

Make an account choose a new password at its next login

passwd -e tips-dev; ssh tips-server id -un

-e sets the last change to the start of 1970, so the password counts as expired. This works whatever the maximum age is: these accounts' passwords never expire on their own, and a last change of day 0 is read as a change being required now. A login from a terminal is asked for a new one before it gets a shell. Here there is no terminal to ask on, so the login is refused, which is also what happens to a script logging in to an account whose password has expired.

Show output
passwd: password changed.
You are required to change your password immediately (administrator enforced).
WARNING: Your password has expired.
Password change required but no TTY available.

Remove a password altogether

passwd -d tips-dev; runuser -u user -- su tips-dev -c 'id -un'

-d empties the password field, and Debian's PAM configuration accepts an empty password, so now any user can su to the account without being asked for anything. This is run as root, which su never asks for a password, so runuser runs the su as user, an ordinary account, and tips-dev is printed without a password prompt. SSH refuses an empty password by default, which hides the problem from anyone testing over the network. Use -l to stop a password working, never -d.

Show output
passwd: password changed.
tips-dev

Set the password's ageing with passwd

passwd -x 90 -w 14 tips-dev; passwd -S tips-dev

-x is the maximum age in days and -w the days of warning, the fifth and sixth fields of the status. -n and -i set the minimum age and the inactivity period. chage sets the same four, and chage -l prints the dates they work out to.

Show output
passwd: password changed.
tips-dev L 2026-09-01 0 90 14 -1