Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Understand and use AppArmor

By updated

Improve this page

AppArmor is the mandatory access control (MAC) of Debian, Ubuntu and openSUSE: it confines each application in a profile that lists the files, capabilities and network access it is entitled to, even once compromised. Where SELinux labels files, AppArmor reasons by paths, which makes it easier to pick up. This guide is for the Debian or Ubuntu administrator who wants to confine a service: check the state of the profiles, generate one for Nginx with aa-genprof, refine it with abstractions, then switch it to enforce without breaking the service.

What you will learn

  • Understand AppArmor's profile-based mandatory access control
  • Check the state of profiles with aa-status and the enforce/complain modes
  • Generate a profile for a service and adjust it from the logs
  • Switch a profile to blocking mode without breaking the application
  • Diagnose an AppArmor denial in the logs

A short history

AppArmor was born in the early 2000s at the company Immunix, with the idea of creating a simpler alternative to SELinux, too complex for many users. Acquired by Novell in 2005, it was integrated into SUSE Linux Enterprise before joining other distributions such as Ubuntu and Debian.

Why use AppArmor?

AppArmor is much more than a simple configuration tool. It plays an essential role in protecting your Linux systems by setting up mandatory access control (MAC) that limits what applications can do, even if they are compromised. Here is why AppArmor is essential to strengthen the security of your servers and applications:

AppArmor acts as an extra layer of defence by limiting the permissions of applications to the resources they need. In case of a security flaw or an exploit in an application (such as a web server), AppArmor prevents the attacker from accessing critical files, executing commands or compromising the whole system. For example:

  • If a vulnerability is exploited in Nginx, AppArmor can prevent access to sensitive directories such as /etc/passwd or /root/.

Each application is confined within a strict permission profile that determines exactly what it can do:

  • Read or write specific files.
  • Network access or command execution.

By minimising permissions, AppArmor limits the potential damage of an attack, even if an application is compromised.

Unlike reactive security tools such as antivirus software or firewalls, AppArmor acts upstream. It prevents unauthorised actions before they happen, by applying preventive security policies.

AppArmor provides full isolation of applications, preventing unwanted interactions with the rest of the system or with other applications.

AppArmor also offers full traceability of system access through its detailed logs:

  • Enforce mode: unauthorised actions are blocked and recorded.
  • Complain mode: violations are recorded so you can adjust profiles without disturbing the application.

These logs let you audit the behaviour of applications and quickly identify problems or suspicious access attempts.

AppArmor is therefore an essential tool for any Linux infrastructure that cares about keeping a high level of security. By limiting application permissions and strengthening their confinement, it protects your systems against a wide range of threats, making your servers more robust and resilient to attacks.

Is it complicated to implement?

Unlike SELinux, which relies on a global security policy that is often hard to configure, AppArmor bets on simplicity and modularity. Rather than examining the system as a whole, it focuses its protection on each application through individual profiles, describing precisely the resources it is entitled to. This more intuitive granular approach has won over a wide audience, from beginners to seasoned administrators, letting them quickly strengthen security without drowning in complex rules.

This simplicity has a trade-off to know in order to choose with your eyes open: AppArmor reasons by file path (pathname-based), not by inode label like SELinux. A hard link, a bind mount or a renamed binary can therefore escape a rule, and AppArmor places fewer kernel hooks than SELinux. It remains sufficient and much simpler in the vast majority of cases; SELinux keeps the edge in environments with maximum requirements.

Features and concepts

Before putting AppArmor into practice, it is essential to understand its mechanisms. Thanks to an approach based on individual profiles and great granularity in defining permissions, AppArmor offers a security solution that is both simple and effective. Here are the main concepts to remember:

  • Operating modes:
    • Enforce: unauthorised actions are blocked and logged apparmor="DENIED".
    • Complain: implicit denials are not blocked but logged apparmor="ALLOWED", which lets you adjust the profile without interrupting the application. Important pitfall: an explicit deny rule is still applied even in complain mode. Complain is therefore not "without protection".
  • AppArmor profiles: each application is governed by a profile specifying the files, directories, libraries and network resources it can access. This isolates each program within its own security perimeter.
  • Permission granularity: rules are defined down to the file path level, allowing very fine control over what the application is allowed to read, write or execute.
  • Audit and reporting: when an application tries to access an unplanned resource, AppArmor generates detailed logs. They help understand the application's real needs, fine-tune profiles and follow the evolution of the security policy over time.
  • Simplicity and flexibility: command-line tools such as aa-genprof, aa-complain, aa-enforce or apparmor_parser make it easy to create, modify and deploy profiles, without requiring deep security expertise. This flexibility makes it easy to adapt AppArmor to changes in the system and to newly deployed services.

