How To List Users In Linux

How To List Users In Linux

Reading time1 min
#Linux#Sysadmin#Security#UserManagement#LinuxUsers#UserEnumeration

Mastering User Enumeration in Linux: Beyond the Basic cat /etc/passwd

Accurate user enumeration is a foundational task for any Linux administrator concerned with security and maintainability. Identifying the difference between interactive user accounts, background system users, and identities managed outside of /etc/passwd is critical for access audits, compliance, and operational hygiene.


Consider a scenario: SSH access issues crop up, or a compliance scan flags unknown accounts. The usual cat /etc/passwd:

cat /etc/passwd

yields all local entries, but treats service, system, and human users alike. Here’s a snip from a CentOS Stream 9 system:

root:x:0:0:root:/root:/bin/bash
systemd-coredump:x:993:993:systemd Core Dumper:/:/sbin/nologin
ana:x:1001:1001:Analytics:/home/ana:/bin/bash

Problem: /etc/passwd neither reflects directory service users (LDAP, SSSD, NIS), nor the status of logged-in sessions, nor does it effectively distinguish real, interactive users from automation and daemons.


Use getent passwd To Respect Name Service Switch

Modern Linux environments (see: glibc ≥ 2.32) aggregate user data from multiple sources. getent is NSS-aware and will list all users the system recognizes, regardless of source.

getent passwd

Output blends local and remote accounts. Example from a hybrid LDAP setup:

root:x:0:0:root:/root:/bin/bash
ana:x:1001:1001:Analytics:/home/ana:/bin/bash
alice:*:2001:2001:Alice Smith:/data/alice:/bin/bash
ldapuser:x:2100:2100:LDAP User:/home/ldapuser:/bin/bash

Note: Remote accounts (LDAP/NIS) only appear if their identities can be resolved—sometimes caching or connectivity issues cause omissions or slow response.

To scope only likely human users, it’s typical to filter by UID ≥ 1000 (some distros, notably Debian, use 1000+, while RHEL-based systems start at 1000):

getent passwd | awk -F: '$3 >= 1000 && $3 < 65534 { print $1 }'

This filters out standard “nobody” (65534) and lower UIDs. Not bulletproof (admins sometimes create service accounts at high UIDs), but a strong baseline for ad-hoc audits.


Real-Time Logins: Distinguishing Existence From Presence

Listing all configured users is different from knowing who’s actually logged in. For real-time session tracking:

who

returns:

ana      pts/1   2024-06-22 13:51 (10.10.0.32)
alice    pts/2   2024-06-22 14:13 (vpn.example.com)

w provides richer context, reporting login times, idle status, source IP, and running commands—helpful when investigating suspicious shell access:

w | head -3
 14:15:52 up  3:48,  2 users,  load average: 0.13, 0.05, 0.03
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
ana      pts/1    10.10.0.32      13:51    1:02m  0.04s  0.04s bash

Investigating Dormant and Stale Accounts (lastlog)

Critical for hygiene: which users have never logged in, or haven’t used their access in months? lastlog cross-references all local accounts with recorded login times:

lastlog | grep -v "**Never logged in**" | head -4
Username         Port     From             Latest
root             pts/0    :0               Sat Jun 22 08:15:34 +0000 2024
ana              pts/1    10.10.0.32       Sat Jun 22 13:47:15 +0000 2024

Dormant accounts frequently signal forgotten users or abandoned services—prime targets for privilege review or removal.

Gotcha: lastlog only tracks local login events. For remote/NSS users, logs may be empty. In enterprise LDAP/SSSD deployments, audit login events via central logging instead.


Inspecting User Attributes (id, finger, and Edge Cases)

Need fast group and shell info?

id alice

outputs:

uid=1002(alice) gid=1002(alice) groups=1002(alice),10(wheel),994(docker)

For richer metadata (if installed):

finger alice

Unlike /etc/passwd, finger can show GECOS (full name, office) fields and last login. Note, on minimal distributions finger is usually absent by default.

Edge: Some integrations (Active Directory via SSSD) omit GECOS—expect partial records.


Identifying Valid Login Accounts by Shell

Many system accounts use non-interactive shells (/usr/sbin/nologin, /bin/false). To surface only users who could plausibly log in interactively:

awk -F: '($7 ~ /bash$|sh$|zsh$/) {print $1}' /etc/passwd

Or, to generalize by enumerating from /etc/shells:

grep -Fxf <(awk -F: '{print $7}' /etc/passwd) /etc/shells | xargs -n1 -I{} awk -F: -v shell="{}" '$7 == shell {print $1}' /etc/passwd

Not elegant (parsing NSS sources plus shells can get hairy), but effective on vanilla installations.


Quick Reference Table

Use CaseCommand Example
All users (local file)cat /etc/passwd
All NSS usersgetent passwd
Probable human users`getent passwd
Currently logged inwho / w
Audit dormant accountslastlog
User details/groupsid alice / finger alice
Interactive shell usersSee /etc/shells based filter above

Practical Notes & Pitfalls

  • Users authenticated only via AD/LDAP may not show up if NSS configuration is incomplete or nscd/sssd is misbehaving.
  • Docker containers can have odd defaults—for Alpine they're UID 1000+, but shells may be /bin/ash.
  • Always check /etc/nsswitch.conf to confirm the order and active sources (passwd: files sss systemd etc).

In Summary

Basic /etc/passwd output is almost never enough for real-world security, compliance, or migration tasks. Mature environments use NSS-based sources, deploy getent, and systematically filter by UID and shell for actionable user lists. Always layer your queries: static files, NSS, session state, and shell. And don’t trust a single command for audit trails—cross-reference.

For deeper automation, tools like ansible.builtin.user (Ansible) and libuser (Python) can parse users programmatically, bridging these techniques at scale.

Alternate tip: Need to list users with expired passwords? Try chage -l username (install passwd package).


Found an edge case in user enumeration on custom PAM stacks or container orchestration? Consider documenting it—real systems always break the mold sooner or later.