FreeBSD pw: Manage Local Users and Groups Safely
Manage FreeBSD local users and groups with pw, preserve UID ownership, understand membership changes, and validate account lifecycle operations.
FreeBSD’s pw utility is the supported command-line editor for the local user and group databases. It handles the related master.passwd, passwd, and group files and updates the password databases used by system interfaces. The important qualifier is local: pw does not administer an external NIS or other identity directory. Confusing local records with directory-backed identity can create duplicate accounts, unexpected group membership, and access that differs between hosts.
Account operations are also filesystem operations. A username is a label; the numeric UID and GID recorded on files are what ownership checks use. Renaming an account does not rename every file, and changing a UID does not automatically rewrite file ownership across mounted filesystems. Plan identity changes as a coordinated migration with an inventory and rollback, not as a single successful pw command.
Inventory before changing records
Record the system release, current account data, source of identity, group memberships, login class, shell, home directory, and active sessions. Use the account tools to query the local view:
pw usershow appuser
pw groupshow appgroup
id appuser
getent passwd appuser
getent group appgroup
who
Use names that exist in the target environment. getent follows the configured name-service switch and can return a record from a source other than local files. The pw utility edits local user and group files only, so verify the record’s source before invoking a local modification command.
Keep a protected copy of relevant account configuration and note the current UID/GID values. Do not publish password database contents. The master.passwd file contains credential material, and group membership can expose administrative roles or application ownership. Capture only what is necessary for the change review.
The account inventory should include numeric ownership on important data paths:
find /var/db/app /srv/app -xdev -uid 1007 -print
find /var/db/app /srv/app -xdev -gid 1007 -print
Replace the example paths and IDs with those from the target host. The -xdev boundary avoids crossing into another filesystem, but it also means you must repeat the inventory for every relevant mounted filesystem. For ZFS datasets, inspect dataset mountpoints and snapshots separately. A snapshot can preserve old file ownership even after the live tree is migrated.
Create groups and users deliberately
Create a group before assigning it as a primary group, then create a local account with explicit identity-relevant settings. This example creates a service identity that has no home directory and is not intended for interactive shell access:
pw groupadd -n appsvc
pw useradd -n appsvc -c "Application service" -g appsvc -d /nonexistent -s /usr/sbin/nologin -w no
pw usershow appsvc
id appsvc
Check that the shell exists on the target release and that the application manager can run as this account. If the daemon requires a writable state directory, create and assign that directory through the service’s documented setup procedure; do not give it ownership of the whole application tree by habit.
For a human operator, a home directory and a valid login shell may be appropriate:
pw useradd -n deploy -c "Deployment operator" -m -s /bin/sh
pw usershow deploy
The example intentionally does not add the account to wheel or any privileged group. Grant group membership only when the required administrative workflow is documented and approved. Verify the defaults from /etc/pw.conf before relying on automatic UID allocation, base-home directory, skeleton files, shell, primary group, or supplementary groups.
The -g option selects the primary group. The -G option sets the account’s supplementary group list; it is not an additive operation when modifying an existing account. If an account must gain one group while retaining existing memberships, inspect the existing list first and pass the complete desired list. Do not list the primary group again as a supplementary group.
Change membership with session semantics in mind
To add an existing account to an additional group without replacing its other memberships, use the documented group modification operation:
pw groupmod operators -m deploy
pw groupshow operators
id deploy
The group database changes immediately, but the groups in an already running login session do not. The user must establish a new session before processes receive the updated supplementary group list. If a daemon uses a long-lived process, restart it through its service manager when its effective groups must change; do not assume a configuration edit alters credentials of existing processes.
When replacing a user’s group set with usermod, explicitly calculate the complete desired membership list first. A partial list can silently remove a group needed by the user’s job. Conversely, adding a broad group without checking the resulting effective access can give the account more authority than intended. Confirm both the user record and group records after the change.
Rename or renumber only with a migration plan
Renaming a login name changes the account database but leaves files identified by the UID. A numeric UID change is more consequential: files owned by the previous number do not magically follow the new UID, and another account could be assigned the old number later. Before changing a UID, inventory every local filesystem and any mounted shared storage, stop or coordinate processes, record the existing ownership map, and plan how to update file owners without crossing into unrelated tenants.
The modification command can be previewed with pw’s no-update mode where supported by the operation:
pw usermod -n deploy -c "Deployment operator" -N
pw usershow deploy
Consult the installed pw(8) manual for exact option placement and output format. A preview can show the resulting account record; it does not prove that files, running processes, SSH keys, scheduled jobs, or application data are migrated. Do not perform an identity-number change until each dependent system has an owner.
For a name-only rename, coordinate home-directory paths, automation, mail routing, crontabs, service configuration, and scripts that refer to the old name. Numeric ownership may still appear as the same UID, but name lookups will show the new name. Test the user login and service behavior from a new session, and keep the old access path available until the transition is accepted.
Locking, disabling, and removing accounts
pw provides lock and unlock operations for local accounts. The manual describes its locking mechanism as a marker added to the password field to prevent successful authentication through that credential database. This is not a complete session-termination or credential-revocation procedure. It does not by itself kill already running processes, cancel jobs, remove SSH keys, revoke external identity tokens, or disable a separate NIS account.
Before deletion, enumerate the account’s processes, files, home, crontab, mail spool, at jobs, service references, and any package or application ownership. A cautious removal plan preserves data and logs the UID/GID before deleting the account. The -r option asks pw to remove the home directory and its contents, but the manual documents limits, including treatment of files not owned by the user and special behavior for a home directory that is a ZFS dataset. Dataset children and snapshots require separate handling.
Prefer an archival workflow when ownership history matters: lock the account, stop or reassign its services, archive the home with metadata, verify the archive, then remove the record only after the retention owner approves. Do not rely on userdel -r as a general filesystem cleanup tool. It will not find every file associated with the UID outside the home path.
Validate the database and the consumers
After any change, query the account and group through more than one view:
pw usershow deploy
pw groupshow operators
id deploy
getent passwd deploy
getent group operators
If the user has an active session, compare the current session’s groups with a new login. If identity is supplied by NSS, verify that getent still resolves the intended source and that the local pw command did not create a conflicting record. Check service logs and test the exact service action under the intended UID/GID.
If a database update fails, read the exit status and error before editing files manually. pw maintains multiple related database files; bypassing the tool can leave them inconsistent. The manual points to vipw and pwd_mkdb for administrative workflows, but these are not interchangeable with an arbitrary text editor. Follow the installed release’s procedure and retain a known-good backup before repairing a damaged account database.
The pw utility can write an account-change log under /var/log/userlog according to its configuration. Preserve that record alongside the change ticket. For configuration-managed hosts, update the source of truth and verify a later convergence run does not revert the local change.
Acceptance criteria
An account change is complete when the local or directory source is known, the intended numeric identity and group set are confirmed, important file ownership has been reconciled, and a fresh login or service restart sees the expected credentials. For removal, confirm no active processes or retained data depend on the old identity and that backups satisfy retention policy.
Record the operation, UID/GID before and after, affected paths, membership changes, service restart, validation commands, rollback strategy, and whether the record is local or directory-managed. This makes account state reproducible and prevents display-name assumptions from becoming filesystem access incidents.
Related:
- FreeBSD Login Class Operations: Apply Session Limits and Environment Policy
- FreeBSD NSS Operations: Trace getent Lookup Order Before Blaming DNS
Sources: