chsh Command in Linux: Complete Guide to Changing Login Shell and Parameters

chsh command in Linux and it perimeters

I switched from bash to zsh a few years ago, and the very first command I ran to make it permanent (rather than just launching zsh manually every session) was chsh. It’s a small, focused command, but understanding exactly what it changes — and what it deliberately restricts — clarifies a piece of the login process that’s otherwise easy to take for granted. Here’s the full picture.

What chsh Does

chsh (change shell) updates the login shell field in /etc/passwd for a user account — the program that gets launched automatically whenever that user logs in, whether via a terminal, SSH, or a display manager’s terminal emulator.

Syntax

Confirmed from chsh --help on util-linux 2.39.3:

Usage: chsh [options] [LOGIN]

Options:
  -h, --help                    display this help message and exit
  -R, --root CHROOT_DIR         directory to chroot into
  -s, --shell SHELL             new login shell for the user account

Options Explained

OptionDescription
-s SHELLSet the new shell directly on the command line, skipping the interactive prompt
-R DIROperate inside a chroot at the given directory — useful when configuring an account for an environment you’re not currently booted into
LOGINThe target username; if omitted, defaults to the current user; changing another user’s shell requires root

Notably absent from this modern util-linux build: a -l/--list-shells flag. Some historical and BSD versions of chsh supported listing valid shells directly; on this system, that job belongs to a separate step — reading /etc/shells directly.

Tested Behavior

$ chsh --help
Usage: chsh [options] [LOGIN]

Options:
  -h, --help                    display this help message and exit
  -R, --root CHROOT_DIR         directory to chroot into
  -s, --shell SHELL             new login shell for the new user account

I also confirmed the valid-shells list, which lives at /etc/shells:

$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/usr/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/usr/bin/dash

chsh checks any non-root user’s requested shell against this file and refuses shells not listed here — a deliberate safety mechanism I’ll explain below. Root can typically bypass this restriction and set any executable path as a shell.

Usage Examples

Interactive mode

$ chsh
Password:
New shell [/bin/bash]: /usr/bin/zsh

Run with no arguments, chsh prompts for your password (even though you’re already logged in — this re-authentication step exists because it’s modifying a security-relevant account attribute) and then the new shell path, showing your current shell as the default if you just press Enter.

Non-interactive, direct assignment

sudo chsh -s /usr/bin/zsh username

This is the form I actually use in practice and in scripts — explicit, no prompts, easy to automate.

Setting a restricted shell for a limited account

sudo chsh -s /usr/bin/rbash restricted_user

rbash (restricted bash) blocks a range of otherwise-normal shell operations — changing directories outside a fixed set, modifying $PATH, using absolute command paths, redirecting output — making it a lightweight, if imperfect, sandboxing mechanism for accounts that need a shell but shouldn’t have full system access. I say “imperfect” deliberately: restricted shells are well-documented as escapable by a motivated user with shell scripting knowledge, so treat this as a mild guardrail, not a hard security boundary.

Disabling interactive login entirely (for service accounts)

sudo chsh -s /usr/sbin/nologin service_account

or the equivalent, slightly older path:

sudo chsh -s /bin/false service_account

nologin is generally preferred over /bin/false because it prints a clear “This account is currently not available” message and logs the attempt, rather than silently exiting — better for both usability and auditability when someone (mistakenly or maliciously) tries to log in as that account.

How chsh Works Internally

chsh is a small setuid-root utility (same family as passwd and su in terms of privilege model) that directly edits the shell field — the seventh, final colon-separated field — of the target user’s line in /etc/passwd:

username:x:1001:1001:Full Name,,,:/home/username:/bin/bash

It performs this edit safely by writing to a temporary lock file (/etc/passwd.lock, historically) and atomically renaming it into place, avoiding the risk of a corrupted /etc/passwd if the process were interrupted mid-write — the same file-locking discipline passwd, usermod, and vipw all follow, since /etc/passwd is one of the most safety-critical files on the system.

Before accepting the change (for non-root users), it validates the requested path against /etc/shells — this validation exists specifically because many other system components (notably FTP daemons, some login managers, and PAM’s pam_shells module) treat presence in /etc/shells as the definition of “a legitimate, fully-privileged interactive shell,” and use that list to distinguish real user accounts from restricted/service accounts. Letting a regular user set an arbitrary, unvetted binary as their login shell would undermine that check system-wide.

Where the Login Shell Actually Gets Used

Understanding chsh requires understanding what actually reads that /etc/passwd field:

Notably, plain su username (without the dash) does not always switch to the target’s configured shell — behavior here can vary, another reason su - is the more predictable, generally-recommended form.

Adding a New Shell System-Wide

If you’ve compiled or installed a shell that isn’t yet in /etc/shells (a common scenario after installing something like fish or nushell via a method outside the distro’s package manager), chsh will refuse it for non-root users until it’s registered:

which fish
# /usr/local/bin/fish
echo /usr/local/bin/fish | sudo tee -a /etc/shells
chsh -s /usr/local/bin/fish

This two-step process — register in /etc/shells, then chsh — is the correct, safe way to roll out a new shell option to users, rather than each user working around the restriction individually.

