id Command in Linux: Complete Guide to User and Group Identification and Parameters

id command in Linux and it perimeters

id is one of the commands I run almost reflexively at the start of any debugging session involving permissions — “who am I, actually, right now, in this shell/container/cron job/SSH session.” It looks trivial, but the effective vs real UID distinction it reports is at the heart of a huge share of permission bugs on Linux. Here’s the full picture.

What id Does

id prints the identity of the current user (or a specified user): their user ID (UID), primary group ID (GID), and every supplementary group they belong to. On SELinux-enabled systems, it also reports the current security context.

Syntax

id [OPTION]... [USER]

Options

Tested directly:

OptionLong formDescription
(none)Full identity: uid, gid, and all groups
-u--userPrint only the effective user ID
-g--groupPrint only the effective group ID
-G--groupsPrint all group IDs (primary + supplementary)
-n--namePrint names instead of numeric IDs (used with -u/-g/-G)
-r--realPrint the real (not effective) ID instead
-Z--contextPrint only the SELinux security context
-z--zeroDelimit output with NUL instead of newline
-aIgnored, kept for compatibility with other Unix id implementations

Tested Examples

$ id
uid=0(root) gid=0(root) groups=0(root)
$ id -u
0
$ id -un
root
$ id -g
0
$ id -gn
root
$ id -G
0
$ id -Gn
root

On a regular multi-group user, id -G/id -Gn would list every supplementary group, not just the primary one — for example, id -Gn deploy on a typical server might return deploy docker sudo if that account belongs to multiple groups.

Checking another user’s identity

id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Real UID vs Effective UID vs Saved UID

This is the concept id exists to expose, and it’s worth understanding properly because it explains a huge amount of Linux permission behavior:

By default, id reports the effective IDs. id -r reports the real IDs instead. These normally match, but diverge specifically during setuid execution:

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root ... /usr/bin/passwd

That s in the owner execute position means: while passwd runs, its effective UID becomes root (0), regardless of which real user launched it — which is exactly how an unprivileged user is able to modify /etc/shadow (a root-only-writable file) through passwd, safely, because the setuid binary’s own code controls exactly what gets written.

How id Works Internally

id reads the calling process’s credentials directly from the kernel via getuid(), geteuid(), getgid(), getegid(), and getgroups() — for the current process, this is essentially free and instantaneous, no file lookups required, since the kernel already tracks these values as part of every process’s task structure. When you run id someuser for another account, it instead consults the name service (/etc/passwd, /etc/group, or NSS-configured sources like LDAP/SSSD if configured) via getpwnam()/getgrouplist(), rather than reading live kernel process state, since there’s no running process for that user to query directly.

This distinction matters in enterprise environments using centralized identity — id ldapuser on a properly configured SSSD/LDAP-integrated system correctly resolves group memberships from the directory, not just local /etc/group, which is a good sanity check that directory integration is actually working.

Real-World Use Cases

Confirming a script or service actually runs as the intended user

# Inside a systemd unit's ExecStartPre, or at the top of a deploy script
id

I add a bare id call as the first line of many deployment scripts specifically to catch the class of bug where a script silently runs as the wrong user (common when sudo, su, or a container’s USER directive doesn’t behave as expected) — far better to fail loud immediately than debug a mysterious permission error five steps later.

Verifying group membership took effect

usermod -aG docker deploy
su - deploy -c "id -Gn"

A classic gotcha: group membership changes via usermod don’t apply to already-running sessions — you have to start a new login session (or newgrp) to see the new group reflected in id. Checking with id -Gn in a fresh shell is the standard way to confirm the change actually took effect versus just existing in /etc/group.

Checking effective identity inside a container

docker run --rm myimage id

This is one of the fastest ways to confirm whether a Dockerfile’s USER directive is actually taking effect, and to catch the common mistake of an image still running as root when it was intended to drop privileges.

Using id in conditional scripting

if [ "$(id -u)" -ne 0 ]; then
  echo "This script must be run as root" >&2
  exit 1
fi

This is the single most common pattern I write in any script that needs root — checking $(id -u) is more portable and reliable than checking $USER or $LOGNAME, both of which can be unset or manipulated in ways UID cannot.

SELinux Context Reporting

On SELinux-enabled distributions (RHEL, Fedora, CentOS by default):

$ id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

This shows the full SELinux context: user, role, type, and sensitivity range — critical information when debugging SELinux-related “permission denied” errors that occur despite standard Unix permissions looking correct. ausearch -m avc -ts recent paired with knowing the exact context from id -Z is a standard SELinux troubleshooting combination.

Troubleshooting

id shows unexpected groups missing after usermod -aG — start a fresh login shell or run newgrp groupname; group membership is evaluated at login/session start, not live.