Prerequisites

  • A distribution where AppArmor is the native MAC: Debian, Ubuntu or openSUSE. On RHEL/Rocky, it is SELinux: to decide, see the comparison SELinux or AppArmor (in French).
  • root access (or sudo).
  • The apparmor-utils package for the aa-* tools (and apparmor-notify for aa-notify).
  • Preferably a test VM: a profile in enforce mode that is too strict can prevent a service from starting.

Installation and configuration

Before you start creating or modifying profiles, make sure AppArmor is properly installed and operational on your system. On popular Linux distributions, such as Ubuntu or Debian, AppArmor is often already present and enabled by default. If that is not the case, installation remains simple and quick.

To check the current state of AppArmor, you can use the following command:

Terminal window
sudo apparmor_status
apparmor module is loaded.
134 profiles are loaded.
39 profiles are in enforce mode.
4 profiles are in complain mode.
transmission-cli
transmission-daemon
transmission-gtk
transmission-qt
91 profiles are in unconfined mode.
wpcom
0 processes have profiles defined.
0 processes are in enforce mode.
0 processes are in complain mode.
0 processes are in prompt mode.
0 processes are in kill mode.
0 processes are unconfined but have a profile defined.
0 processes are in mixed mode.

This report tells you whether the AppArmor kernel module is loaded, which mode your current profiles run in and how many processes are actually confined. AppArmor has no daemon: the kernel is what applies the profiles.

If you find that AppArmor is not installed, you can install it through your package manager. For example, on Ubuntu or Debian, enter:

Terminal window
sudo apt update
sudo apt install apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra

These tools, including apparmor-utils, give you the features you need to manage your profiles, switch them to complain or enforce mode, and create new ones.

Once the installation is done, make sure AppArmor is enabled at boot. On modern distributions using systemd, you can check the service with the command below. The apparmor.service unit is of type oneshot with RemainAfterExit=yes (read on Ubuntu 24.04 and Debian 13): it loads the profiles then stops, so a healthy machine shows active (exited). The output below, captured in an LXC container (note the zzz-lxc-service.conf drop-in), shows inactive (dead) instead: profiles do not load there.

Terminal window
sudo systemctl status apparmor
○ apparmor.service - Load AppArmor profiles
Loaded: loaded (/usr/lib/systemd/system/apparmor.service; enabled; preset: enabled)
Drop-In: /run/systemd/system/service.d
└─zzz-lxc-service.conf
Active: inactive (dead)
Docs: man:apparmor(7)
https://gitlab.com/apparmor/apparmor/wikis/home/

If the service is not enabled, you can start it and enable it for the next reboots:

Terminal window
sudo systemctl start apparmor
sudo systemctl enable apparmor

On Debian, Ubuntu and openSUSE, AppArmor is loaded by default without any kernel parameter. Check it with cat /sys/module/apparmor/parameters/enabled (which returns Y) and cat /sys/kernel/security/lsm (which contains apparmor). The security=apparmor (or apparmor=1) option is only used to re-enable it if someone explicitly turned it off with apparmor=0 in GRUB. On a recent installation, you therefore have nothing to add to the boot loader.

Creating and managing profiles

One of the main strengths of AppArmor lies in its management through individual profiles. Each profile defines precisely the resources a given application is allowed to access, which isolates it and limits the impact of a security flaw.

AppArmor profiles are stored in the /etc/apparmor.d/ directory, with one file per application, named after the binary's path whose / become dots: the profile of /usr/sbin/nginx is called usr.sbin.nginx. They are simple text files, editable with your favourite editor, which makes them easy to adapt to your needs.

Generate an initial profile

There are two ways to get a first profile: write it by hand then load it with apparmor_parser, or let aa-genprof learn it by observing the application. The first hides a pitfall about the loading mode.