Troubleshooting

“chsh: /path/to/shell is an invalid shell” — the path isn’t listed in /etc/shells, or the file doesn’t exist/isn’t executable at that exact path; verify with which and add it to /etc/shells if it’s legitimately installed but just unregistered.

Shell change doesn’t take effect — you’re still in your existing shell’s already-running process; the new shell only launches on your next login session (new SSH connection, new terminal, or su - yourself), not retroactively for currently open sessions.

“Permission denied” trying to change another user’s shell — you need root privileges (via sudo) to change any account’s shell other than your own.

Locked out after setting an invalid or broken shell — if the configured shell path is wrong or the binary is missing, login attempts will fail (or drop back to the login prompt immediately). Recovery: log in via a different account/root and run sudo chsh -s /bin/bash affected_user, or use usermod -s /bin/bash affected_user as an equivalent fix from a working root session.

chsh vs Related Commands

CommandPurpose
chshUser-facing, interactive-friendly tool specifically for changing the login shell field
usermod -s SHELL userBroader account-modification tool; changing the shell is just one of many things it can do, always requires root, no interactive prompt
chfnSibling command for changing the GECOS field (full name, office, phone — the descriptive account info), unrelated to the shell itself
vipwDirect, careful manual editing of /etc/passwd with locking, the low-level tool chsh/usermod insulate you from needing

For scripted account provisioning, I generally prefer usermod -s over chsh -s simply because usermod is the more general-purpose account-management tool already in use for everything else in the same script, but they accomplish the identical underlying change for this specific field.

Security Implications

The /etc/shells validation chsh performs for non-root users is a meaningful, if narrow, security control — it prevents a regular user from casually setting an arbitrary unvetted binary as their “shell” in a way that could confuse other system components relying on that file as a trust boundary. For accounts that should never get an interactive session at all — service accounts, application users, accounts created solely to own a specific daemon’s files — explicitly setting the shell to /usr/sbin/nologin is a standard, low-effort hardening step I apply by default at account-creation time, not as an afterthought. Because chsh is setuid-root, keeping the package that provides it (util-linux or shadow-utils, depending on distribution) patched is part of the same baseline hygiene as any other setuid binary on the system.

Distribution Compatibility

chsh ships as part of util-linux on Debian/Ubuntu-family systems and as part of shadow-utils (alongside passwd, usermod) on RHEL/Fedora-family systems, with consistent core behavior across both. The -R/--root chroot option is present on modern util-linux builds; older shadow-utils implementations may lack it, relying instead on actually chrooting via a separate wrapper before invoking chsh. /etc/shells‘ default contents vary by distribution’s installed shell packages, but the validation mechanism itself is universal across mainstream distros.

Historical Context: BSD Origins and GNU/Linux Divergence

chsh traces back to BSD Unix, part of a small family of “change” utilities (chsh, chfn) that let ordinary users self-service a handful of account attributes without needing an administrator to hand-edit /etc/passwd for every trivial request. The core idea — letting non-root users make a narrow, validated change to their own account record through a setuid-root helper rather than granting broader write access to /etc/passwd — is a pattern that shows up repeatedly across Unix account-management tooling, and chsh is one of the cleanest, smallest examples of it. The current Linux implementation (from util-linux or shadow-utils depending on distribution) preserves this exact model, though the specific flag set has narrowed somewhat compared to older BSD and pre-shadow-suite versions, which sometimes included a -l/list flag directly in chsh itself rather than requiring users to read /etc/shells manually.

Interaction with Containers and Minimal Images

Minimal container base images frequently ship with a severely trimmed /etc/shells, sometimes containing only /bin/sh, and may not even include chsh as an installed binary at all (BusyBox-based images in particular often lack it entirely, relying instead on direct edits via usermod or manual /etc/passwd editing during image build). If you’re customizing a Dockerfile and want a particular shell to be the default for an interactively-used container, it’s generally more reliable and reproducible to set it directly during user creation rather than relying on chsh being present at runtime:

RUN useradd -m -s /usr/bin/zsh appuser

I mention this because troubleshooting “why doesn’t chsh work in my container” usually resolves to “chsh was never installed in this minimal image to begin with” rather than any configuration problem — worth checking with which chsh before assuming something is broken.

Verifying the Change End-to-End

After running chsh, I always confirm the change actually landed correctly and will take effect as expected, rather than trusting the command’s silent success:

getent passwd username | cut -d: -f7

This queries the account database directly (respecting NSS, so it works correctly even for LDAP-backed accounts) and prints just the shell field, giving a clean, unambiguous confirmation independent of whatever caching a currently open session might still be showing.

Summary

chsh does one narrowly-scoped job — updating the login shell field in /etc/passwd — but it does it with the same careful atomic-write discipline and /etc/shells-backed validation that keeps this security-relevant file trustworthy for every other tool that reads it. Whether you’re personalizing your own environment with a new shell or locking down a service account with nologin, it’s the right, minimal tool for the job — reach for usermod -s instead only when you’re already scripting broader account changes in the same breath.

References

Exit mobile version