passwd
Set, lock and expire an account's password
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:
-llocks it.-eforces a new one at the next login.-dremoves it.-Sprints a one-line summary of all of that.-n,-x,-wand-iset its ageing. They are the same settingschagechanges, 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
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