A hand-written profile is loaded straight into enforce mode

Worth knowing before writing your first profile, because it goes against the method taught everywhere, "complain first, enforce afterwards". apparmor_parser -r loads the profile in enforce mode, immediately.

Measured on Ubuntu 24.04 with apparmor 4.0.1: the aa-status counter goes from 23 to 24 profiles in enforce mode right after loading, without any aa-complain being involved. The program is therefore blocked from the first second, before you could observe what it needs.

The countermeasure fits in one command, to run right after loading:

Terminal window
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
sudo aa-complain /usr/sbin/nginx # do not forget

aa-genprof, on the other hand, handles this switch for you: one more reason to go through it when it is usable.

To create a base profile, use the aa-genprof tool:

Terminal window
sudo apt install nginx curl
sudo aa-genprof /usr/sbin/nginx

Debian 12 and 13 pitfall: aa-genprof cannot find the log

Since Debian 12, rsyslog is no longer installed by default and /var/log/syslog does not exist (everything goes through journald). Yet /etc/apparmor/logprof.conf only looks for /var/log/audit/audit.log, /var/log/syslog and /var/log/messages (read on Debian 13, apparmor-utils 4.1.0). aa-genprof and aa-logprof then fail with ERROR: Can't find system log "/var/log/syslog". The workaround, keeping a stream of the kernel log in a second terminal:

Terminal window
journalctl -b -k -e -f > /var/tmp/aa.log &
sudo aa-genprof -f /var/tmp/aa.log /usr/sbin/nginx

When aa-genprof or aa-logprof suggests abstractions, sort them: the tool often suggests off-topic includes (for example dovecot-common, postfix-common or totem for an nginx profile). Refuse anything that does not match the application.

For example, when trying to start the Nginx service:

Terminal window
sudo systemctl restart nginx
Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.

In the terminal window where you started aa-genprof, you will see the access attempts blocked by AppArmor appear. You can allow or refuse them, depending on your application's needs.

[(S)can system log for AppArmor events] / (F)inish
Reading log entries from /var/log/syslog.
Complain-mode changes:
Enforce-mode changes:
Profile: /usr/sbin/nginx
Path: /var/log/nginx/error.log
New Mode: w
Severity: 8
[1 - /var/log/nginx/error.log w,]
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / Abo(r)t / (F)inish
Profile: /usr/sbin/nginx
Path: /var/log/nginx/error.log
New Mode: w
Severity: 8
[1 - /var/log/nginx/error.log w,]
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / Abo(r)t / (F)inish
Adding /var/log/nginx/error.log w, to profile.
= Changed Local Profiles =
The following local profiles were changed. Would you like to save them?
[1 - /usr/sbin/nginx]
(S)ave Changes / Save Selec(t)ed Profile / [(V)iew Changes] / View Changes b/w (C)lean profiles / Abo(r)t
Writing updated profile for /usr/sbin/nginx.

This tool guides you by observing the application's behaviour. In complain mode, AppArmor does not prevent access, but logs it. By testing Nginx, for example by restarting the service, you will see all the access attempts appear, which you then only have to allow or refuse.

The file built by aa-genprof is a good starting point, but it may need adjustments to match your needs exactly.

# Last Modified: Fri Dec 13 06:45:45 2024
abi <abi/4.0>,
include <tunables/global>
/usr/sbin/nginx {
include <abstractions/base>
include <abstractions/nameservice>
capability dac_override,
/usr/sbin/nginx mr,
/var/log/nginx/access.log w,
/var/log/nginx/error.log w,
owner /etc/group r,
owner /etc/nginx/mime.types r,
owner /etc/nginx/nginx.conf r,
owner /etc/nginx/sites-available/default r,
owner /etc/nsswitch.conf r,
owner /etc/passwd r,
owner /run/nginx.pid r,
owner /run/nginx.pid w,
}

This file contains the basic rules for Nginx, allowing read and write access to some configuration and log files. You can refine it by adding or removing rules, depending on your application's needs.

Adjust and customise profiles

If we try to view the Nginx home page, we get:

Terminal window
curl http://localhost
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.24.0 (Ubuntu)</center>
</body>
</html>

