Read AppArmor Policy and Process Counts with aa-status

aa-status answers three questions fast: is AppArmor loaded, which profiles are enforcing, and which processes are actually confined. It also gives you a compact count for scripts, and JSON for when another tool needs the details. The examples match AppArmor 4.0.1, installed here as package version 4.0.1really4.0.1-0ubuntu0.24.04.7.

Allow about ten minutes. You need the AppArmor utilities package and a shell. This guide only reads policy state: it does not load, unload or change profiles, restart services, or touch kernel settings. The command normally needs elevated privileges to read the loaded profile set, so expect to reach for sudo on the queries that matter.

1. Run the default report

Start with the full human-readable report:

$ sudo aa-status
apparmor module is loaded.
128 profiles are loaded.
34 profiles are in enforce mode.
4 profiles are in complain mode.
...
315 processes have profiles defined.
315 processes are in enforce mode.
...

The profile and process counts shift constantly as packages, containers and desktop applications come and go: this is a snapshot, not a permanent inventory. A clean exit status only means AppArmor is enabled and policy is loaded, it does not mean any particular service is protected.

Checkpoint: run sudo aa-status and look first for apparmor module is loaded., then check the profile and process sections. If you see You do not have enough privilege to read the profile set., rerun with sudo rather than trusting the partial output.

2. Separate profiles from processes

The default report mixes loaded policy with processes it found through /proc. Pick one dataset when the other would just get in the way:

$ sudo aa-status --show=profiles
apparmor module is loaded.
128 profiles are loaded.
34 profiles are in enforce mode.
4 profiles are in complain mode.
...

$ sudo aa-status --show=processes
apparmor module is loaded.
315 processes have profiles defined.
315 processes are in enforce mode.
...

Use the profile view to review policy coverage, and the process view to check what is confined right now. A loaded profile can have no matching process at all, and the process count can drift between two runs seconds apart. The manual is upfront that the process information is susceptible to race conditions, since it is gathered from /proc.

3. Check a service or profile by filtering

Filters take POSIX regular expressions. Filter the profile names when you only care about one application:

$ sudo aa-status --filter.profiles='^docker-default$' --show=processes
apparmor module is loaded.
313 processes have profiles defined.
311 processes are in enforce mode.
   /usr/sbin/apache2 (86297) docker-default
   /webhooks (466165) docker-default
   ...

Your counts and process list will look different. What matters is exit status 0 and a list whose profile names actually match the expression. To filter by executable instead, use the executable filter:

$ sudo aa-status --filter.exe='^/usr/sbin/apache2$' --show=processes

Other filters target the profile mode and process ID: --filter.mode for mode text, --filter.pid for a regular expression against process IDs. Quote every expression. Leave it unquoted and something meaningful to the shell can rewrite it before aa-status ever sees it.

4. Ask for counts in scripts

--count prints only the number of entries for whatever you selected, and implies quiet output. Much easier to consume than scraping prose:

$ sudo aa-status --count --show=profiles
128
$ sudo aa-status --count --show=processes
315

Counts are not identifiers. A profile count can sit unchanged while an important profile quietly switches mode, and a process count can shift as a service restarts. For a health check, pair a count with an explicit mode filter, or switch to JSON when you need the actual names and states.

Keep the command's exit status separate from its output in a shell check:

if count=$(sudo aa-status --count --show=profiles); then
    printf 'Loaded AppArmor profiles: %s\n' "$count"
else
    status=$?
    printf 'aa-status failed with status %s\n' "$status" >&2
    exit "$status"
fi

5. Use JSON for machine consumption

Reach for --json when a program needs the policy and process data without parsing human sentences. The installed command emits a JSON object with a version field and collections for profiles and processes:

$ sudo aa-status --json | head -c 160
{"version": "2", "profiles": {"docker-default": "enforce", ...

The output can get large, especially on a host running containers. Keep it on a pipe, or redirect it to a deliberately chosen temporary file if you need a JSON tool to poke at it. --pretty-json gives you the same data with human-friendly indentation. Do not assume the displayed order is stable, and do not hard-code the live counts from this example.

6. Interpret failures and exit codes

Capture the status right after the command runs. The documented values are:

Do not collapse every non-zero result into "AppArmor is disabled". Status 3 points at the kernel securityfs interface, status 4 points at access. A privileged retry tells the two apart:

aa-status --quiet
status=$?
case "$status" in
    0) printf '%s\n' 'AppArmor policy is loaded' ;;
    1) printf '%s\n' 'AppArmor is not enabled or loaded' ;;
    2) printf '%s\n' 'AppArmor is enabled but has no loaded policy' ;;
    3) printf '%s\n' 'AppArmor control files are unavailable' ;;
    4) printf '%s\n' 'Insufficient privilege to read AppArmor control files' ;;
    42) printf '%s\n' 'aa-status reported an internal error' >&2 ;;
    *) printf 'Unexpected aa-status status %s\n' "$status" >&2; exit 1 ;;
esac

--quiet suppresses error messages, not the state data a normal report shows. Use it for a status-only check, then report the numeric result yourself. A successful command still does not prove a named application is confined.

Done means