AppArmor Explained
Learn how AppArmor profiles work, how to switch between enforce and complain mode, create new profiles, and diagnose access denials on Ubuntu, Debian, and Arch.
Before you start
- ▸Root or sudo access on the target system
- ▸Basic familiarity with reading system log output
- ▸The application you want to confine installed and functional before profiling
AppArmor is a Linux Security Module (LSM) that confines programs to a defined set of resources using per-application profiles. Unlike SELinux, which labels every object on the filesystem, AppArmor works with file paths, making profiles easier to read and write. It ships enabled by default on Ubuntu and Debian, and is available on other distributions with a bit of setup. This guide covers the profile system, the enforce/complain mode distinction, and how to diagnose and fix confinement problems.
How AppArmor Works
The kernel loads AppArmor profiles at boot. Each profile is a plain-text ruleset tied to an executable path (e.g., /usr/sbin/nginx). When that executable runs, the kernel enforces the rules. Violations are either blocked and logged (enforce mode) or only logged (complain mode). Processes not covered by any profile run unconfined — AppArmor does not restrict what it does not know about.
- Enforce mode — Rules are actively enforced. Unauthorized access is denied and logged to the audit log or syslog.
- Complain mode — The kernel logs what would have been denied but allows the action. Use this when writing or tuning a new profile.
- Unconfined — No profile loaded for this executable. The process has full normal Unix permissions.
Installation and Status
Debian / Ubuntu
AppArmor is installed and enabled by default on Ubuntu 20.04+ and Debian 11+. Check the status:
sudo aa-status
If the tools are missing, install them:
sudo apt install apparmor apparmor-utils apparmor-profiles apparmor-profiles-extras
Fedora / RHEL / Rocky
These distributions ship with SELinux as the default LSM. Running two LSMs simultaneously is unsupported. If you specifically need AppArmor, you would have to compile a custom kernel — in practice, use SELinux on this family.
Arch Linux
The Arch kernel includes AppArmor support. Install the userspace tools and profiles, then enable it:
sudo pacman -S apparmor
sudo systemctl enable --now apparmor
Add the kernel parameter to GRUB or your bootloader:
# /etc/default/grub — add to GRUB_CMDLINE_LINUX_DEFAULT
apparmor=1 security=apparmor
sudo grub-mkconfig -o /boot/grub/grub.cfg
Reboot and confirm with sudo aa-status.
Understanding Profiles
Profiles live in /etc/apparmor.d/. A minimal profile for a hypothetical application looks like this:
cat /etc/apparmor.d/usr.sbin.nginx
A real nginx profile contains blocks like:
/usr/sbin/nginx {
#include <abstractions/base>
#include <abstractions/nameservice>
capability net_bind_service,
capability setuid,
capability setgid,
/var/log/nginx/** rw,
/etc/nginx/** r,
/usr/share/nginx/** r,
/run/nginx.pid rw,
network tcp,
}
Rule syntax uses permission letters: r (read), w (write), x (execute), m (memory-map executable), k (lock), l (link). Glob patterns (** for recursive, * for single component) are supported. The #include abstraction files bundle common rule sets so you do not repeat boilerplate.
Managing Modes: enforce, complain, disable
Set a Single Profile to Complain Mode
sudo aa-complain /usr/sbin/nginx
Set a Single Profile to Enforce Mode
sudo aa-enforce /usr/sbin/nginx
Disable a Profile Entirely
sudo aa-disable /usr/sbin/nginx
This creates a symlink in /etc/apparmor.d/disable/ and removes the profile from the running kernel. To re-enable:
sudo aa-enable /usr/sbin/nginx
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
Reload All Profiles
sudo systemctl reload apparmor
Creating a New Profile
The aa-genprof tool automates profile creation. It puts the application in complain mode, asks you to exercise it, then generates allow rules from the log.
sudo aa-genprof /usr/local/bin/myapp
In a second terminal, run your application through all its normal operations. Back in aa-genprof, press S to scan the logs, then answer the prompts to allow or deny each access. Press F to finish and save. The tool writes the profile to /etc/apparmor.d/ and switches it to enforce mode.
For an already-running application, use aa-logprof to parse existing logs and propose rule additions:
sudo aa-logprof
Verifying Confinement
Check which profiles are loaded and in which mode:
sudo aa-status
Sample output (abbreviated — yours will differ):
apparmor module is loaded.
72 profiles are loaded.
55 profiles are in enforce mode.
17 profiles are in complain mode.
0 profiles are in kill mode.
...
Confirm a specific process is confined:
cat /proc/$(pgrep nginx | head -1)/attr/current
A confined process shows its profile name and mode (e.g., nginx (enforce)). An unconfined process shows unconfined.
Troubleshooting Denials
Reading Denial Logs
AppArmor writes denials to the audit log or, if auditd is not running, to /var/log/syslog or the journal.
sudo journalctl -k --grep="apparmor" | tail -30
# If auditd is running
sudo ausearch -m avc -ts recent
A denial line looks like:
kernel: audit: type=1400 audit(...) apparmor="DENIED" operation="open" \
profile="nginx" name="/etc/ssl/private/server.key" \
pid=1234 comm="nginx" requested_mask="r" denied_mask="r" fsuid=33 ouid=0
The important fields: profile (which profile fired), name (the resource), requested_mask (what the process wanted), denied_mask (what was blocked).
Adding a Missing Rule Manually
Open the profile and add the rule, then reload:
sudo nano /etc/apparmor.d/usr.sbin.nginx
# Add: /etc/ssl/private/server.key r,
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
Using aa-logprof to Patch a Profile
sudo aa-logprof
This reads all new denials since the last run, presents each one, and proposes a rule. Accepting a suggestion writes it directly to the profile. Run sudo systemctl reload apparmor afterward to activate changes.
Common Problems
- Service starts but fails silently — Almost always a denial. Switch to complain mode, restart the service, check logs, then use
aa-logprof. - Profile exists but is not loaded — Run
sudo apparmor_parser -a /etc/apparmor.d/your.profileto load it without rebooting. - Child process runs unconfined — The parent profile needs an explicit
cxorpxtransition rule for the child executable. Useaa-logprofto catch these. - Network access denied — Add a
networkrule specifying the address family and socket type, e.g.,network inet stream,. - Changes not taking effect — Confirm the profile path matches the exact binary path with
which myapporrealpath /path/to/bin. Symlinks can cause mismatches.
Frequently asked questions
- Does AppArmor replace normal Unix file permissions?
- No. AppArmor is an additional layer. A process must satisfy both standard DAC (owner/group/other) permissions and its AppArmor profile. Granting a path in a profile does not override filesystem permissions.
- Can I run AppArmor and SELinux at the same time?
- No. The Linux kernel only activates one LSM security module at boot. Choose one. Debian/Ubuntu default to AppArmor; Fedora, RHEL, and Rocky default to SELinux.
- Will disabling a profile affect other profiles or the system security generally?
- Disabling a profile only removes confinement for that specific executable; all other profiles remain active. The process then runs with standard Unix permissions only, which may be sufficient risk depending on the application.
- How do I confine a script rather than a compiled binary?
- Create the profile keyed to the interpreter path (e.g., /usr/bin/python3) and use a hat or child profile for your script. Alternatively, wrap the script in a small compiled launcher binary that AppArmor can track directly.
- Why does my service work fine in complain mode but break in enforce mode even after running aa-logprof?
- Some code paths only execute under specific conditions (error handling, infrequent features, signal handling). Exercise every code path during profiling, or watch enforce-mode denials in production and run aa-logprof again to catch the remaining gaps.
Related guides
Manage Secrets with Ansible Vault
Encrypt Ansible secrets with AES-256 using ansible-vault: encrypt files and inline vars, automate with password files, and isolate group-level secrets with vault IDs.
Apply CIS Benchmarks with OpenSCAP
Use OpenSCAP and scap-security-guide to evaluate, report on, and remediate Linux systems against CIS Benchmarks — covering install, eval, and automation.
How to Audit a Linux System with auditd
Set up auditd on Linux to track file access, syscalls, and privilege use. Covers persistent rules, file watches, ausearch, and aureport across major distros.
How to Audit Linux Hardening with Lynis
Run Lynis to audit your Linux server, interpret the hardening index and warning output, and work through findings from critical to low-effort wins.