Inspect systemd Control Groups with systemd-cgls
A service is eating CPU somewhere below it and systemd-cgls is how you find which process is actually doing the damage. This guide gives you a repeatable way to inspect the processes below a unit, a cgroup path or a container, keeping the output tied to the scope you actually asked for. Examples were checked with systemd 255, package version 255.4-1ubuntu8.17, on a unified cgroup hierarchy. Allow about ten minutes.
The route
Jump straight to the step you need, or tick off Done means at the end.
- You need: a shell and an installed
systemd-cglscommand. - The normal checks are read-only and do not need
sudo. You may need elevated privileges to inspect cgroups or processes your account cannot read, but privilege does not change what a unit contains.
1. Confirm the local command
Start with the version and help output. This catches an older system before you rely on options added in later releases:
$ systemd-cgls --version
systemd 255 (255.4-1ubuntu8.17)
$ systemd-cgls --help
The installed manpage describes systemd-cgls as a tree view of a selected Linux control group hierarchy: it shows member processes and descendant subgroups. Empty groups are hidden by default, and process names can be shortened unless you ask for full output.
Checkpoint
Confirm the binary your shell actually uses, and record the version if you are putting the command into monitoring or a runbook.
$ command -v systemd-cgls
/usr/bin/systemd-cgls
$ systemd-cgls --version | head -n 1
systemd 255 (255.4-1ubuntu8.17)
2. View the relevant part of the hierarchy
With no argument, the command picks its scope from the current directory. Under /sys/fs/cgroup/, it shows the cgroup that directory represents. From an ordinary directory such as your home directory, it shows the full systemd hierarchy:
$ cd /tmp
$ systemd-cgls --no-pager
Control group /:
-.slice
├─init.scope
│ └─1 /sbin/init
└─system.slice
├─systemd-journald.service
│ └─455 /usr/lib/systemd/systemd-journald
└─...
Exact units, PIDs and tree branches vary by host. The useful check is the first line and the presence of the service or slice you are investigating. --no-pager keeps output in the terminal and makes redirection predictable.
For a narrower view, pass a cgroup path. A full path avoids ambiguity:
$ systemd-cgls --no-pager /sys/fs/cgroup/system.slice
Control group /system.slice:
├─systemd-journald.service
│ └─455 /usr/lib/systemd/systemd-journald
└─...
A path without the mount prefix is treated as being inside the systemd control group hierarchy, but the full path helps avoid confusion when another cgroup hierarchy is mounted.
3. Inspect one service by unit name
For service troubleshooting, use --unit instead of hunting through a large tree by eye:
$ systemd-cgls --no-pager --unit=systemd-journald.service
Unit systemd-journald.service (/system.slice/systemd-journald.service):
└─455 /usr/lib/systemd/systemd-journald
This selects the cgroup subtree for the named unit; it does not start, stop or reload it. Add more unit names when comparing several services:
$ systemd-cgls --no-pager --unit=systemd-journald.service systemd-udevd.service
If a name is not valid on this host, the command reports an error. Check the exact loaded unit name with systemctl list-units --type=service; do not silently swap in a similarly named service.
4. Make long process trees useful
Process command lines are ellipsised by default. Use -l or --full when the truncated argument is the detail you need:
$ systemd-cgls --no-pager --full --unit=systemd-journald.service
Unit systemd-journald.service (/system.slice/systemd-journald.service):
└─455 /usr/lib/systemd/systemd-journald
Use --all when an empty control group matters to your investigation; it changes visibility only, and does not create or remove groups:
$ systemd-cgls --no-pager --all --unit=systemd-journald.service
Kernel threads are omitted unless you add -k, which is usually the clearer default for service work. Include them when you are investigating kernel activity and can live with busier output.
5. Check user units and containers separately
User units have their own selection option. Pass the exact user unit name, including its suffix:
$ systemd-cgls --no-pager --user-unit=backup.service
A missing user unit is not evidence that the system service is missing. Check the user manager's units with systemctl --user list-units, then repeat the lookup with the exact name.
When systemd manages a container, -M or --machine= limits the display to the cgroup area for that machine:
$ systemd-cgls --no-pager --machine=CONTAINER_NAME
Replace CONTAINER_NAME with the machine name reported by machinectl list. The command only inspects the selected area; it does not enter the container, execute a command there or alter its lifecycle.
6. Add IDs when the cgroup identity matters
On systemd 250 and later, -c or --cgroup-id= can include the numeric ID of each listed cgroup. The short form enables it; the long form takes a boolean value:
$ systemd-cgls --no-pager --cgroup-id=yes --unit=systemd-journald.service
Likewise, -x or --xattr= controls whether extended-attribute information is shown. Both default to off in the installed systemd 255 manpage. Use them when correlating output with tooling that records cgroup IDs or attributes, not as a general-purpose verbosity switch.
7. Handle permissions and output safely
A non-zero exit status means the query failed. Capture it when scripting:
$ systemd-cgls --no-pager --unit=systemd-journald.service
status=$?
if [ "$status" -ne 0 ]; then
printf 'systemd-cgls failed with status %s\n' "$status" >&2
exit "$status"
fi
Tip
Do not parse the decorative tree characters as a stable machine interface. For automation, prefer systemd or cgroup APIs designed for structured data, and use this command for human inspection.
If access is denied, confirm the unit or path first. Only then retry with the minimum privilege your host policy allows:
$ sudo systemd-cgls --no-pager --unit=UNIT_NAME.service
This is a read-only diagnostic example. Do not use sudo as a way to guess unit names, and do not turn a service-disrupting command into a follow-up experiment while diagnosing the output. Nothing above changes cgroups or services, so there is no undo step needed.
Done means
- You confirmed the installed systemd-cgls version and binary.
- You selected the intended scope: hierarchy, cgroup path, unit, user unit or machine.
- You used
--no-pagerfor repeatable terminal or captured output. - You know empty groups, kernel threads and long process arguments are hidden or shortened by default.
- You treated non-zero status and permission errors as conditions to investigate, not proof a unit is absent.
- You made no service, cgroup, container or persistent configuration changes.