The logs show that access was blocked:

Terminal window
sudo journalctl | grep -i apparmor | tail -n 10
Dec 13 06:44:54 calm-tadpole kernel: audit: type=1400 audit(1734072294.310:319): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="/usr/sbin/nginx" pid=2276 comm="apparmor_parser"
Dec 13 06:45:00 calm-tadpole kernel: audit: type=1400 audit(1734072300.682:320): apparmor="DENIED" operation="capable" class="cap" profile="/usr/sbin/nginx" pid=2286 comm="nginx" capability=7 capname="setuid"
Dec 13 06:45:45 calm-tadpole kernel: audit: type=1400 audit(1734072345.725:321): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="/usr/sbin/nginx" pid=2444 comm="apparmor_parser"
Dec 13 06:54:22 calm-tadpole kernel: audit: type=1400 audit(1734072862.240:322): apparmor="DENIED" operation="open" class="file" profile="/usr/sbin/nginx" name="/var/www/html/index.nginx-debian.html" pid=2534 comm="nginx" requested_mask="r" denied_mask="r" fsuid=33 ouid=0

Once the initial profile is created, you can refine it by editing the file in /etc/apparmor.d/ directly. The rules state the allowed actions (read, write, execute) for each file or directory. This journal reports two distinct gaps, and the generated profile hides a third one:

  • capname="setuid": the Nginx master runs as root and must change identity to start its workers as www-data. It needs capability setuid and capability setgid;
  • the read of /var/www/html/index.nginx-debian.html: a rule on the directory alone (/var/www/html/ r,) is not enough, its content must also be covered with the ** pattern;
  • the profile only allows specific files of /etc/nginx/, whereas Nginx also reads the conf.d/, modules-enabled/ and sites-enabled/ directories. Without them it starts with no site at all, and nothing listens on port 80. The /etc/nginx/** r, rule covers the whole configuration, read-only.
abi <abi/4.0>,
include <tunables/global>
/usr/sbin/nginx {
include <abstractions/base>
include <abstractions/nameservice>
capability dac_override,
capability setuid,
capability setgid,
/usr/sbin/nginx mr,
/etc/nginx/** r,
/var/log/nginx/*.log w,
/run/nginx.pid rw,
/var/www/html/ r,
/var/www/html/** r,
}

But we will see that there is a simpler way to add rules to an existing profile, using includes.

Include existing profiles

To avoid repeating common rules across several profiles, AppArmor offers the include directive. It adds the rules of a shared file to a profile, simplifying the management of shared permissions.

In our example, Nginx shares common rules with other services, such as lighttpd or Apache. To avoid repeating them, we can include them directly in the Nginx profile, inside its block:

/usr/sbin/nginx {
include <abstractions/base>
include <abstractions/nameservice>
...
}

These directives inject common rules, simplifying the management of shared permissions. You can also create your own include files, to group rules specific to your environment.

Include files are stored in the /etc/apparmor.d/abstractions/ directory.

apache2-common
apparmor_api
aspell
audio
...
wayland
web-data
winbind
wutmp
X
xad
xdg-desktop
xdg-open

Among the last configuration files, we can see that the web-data rules exist. Let us check what they contain:

Terminal window
cat /etc/apparmor.d/abstractions/web-data
# ------------------------------------------------------------------
#
# Copyright (C) 2002-2006 Novell/SUSE
# Copyright (C) 2014 Canonical Ltd
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
abi <abi/4.0>,
/srv/www/htdocs/ r,
/srv/www/htdocs/** r,
# virtual hosting
/srv/www/vhosts/ r,
/srv/www/vhosts/** r,
# mod_userdir
@{HOME}/public_html/ r,
@{HOME}/public_html/** r,
/srv/www/rails/*/public/ r,
/srv/www/rails/*/public/** r,
/var/www/html/ r,
/var/www/html/** r,
# Include additions to the abstraction
include if exists <abstractions/web-data.d>

So we can include them in our Nginx profile, instead of the two /var/www/html/ rules written by hand. Here is the complete profile:

abi <abi/4.0>,
include <tunables/global>
/usr/sbin/nginx {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/web-data>
capability dac_override,
capability setuid,
capability setgid,
/usr/sbin/nginx mr,
/etc/nginx/** r,
/var/log/nginx/*.log w,
/run/nginx.pid rw,
}

After each change, reload the profile:

Terminal window
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx

Change the mode of a profile

After adjusting our profile, we can switch it to "enforce" mode to really block unauthorised access, then check the service:

Terminal window
sudo aa-enforce /usr/sbin/nginx
sudo systemctl restart nginx
ps -o user=,comm= -C nginx
curl -s -o /dev/null -w "%{http_code}\n" http://localhost/

The test reads on three signs. ps must show the master as root and at least one worker as www-data, curl must answer 200, and journalctl -k | grep DENIED must add nothing more. Measured on 1 October 2026 on Ubuntu 24.04 (Nginx 1.24, apparmor 4.0.1): this profile passes all three checks, systemctl reload nginx included. The generated profile, without the three additions described above, started with no visible error all the same: systemctl showed active, but only the master ran and curl got no answer. An active service is not a service that works: check it.

Conversely, if you want to put a profile back in "complain" mode to observe new actions, use:

Terminal window
sudo aa-complain /usr/sbin/nginx

Maintain profiles over time

Applications evolve. New features, updates or modules may require profile adjustments. Temporarily put the profile back in complain mode to observe the new needs, then modify it and apply "enforce" mode again.

This iterative approach lets you keep precise and suitable profiles, strengthening the security of your services without disturbing how they work.

Troubleshooting

The table starts from the symptom you observe, gives the most likely cause, then the solution.

SymptomProbable causeSolution
aa-genprof: Can't find system log "/var/log/syslog"Debian 12 or 13 without rsyslog (journald only)journalctl -b -k -e -f > /var/tmp/aa.log then aa-genprof -f /var/tmp/aa.log
A service no longer starts after aa-enforcePath missing from the profileBack to aa-complain, reproduce, aa-logprof, then aa-enforce
The profile "does not protect" in complain modeComplain / enforce confusionNormal: complain logs ALLOWED. Only explicit deny rules block in complain mode
A sandbox (Chrome, AppImage) breaks on Ubuntu 24.04apparmor_restrict_unprivileged_userns=1 (default)A dedicated profile rather than a global deactivation (see the Ubuntu docs)
A disabled profile comes backLink left in /etc/apparmor.d/disable/rm /etc/apparmor.d/disable/<profile> then apparmor_parser -r

Put it into practice

Work with an AppArmor profile on a VM: switch it to complain mode, observe the logs, then switch it back to enforce. A timed drill validates the skill under exam conditions.

Key points

  • AppArmor confines each application through a profile based on paths, simpler than SELinux but bypassable through a hard link / bind mount
  • Loaded by default on Debian/Ubuntu/openSUSE, without a kernel parameter
  • Enforce blocks and logs DENIED; complain logs ALLOWED but still applies explicit deny rules
  • Work cycle: aa-complain → generate denials → aa-logprof → aa-enforce
  • On Debian 12 and 13, work around the missing /var/log/syslog for aa-genprof/aa-logprof
  • Sort the abstractions suggested by aa-genprof: refuse the off-topic ones

Knowledge check

Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Conclusion

AppArmor is a powerful and flexible tool to strengthen the security of your Linux systems, while staying simple to configure thanks to its profile-based approach. Throughout this guide, we explored its key features, from installation to creating and managing profiles, along with concrete practical cases. Thanks to abstractions such as web-data, AppArmor lets you centralise permissions and simplify the configuration of common applications.

Whether you are a system administrator or an advanced user, AppArmor offers you an effective way to limit applications' access to the resources they need, thus reducing your system's attack surface. With interactive tools such as aa-genprof and aa-logprof, adapting your profiles to the specific needs of your applications becomes easy while keeping a high level of security.

To go further, do not hesitate to:

  • Test AppArmor on other services and applications of your infrastructure.
  • Use the existing abstractions to simplify your profiles.
  • Create custom rules suited to your specific environments.

By integrating AppArmor into your security strategy, you take one more step towards a robust, protected and better controlled Linux system. Secure your applications now and take control of your system resources with AppArmor.

FAQ: frequently asked questions

The questions below cover the points that come up most often about AppArmor: how it works, the difference with SELinux, the modes and profile management.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn