The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Check module availabilityaa-enabledView examples
Inspect loaded policysudo aa-statusView examples
Inspect one task labelpid=$(systemctl show reportd.service -p MainPID \ --value); sudo cat "/proc/$pid/attr/current"View examples
Find recent denialssudo journalctl -k --grep='apparmor="DENIED"' \ --since='15 minutes ago'View examples
Query Audit recordssudo ausearch -m AVC,USER_AVC -ts recent | grep 'apparmor="DENIED"'View examples
Include base abstraction#include <abstractions/base>View examples
Include local overrides#include if exists <local/usr.local.bin.reportd>View examples
Allow recursive reads/srv/reporting/** r,View examples
Allow directory traversal/srv/reporting/ r,View examples
Limit writes to owned filesowner /var/lib/reportd/** rwk,View examples
Execute without transition/usr/bin/logger ix,View examples
Allow TCP networkingnetwork inet stream,View examples
Validate without loadingapparmor_parser -Q /etc/apparmor.d/usr.local.bin.reportdView examples
Expand includesapparmor_parser -p /etc/apparmor.d/usr.local.bin.reportdView examples
Replace one loaded profilesudo apparmor_parser -r \ /etc/apparmor.d/usr.local.bin.reportdView examples
Load complain mode temporarilysudo apparmor_parser -C -r \ /etc/apparmor.d/usr.local.bin.reportdView examples
Persist complain modesudo aa-complain /usr/local/bin/reportdView examples
Restore enforce modesudo aa-enforce /usr/local/bin/reportdView examples
Review logged behaviorsudo aa-logprofView examples
Disable one profilesudo aa-disable /usr/local/bin/reportdView examples

AppArmor confines programs with named, path-oriented profiles in addition to normal Unix permissions. A safe workflow starts by proving which profile and executable attachment are active, capturing a representative denial, and translating only the intended behavior into narrow rules. Validate profiles without loading them, keep site changes in local includes when packaged profiles support them, stage learning deliberately, and restore enforce mode before calling a deployment complete.

Step by step

Detailed examples

01

Prove that AppArmor and the expected attachment are active

A loaded module, a loaded profile, and a confined process are different facts. aa-enabled checks kernel availability; aa-status summarizes loaded policy and task modes. For a specific verified PID, /proc/PID/attr/current exposes its label and whether it is enforcing or complaining. Confirm the executable path as well: path changes, symlinks, wrappers, containers, and service managers can produce a different attachment than the profile filename suggests.

Inspect module, loaded profiles, and one service process
aa-enabled
sudo aa-status
pid=$(systemctl show reportd.service -p MainPID --value)
sudo cat "/proc/$pid/attr/current"
systemctl show reportd.service -p ExecStart
Back to quick reference ↑
02

Read the denied operation, profile, target, and requested mask together

Reproduce one known failing action and constrain log queries to its time window. An AppArmor event identifies the profile, operation, target name, requested and denied masks, process, and sometimes network fields. Verify that the action is a legitimate application requirement before changing policy. An explicit deny rule may intentionally suppress logging, and missing AppArmor events can mean the failure came from DAC, seccomp, capabilities, a read-only mount, or another control layer.

Collect recent kernel and Audit evidence
sudo journalctl -k --grep='apparmor="DENIED"' --since='15 minutes ago' --no-pager
sudo ausearch -m AVC,USER_AVC -ts recent | grep 'apparmor="DENIED"'
Back to quick reference ↑
03

Anchor the profile to an executable and a declared feature ABI

Profiles normally live under /etc/apparmor.d and use filenames derived from executable paths, but the profile header controls attachment. Include tunables/global and an explicit ABI where supported by the distribution, then import only suitable abstractions. Abstractions are maintained rule sets, not harmless macros: inspect their expansion and security effect. Flags such as attach_disconnected or chroot_relative change path mediation semantics and should not be copied into a profile without a demonstrated need.

Minimal named service profile
abi <abi/4.0>,
#include <tunables/global>

/usr/local/bin/reportd {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include if exists <local/usr.local.bin.reportd>

  /usr/local/bin/reportd mr,
  /etc/reportd/config.yaml r,
}
Back to quick reference ↑
04

Express path access precisely and account for directory mediation

A trailing slash describes a directory, while * does not cross slash boundaries and ** does. Applications often need directory traversal plus separate file permissions, atomic-replace access in a parent directory, or lock permissions in addition to file writes. Use owner only when the file-system UID relationship is a real invariant. Avoid broad /** write rules: they conceal which state, configuration, secret, or executable the service actually needs.

Separate immutable input, owned state, and log append access
/srv/reporting/ r,
/srv/reporting/** r,

/var/lib/reportd/ rw,
owner /var/lib/reportd/** rwk,

/var/log/reportd/ rw,
/var/log/reportd/*.log a,
Back to quick reference ↑
05

Choose execution transitions and non-file permissions deliberately

Execution modes are security boundaries: ix inherits the current profile, px requires a discrete profile transition, and ux executes unconfined and should generally be rejected in production policy. File access alone does not grant networking, capabilities, signals, ptrace, D-Bus, mounts, or other mediated resources. Grant the narrow family, type, capability, or peer relationship demonstrated by the workload; a capability rule still confers a powerful kernel privilege.

Constrain a trusted helper and outbound socket family
/usr/bin/logger ix,
network inet stream,
capability net_bind_service,
signal (receive) set=(term) peer=unconfined,
Back to quick reference ↑
06

Keep site policy separate from packaged profile ownership

When a vendor profile contains an optional local include, place site-specific rules in the matching /etc/apparmor.d/local file. This reduces package-upgrade conflicts and makes the local security delta auditable. The include must appear inside the intended profile, and its filename is based on the profile's policy filename—not an arbitrary application label. Version the local file, document why every rule exists, and verify that the package profile still includes it after upgrades.

Local additions for a packaged-style profile
# /etc/apparmor.d/local/usr.local.bin.reportd
/etc/reportd/site.yaml r,
/srv/customer-reports/ r,
/srv/customer-reports/** r,
Back to quick reference ↑
07

Compile without loading, review expansion, then replace one profile

Run apparmor_parser with -Q to compile while skipping the kernel load, and use -p when include expansion needs review. Only after review should -r replace the loaded profile. The on-disk file is the persistent source that the boot-time service will load; the kernel copy is current runtime state. Reloading all AppArmor policy increases blast radius, so prefer replacing the one changed profile and verify its loaded mode and application behavior immediately.

Validate and deploy a single reviewed profile
apparmor_parser -Q /etc/apparmor.d/usr.local.bin.reportd
apparmor_parser -p /etc/apparmor.d/usr.local.bin.reportd
sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.reportd
sudo aa-status
Back to quick reference ↑
08

Bound learning mode and distinguish runtime from persistent changes

Complain mode logs most policy violations instead of enforcing them, so it is a security reduction rather than a harmless verbosity setting. apparmor_parser -C -r forces the running replacement into complain mode without changing the source profile; the next normal reload restores the source-declared mode. aa-complain changes the on-disk mode and reloads it, so it persists until aa-enforce reverses it. Exercise a documented test matrix, review aa-logprof suggestions individually, and finish by verifying enforce mode.

Time-box learning and explicitly restore enforcement
sudo apparmor_parser -C -r /etc/apparmor.d/usr.local.bin.reportd
sudo aa-logprof
sudo aa-enforce /usr/local/bin/reportd
sudo aa-status
Back to quick reference ↑
09

Roll back policy changes without silently abandoning confinement

Keep the last reviewed profile and local include so a bad change can be reverted and reloaded as one unit. aa-disable both unloads and persistently disables a profile, leaving matching executions unconfined; it is an emergency action, not a troubleshooting shortcut. If it is explicitly authorized, record the exposure window and restore the profile with aa-enforce after fixing the source. Do not unload all profiles or disable AppArmor globally to solve one application's denial.

Reload a reviewed rollback and confirm enforcement
apparmor_parser -Q ./usr.local.bin.reportd.reviewed
sudo apparmor_parser -r ./usr.local.bin.reportd.reviewed
sudo aa-enforce /usr/local/bin/reportd
sudo aa-status
Back to quick reference ↑

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. Ubuntu Server DocumentationAppArmorubuntu.com
  2. Ubuntu Security DocumentationAppArmor Security Profilesdocumentation.ubuntu.com
  3. Ubuntu Manpage Repositoryapparmor_parser(8)manpages.ubuntu.com
  4. AppArmor ProjectAppArmor Userspace Projectgitlab.com
  5. SUSESecurity and Hardening Guide: AppArmordocumentation.suse.com

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback