55
AppArmor locks down what each program can access on your Linux system using simple, path-based security profiles. This guide walks you through checking, installing, creating, and managing those profiles on Ubuntu, Debian, and Arch Linux.
What Is AppArmor and Why Does It Matter?
Mandatory Access Control (MAC) sits on top of the standard Linux Discretionary Access Control (DAC) system. While DAC lets a file owner decide who can read, write, or execute a file, MAC enforces rules set by the system administrator, rules that even root cannot override.
AppArmor is a Linux Security Module (LSM) that implements MAC using path-based profiles. Each profile defines exactly what files, capabilities, and network resources a specific program is allowed to access. If a program tries to access something not listed in its profile, the kernel blocks it. The official AppArmor wiki documents the full architecture.
Why should a beginner care? Because a misconfigured web server, a vulnerable browser extension, or a compromised container can only damage what its AppArmor profile allows. Without MAC, a single exploited process gives an attacker the entire system. With AppArmor, the blast radius is contained.
I have seen production servers taken down by a single exploited service that had full access to the filesystem. AppArmor prevents that scenario by restricting what each program can touch, even if the process runs as root.
Why It Matters: Without MAC, a single compromised process gives the attacker everything the compromised user owns. With AppArmor, that same process can only access the specific files and capabilities its profile allows. The difference between a contained incident and a full system compromise often comes down to whether MAC was enabled.
AppArmor ships enabled by default on Ubuntu and Debian-based systems. On Arch Linux and its derivatives (like CachyOS), you need to enable it manually. On Fedora and RHEL, AppArmor is not the default; those distributions use SELinux instead, which we may cover in a future article.
AppArmor vs SELinux: Which One Are You Running?
Both AppArmor and SELinux implement MAC, but they take fundamentally different approaches:
- AppArmor uses path-based profiles. A profile says “this program can read /etc/nginx/nginx.conf”; it references the file’s filesystem path directly.
- SELinux uses label-based policies. Every file, process, and port gets a security label, and the policy controls which labels can interact.
Here is how to check which one your system uses:
$ cat /sys/fs/selinux/enforced 2>/dev/null && echo "SELinux" || echo "Checking AppArmor..." $ cat /sys/module/apparmor/parameters/enabled 2>/dev/null Y
On Ubuntu, the second command returns Y. On Fedora, the first command returns the SELinux enforcing status instead. If neither module is loaded, your system is running with DAC only, with no MAC protection at all.
The practical difference for beginners: AppArmor profiles are plain text files you can read and edit with any text editor. SELinux policies are compiled from a custom language and are harder to modify by hand. In my experience, that accessibility is why AppArmor is often recommended as a starting point for learning MAC.
Check If AppArmor Is Enabled on Your System
Before installing anything, verify whether AppArmor is already running. On Ubuntu and Debian, it is enabled by default. I recommend checking this first on any new system you administer.
Run the following to check the kernel module status:
$ cat /sys/module/apparmor/parameters/enabled Y
A Y means the AppArmor kernel module is loaded and active. An N means it is not; on Arch Linux, you will see N until you enable it through your bootloader configuration.
For a detailed status report including loaded profiles and their modes, use aa-status:
$ sudo aa-status apparmor module is loaded. 234 profiles are loaded. 156 profiles are in enforce mode. /usr/bin/man /usr/bin/papers /usr/sbin/chronyd /usr/sbin/cups-browsed /usr/sbin/cupsd ... 3 profiles are in complain mode. /usr/sbin/sssd Xorg Xorg_wrap 0 profiles are in prompt mode. 0 profiles are in kill mode. 75 profiles are in unconfined mode. firefox chromium ... 13 processes have profiles defined. 12 processes are in enforce mode. 0 processes are in complain mode.
This output tells you several important things:
- 234 profiles loaded: your system ships with a large set of predefined security policies
- 156 in enforce mode: these profiles actively block unauthorized access
- 3 in complain mode: these profiles log violations but do not block them (useful for testing)
- 75 unconfined: these profiles exist but do not restrict the program at all
You can also check the systemd service status:
$ systemctl status apparmor
● apparmor.service - Load AppArmor profiles
Loaded: loaded (/usr/lib/systemd/system/apparmor.service; enabled; preset: enabled)
Active: active (exited) since Tue 2026-08-25 18:15:34 EDT; 2 days ago
Main PID: 1067 (code=exited, status=0/SUCCESS)
CPU: 265ms
Aug 25 18:15:34 ubuntu systemd[1]: Finished apparmor.service - Load AppArmor profiles.
The active (exited) status is normal; the apparmor service runs once at boot to load profiles, then exits. If you see inactive (dead) on a system where AppArmor should be enabled, something is wrong with your configuration.
Understanding AppArmor Profile Modes
AppArmor profiles operate in three primary modes. I switch between these modes regularly when testing new configurations.
Enforce mode is the default and the most secure. The kernel actively blocks any resource access not explicitly allowed by the profile. If a program tries to write to a file its profile does not permit, the operation fails silently, and the program never knows it was denied.
Complain mode logs violations to the system audit log but does not block them. This is the mode you use when testing a new profile or troubleshooting why a legitimate operation is being denied. Every denied action appears in journalctl or dmesg with an apparmor=”DENIED” entry.
Disabled means the profile is loaded but imposes no restrictions. The program runs as if AppArmor were not there. Unconfined profiles (shown in aa-status) fall into this category, as they exist in the profile directory but have no enforcement rules.
Two additional modes, kill and prompt, are available in newer AppArmor versions. Kill mode terminates the process immediately on a violation. Prompt mode (a Linux 7.x addition) asks the user whether to allow or deny the action, similar to a firewall prompt. These are advanced options and not covered in this beginner guide.
Insight: Think of enforce mode as a locked door and complain mode as an open door with a security camera. Enforce stops the intruder. Complain lets them in but records everything they do. You use complain mode during testing so you can see what the program actually needs before you lock the door.
Installing AppArmor Tools on Ubuntu, Debian, and Arch Linux
Ubuntu and Debian (Primary Target)
AppArmor is already enabled on Ubuntu and Debian. The base system includes the core profiles. To get the management tools that let you create, modify, and switch profiles, install the apparmor-utils package:
$ sudo apt install apparmor-utils Reading package lists... Done Building dependency tree... Done The following additional packages will be installed: python3-apparmor python3-libapparmor The following NEW packages will be installed: apparmor-utils python3-apparmor python3-libapparmor 0 upgraded, 3 newly installed, 0 to remove and 142 not upgraded. Setting up apparmor-utils (5.0.2-0ubuntu1~26.04.1) .
This installs the aa- command suite: aa-status, aa-complain, aa-enforce, aa-genprof, aa-logprof, and others. The package pulls in Python bindings automatically.
For additional profiles beyond the defaults, install apparmor-profiles:
$ sudo apt install apparmor-profiles
Arch Linux and CachyOS (Secondary Target)
On Arch Linux, AppArmor is available in the official repositories but is not enabled by default. First, install the packages:
[cachyos ~]$ sudo pacman -S apparmor resolving dependencies... looking for conflicting packages... Package (2) New Version Net Change Download Size extra/python-legacy-cgi 2.6.4-2 0.18 MiB 0.04 MiB cachyos-extra-v3/apparmor 4.1.7-1.1 4.88 MiB 1.31 MiB Total Download Size: 1.35 MiB Total Installed Size: 5.05 MiB :: Proceed with installation? [Y/n] Y
On Arch and CachyOS, the aa- command tools are included in the base apparmor package; there is no separate apparmor-utils package like on Ubuntu.
Next, you need to enable AppArmor in your bootloader. On Arch Linux with GRUB, edit /etc/default/grub and add the AppArmor kernel parameter:
# Edit /etc/default/grub GRUB_CMDLINE_LINUX_DEFAULT="quiet apparmor=1 security=apparmor" # Regenerate GRUB config $ sudo grub-mkconfig -o /boot/grub/grub.cfg
If you use CachyOS with the Limine bootloader, edit /boot/limine.conf instead. Append apparmor=1 security=apparmor to the existing cmdline value:
# Before (Limine config) cmdline: quiet nowatchdog splash rw rootflags=subvol=/@ root=UUID=... # After cmdline: quiet nowatchdog splash rw apparmor=1 security=apparmor rootflags=subvol=/@ root=UUID=...
After editing, reboot. Then enable the AppArmor service:
$ sudo systemctl enable --now apparmor.service
Verify it loaded correctly:
$ cat /sys/module/apparmor/parameters/enabled Y
On CachyOS, before enabling the kernel parameter, the module reports N and aa-status shows the filesystem is not mounted. After the reboot with the kernel parameter, both switch to the active state.
Fedora and RHEL (Context Only)
AppArmor is not the default MAC on Fedora, RHEL, or any Red Hat-derived distribution. Those systems use SELinux. While the Linux kernel technically supports loading both modules simultaneously (LSM stacking), Fedora does not ship AppArmor tooling in its repositories, and no one maintains AppArmor profiles for Fedora packages.
If you are running Fedora or RHEL, do not attempt to install AppArmor. Use SELinux instead. We may cover SELinux in a future article.
Reading and Understanding Existing AppArmor Profiles
AppArmor profiles live in /etc/apparmor.d/. Each file defines the security policy for one program. Let us look at a real profile to understand the syntax.
The profile for the man command on Ubuntu:
$ cat /etc/apparmor.d/usr.bin.man # vim:syntax=apparmor #include/usr/bin/man { #include /usr/bin/eqn rmCx -> &man_groff, /usr/bin/grap rmCx -> &man_groff, ... /** mrixwlk, unix, capability setuid, capability setgid, deny capability dac_override, deny capability dac_read_search, ... }
Here is what each part means. I find that walking through a real profile is the fastest way to learn the syntax.
- #include
loads global variables (like the parser tuning settings). Every profile needs this line. - /usr/bin/man { … } the profile name matches the program path. Everything inside the braces defines what the program can and cannot do.
- #include
imports a standard set of rules that most programs need (access to /proc, /dev/null, basic libraries, etc.). This saves you from writing dozens of common rules manually. - /usr/bin/eqn rmCx -> &man_groff, allows man to execute eqn and redirect its output to the man_groff name space.
- /etc/myapp/** r, allows reading any file under /etc/myapp/. The double asterisk is a glob that matches everything recursively.
- capability setuid, allows the program to change its user ID (needed for certain operations).
- deny capability dac_override, explicitly blocks the program from bypassing file permission checks.
The abstractions directory contains shared rule sets that multiple profiles can include:
$ ls /etc/apparmor.d/abstractions/ X dbus gnome mysql python apache2-common dbus-session gnupg nameservice qt5 apparmor_api dconf groff nis qt6 base fonts gtk nss-systemd ruby bash freedesktop.org kerberosclient openssl samba crypto gio-open ldapclient perl ssl_certs
For example, #include
Creating Your First AppArmor Profile With aa-genprof
The aa-genprof command walks you through creating a profile interactively. It monitors the system while you run the target program, collects all the resource accesses, and builds a profile from what it observes.
Open two terminals. In the first, start the profile generator for your target program:
$ sudo aa-genprof /usr/bin/myapp
In the second terminal, exercise the program. Run every feature you want the profile to allow: open files, make network connections, create temporary files. The more thoroughly you test, the more complete the profile will be.
When you are done testing, switch back to the first terminal and press S to scan the logs. aa-genprof shows each detected access and asks whether to allow or deny it. Press A to allow, D to deny, or I to skip (ignore) the rule.
When you reach the end, press F to finish. The tool writes the profile to /etc/apparmor.d/usr.bin.myapp and loads it in complain mode by default. I prefer this default because it lets you test without breaking anything.
After creating the profile, review it in a text editor. Clean up any unnecessary rules, add comments explaining what each section does, and verify the file permissions are correct (chmod 644 /etc/apparmor.d/usr.bin.myapp).
Switching Profiles Between Enforce and Complain Mode
Once a profile is loaded, you can switch its mode without reloading it. I use this workflow constantly when testing new profiles or troubleshooting denials on existing ones.
To switch a profile to complain mode (log-only, no blocking):
$ sudo aa-complain /usr/sbin/chronyd Setting /usr/sbin/chronyd to complain mode. 4 profiles are in complain mode. 2 processes are in complain mode.
To switch it back to enforce mode (full blocking):
$ sudo aa-enforce /usr/sbin/chronyd Setting /usr/sbin/chronyd to enforce mode. 158 profiles are in enforce mode. 12 processes are in enforce mode.
You can also apply these commands to all loaded profiles at once:
# Set everything to complain mode (testing) $ sudo aa-complain /etc/apparmor.d/* # Set everything to enforce mode (production) $ sudo aa-enforce /etc/apparmor.d/*
When troubleshooting a denial, switch the offending profile to complain mode, reproduce the issue, review the logs, update the profile, then switch back to enforce mode. This cycle (complain, test, refine, enforce) is the standard workflow for AppArmor profile management.
To disable a profile entirely:
$ sudo aa-disable /etc/apparmor.d/usr.sbin.nginx
This moves the profile to /etc/apparmor.d/disable/ and unloads it. The profile file remains on disk but imposes no restrictions until you re-enable it with aa-enable.
Pro Tip: When testing a new profile, set everything to complain mode first with sudo aa-complain /etc/apparmor.d/*, exercise every feature of the target program, then review sudo journalctl -k | grep apparmor=”DENIED” to see exactly what access the program needs. Fix the profile, then switch back to enforce with sudo aa-enforce /etc/apparmor.d/*.
Troubleshooting Common AppArmor Denials
When AppArmor blocks a legitimate operation, you will see denial messages in the kernel log. I check these logs weekly on production systems. Here is a real example captured from an Ubuntu 26.04 system:
$ sudo journalctl -k | grep -i apparmor | tail -10 Aug 26 18:00:50 ubuntu kernel: audit: type=1400 audit(1787781650.787:279): apparmor="DENIED" operation="open" class="file" profile="snap.firmware-updater.firmware-notifier" name="/proc/sys/vm/max_map_count" pid=13677 comm="firmware-notifi" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 Aug 27 00:13:38 ubuntu kernel: audit: type=1400 audit(1787804018.262:282): apparmor="DENIED" operation="open" class="file" profile="/usr/sbin/cupsd" name="/etc/paperspecs" pid=14991 comm="cupsd" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 Aug 27 00:13:38 ubuntu kernel: audit: type=1400 audit(1787804018.420:283): apparmor="DENIED" operation="capable" class="cap" profile="/usr/sbin/cupsd" pid=14991 comm="cupsd" capability=12 capname="net_admin"
Each line tells you exactly what happened:
- apparmor=”DENIED”: the operation was blocked
- operation=”open”: the program tried to open a file
- profile=”/usr/sbin/cupsd”: which profile caused the denial
- name=”/etc/paperspecs”: which file it tried to access
- requested_mask=”r”: it wanted read access
- denied_mask=”r”: read access was denied
To resolve a denial, you have three options:
- Add the missing rule to the profile: edit the profile file, add the required path or capability, then reload it with sudo apparmor_parser -r /etc/apparmor.d/profile.name
- Switch to complain mode: if you are unsure what access the program needs, switch its profile to complain mode temporarily, reproduce the activity, and let aa-logprof suggest the missing rules
- Check if the program genuinely needs the access: some denials indicate a program doing something it should not. A web server trying to access /etc/paperspecs (a LaTeX specification file) is suspicious. Investigate before blindly allowing it.
The aa-logprof command parses the audit log and interactively suggests profile updates. It is the fastest way to refine a profile based on real usage patterns.
What Linux 7.3 Brings to AppArmor
As of August 2026, the Linux 7.3 merge window has introduced several significant improvements to AppArmor that are worth knowing about, even if you are a beginner.
Compressed policies are the headline feature. I have been waiting for this improvement. AppArmor profiles can now be compressed using zstd (Zstandard) in userspace before being loaded into the kernel. For large systems with hundreds of profiles, this reduces kernel loading time by up to 30%. The compression is transparent; the kernel decompresses profiles automatically, and you write and manage them the same way you always have. This work was contributed by Maxime Bélair of Canonical, the company behind Ubuntu.
Audit mode is a new mechanism to silence complain mode messages. Previously, running a profile in complain mode generated a flood of audit log entries that could overwhelm log storage and make it harder to find real issues. The new audit mode lets you suppress these messages while still tracking denials internally.
Network mediation refactoring cleans up the internal code that handles network access rules. This is preparation for extended inet mediation, more granular control over which network operations a profile allows. The refactoring does not change behavior today but lays the groundwork for future features.
These improvements arrive with AppArmor 4.1.7+ and are included in Ubuntu 26.04 and the latest Arch Linux packages. If you are running an older distribution, you may not have them yet, but the core AppArmor workflow described in this guide works the same way regardless of version.
Worth Knowing: The zstd compression in Linux 7.3 reduces kernel loading time by up to 30% for systems with many profiles. If you manage a server with hundreds of AppArmor profiles, this improvement alone can shave seconds off your boot time. The compression is transparent, so you do not need to change how you write or manage profiles.
Tips for Better Security With AppArmor
After working with AppArmor across dozens of systems, here are the practices that make the biggest difference:
Start with complain mode. I never put a new profile directly into enforce mode. Test it in complain mode, exercise every feature of the target program, review the logs, and only then switch to enforce. This prevents you from accidentally breaking production services.
Use abstractions. I never rewrite rules that already exist. The /etc/apparmor.d/abstractions/ directory contains tested, community-maintained rule sets for common patterns like database access, SSL certificates, and desktop integration. Include them in your profiles.
Add comments. A profile without comments is a maintenance nightmare six months later. Add comments explaining why each rule exists, who approved it, and when it was last reviewed. Future you (or your colleague) will be grateful.
Keep profiles version-controlled. Store your custom profiles in a Git repository. When you update a profile, commit the change with a description of what you modified and why. This gives you a history of security decisions and makes it easy to roll back if something breaks.
Review denials weekly. I run sudo journalctl -k | grep apparmor=”DENIED” at least once a week. A spike in denials can indicate a compromised service trying to access resources it should not, or a software update that changed what the program needs. Either way, you want to know about it.
Do not disable profiles just to fix errors. When a profile blocks something, do not disable it as a shortcut. Take the time to understand what the program needs, add the minimal required rule, and keep the profile active. Disabled profiles are the same as having no MAC protection at all.
Keep AppArmor updated. AppArmor is part of the kernel and the userspace tooling. Security patches and new features arrive through regular system updates. Make sure your kernel and apparmor-utils packages stay current.
Combine with other security layers. I treat AppArmor as one piece of a defense-in-depth strategy. Pair it with firewall rules (ufw, nftables), network monitoring, and regular system audits. No single tool protects against everything.
Be careful with containers. AppArmor profiles apply to the host system, not inside containers. If you are running Docker or LXC, the container runtime applies its own AppArmor profile to the container process. Do not assume a container is automatically confined by the same profiles that protect host services.
Document your decisions. When you deny a capability in a profile, add a comment explaining why. When you allow an unusual access, explain the use case. Security audits require justification, and your future self will not remember the reasoning behind every rule.
Frequently Asked Questions About AppArmor
Is AppArmor enabled by default on Ubuntu?
Yes. AppArmor has been enabled by default on Ubuntu since version 8.04. The kernel module loads automatically, and profiles are activated at boot by the apparmor.service. You do not need to install or configure anything to get basic MAC protection on Ubuntu or Debian.
What is the difference between AppArmor enforce mode and complain mode?
Enforce mode actively blocks any resource access not allowed by the profile. Complain mode logs violations to the audit log but does not block them. Use complain mode when testing new profiles or troubleshooting denials, then switch to enforce mode once the profile is verified.
Can I use AppArmor on Fedora or RHEL?
No. Fedora and RHEL use SELinux as their default MAC system. AppArmor is not included in Fedora repositories, and no one maintains AppArmor profiles for Fedora packages. Use SELinux on those distributions instead.
How do I check if AppArmor is running on my system?
Run cat /sys/module/apparmor/parameters/enabled. If it returns Y, AppArmor is active. For a detailed status report with loaded profiles and their modes, run sudo aa-status.
What does aa-genprof do?
aa-genprof interactively creates AppArmor profiles by monitoring a running program. It watches what files, capabilities, and network resources the program accesses, then builds a profile based on observed behavior. You run the program in a second terminal while aa-genprof collects data, then review and approve the generated rules.
Does AppArmor work inside Docker containers?
AppArmor profiles apply to the host system, not inside containers. Docker and LXC apply their own AppArmor profiles to container processes. A container does not inherit the same profiles that protect host services. For container-specific security, you need to configure container-level AppArmor or seccomp profiles separately.
Further Reading
For official documentation, visit the AppArmor project website and the AppArmor Quick Start Guide. The ArchWiki also maintains a detailed AppArmor page covering Arch-specific configuration.