id someuser returns “no such user” but the account clearly exists — check NSS configuration (/etc/nsswitch.conf); if the account comes from LDAP/SSSD/Winbind and that service is down or misconfigured, local lookup tools like id will fail even though the account “exists” in the directory.

Effective and real UID differ unexpectedly outside of setuid context — check for setuid()/seteuid() calls in wrapper scripts or the process’s own capability-dropping logic; this is expected behavior for privilege-dropping daemons but worth confirming intentional.

id vs Related Commands

CommandPurpose
idFull identity report: UID, GID, groups, optionally SELinux context
whoamiPrints only the effective username — a small subset of id -un
who / wShows who is logged into the system generally, not the current process’s identity
groupsPrints only group memberships by name, a friendlier subset of id -Gn
lognamePrints the login name from the session’s audit/login record, distinct from effective UID

whoami and id -un return the same value in almost every situation, but whoami has no equivalent for real vs effective distinction or group reporting, so I reach for id whenever I need more than just the username.

Security Implications

id itself carries no privilege risk — it’s read-only and reports what the kernel already knows. Its real security value is as a verification tool: confirming that privilege-dropping actually worked in a daemon or container, confirming that a setuid binary’s effective UID during execution matches expectations, and confirming group-based access control changes took effect before relying on them. I treat “run id and actually read the output” as a mandatory step any time I change a service’s runtime user, not an optional sanity check — silent failures here (a service still running as root when you believe it dropped to an unprivileged user) are a genuinely common source of real-world security incidents.

Distribution Compatibility

id is part of GNU coreutils and behaves identically across Debian, Ubuntu, Fedora, RHEL/CentOS, Arch, and openSUSE. The -Z (SELinux context) flag only produces meaningful output on SELinux-enabled systems; on AppArmor-based systems (Ubuntu, Debian, openSUSE) it simply reports nothing extra, since AppArmor’s confinement model isn’t exposed the same way. BusyBox’s id (Alpine, minimal containers) supports the core flags (-u, -g, -G, -n) but omits SELinux context reporting entirely.

id in Shell Prompt Customization

Beyond debugging, id (or more commonly whoami, its narrower cousin) frequently shows up embedded in shell prompt configuration, specifically to visually flag when a session is running as root — a genuinely useful safety cue on systems where accidentally leaving a root shell open is a real risk:

if [ "$(id -u)" -eq 0 ]; then
  PS1='\[\033[1;31m\]\u@\h:\w#\[\033[0m\] '   # red prompt for root
else
  PS1='\u@\h:\w\$ '
fi

I’ve used variations of this on every server I administer — a bright red prompt is a much more reliable warning than remembering to check whoami before running a destructive command.

id and Capabilities: Beyond Simple UID Checks

Modern Linux security models extend well past the classic UID 0 = root binary. Linux capabilities (CAP_NET_BIND_SERVICE, CAP_SYS_ADMIN, and dozens more) allow a process to hold specific privileged abilities without full root UID, and id alone doesn’t report these — a process can show uid=1001(appuser) from id while still holding meaningful elevated capabilities granted via setcap on its executable or inherited through its capability set. For a complete privilege picture beyond what id reports, getpcaps <pid> or capsh --print are the tools that fill that gap. I mention this specifically because it’s a common misunderstanding — seeing a non-root UID in id output doesn’t automatically mean a process has no elevated privileges at all; it just means it isn’t using the traditional all-or-nothing root mechanism.

getpcaps $$

This shows the capability set of the current shell process, a useful complement to id when auditing exactly what a process can do, not just which account it’s nominally running as.

id in User Namespaces and Rootless Containers

Modern container runtimes increasingly rely on Linux user namespaces to run containers “rootlessly” — a process can appear as UID 0 inside its own namespace while actually mapping to an unprivileged UID on the host, dramatically reducing the blast radius of a container escape. id run inside such a namespace reports the namespace-local identity, which can look identical to genuine root (uid=0(root)) even though the host sees a completely different, unprivileged UID for that same process:

podman run --rm alpine id
# uid=0(root) gid=0(root) groups=0(root)

This is expected and correct rootless-container behavior, not a security bug — but it does mean that id output alone, viewed from inside a namespace, is not sufficient evidence of genuine host-level root privilege. Confirming the actual host-side mapping requires checking /proc/<pid>/uid_map from outside the namespace, a detail worth knowing before treating in-container id output as a full security assessment.

Summary

id is a small, fast, read-only window into exactly how the kernel currently sees a process’s identity — real UID, effective UID, and full group membership — and understanding the real/effective distinction it exposes explains a large share of “why does this have permission to do that” questions on Linux. I treat it as a default first step whenever a permission-related bug shows up, in scripts, containers, or interactive sessions alike.

References

Exit mobile version