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:

  • login (the program invoked after successful authentication on a text console) execs the user’s configured shell as the final step of session setup.
  • sshd, when a client requests an interactive session without specifying a command, execs the target user’s login shell.
  • Display managers (GDM, SDDM, LightDM) typically use it to determine what shell to launch inside a terminal emulator, and sometimes for session-startup script sourcing.
  • su - specifically execs the target user’s configured login shell as part of establishing the full login environment (see the su guide for more on this).

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

  • Linux man-pages — chsh(1): https://man7.org/linux/man-pages/man1/chsh.1.html
  • Linux man-pages — shells(5): https://man7.org/linux/man-pages/man5/shells.5.html
  • Linux man-pages — passwd(5): https://man7.org/linux/man-pages/man5/passwd.5.html
  • Debian Wiki — Shells: https://wiki.debian.org/Shell
Total
0
Shares

Leave a Reply

Previous Post
uname command in Linux and it perimeters

uname Command in Linux: Complete Guide to System Information Display and Parameters

Next Post
groups command in Linux and it perimeters

groups Command in Linux: Complete Guide to Group Membership Display and Parameters

Related Posts