Ubuntu and Debian use AppArmor to restrict what individual programs can do. It’s the reason MySQL refuses to use a data directory you moved to /data, or a service can’t read a file even though permissions look fine. AppArmor is simpler than SELinux: rules are written per program, as path lists. Most problems take a small local override to fix.

How it works

Each confined program has a profile, a file in /etc/apparmor.d/ listing the paths it may read or write, the capabilities it may use, and network access. Anything not allowed is denied, even for root.

Profiles run in one of two modes:

  • enforce — violations are blocked and logged;
  • complain — violations are only logged. Use it for testing and troubleshooting.

Check the status

sudo aa-status
apparmor module is loaded.
48 profiles are loaded.
46 profiles are in enforce mode.
   /usr/sbin/mysqld
   /usr/sbin/tcpdump
   …
2 profiles are in complain mode.
…

aa-status shows which programs are confined, and which running processes have a profile.

Find denials

sudo journalctl -k | grep -i apparmor | grep DENIED
sudo dmesg | grep -i 'apparmor="DENIED"'
sudo grep DENIED /var/log/syslog          # if rsyslog is installed

A typical line:

apparmor="DENIED" operation="open" profile="/usr/sbin/mysqld" name="/data/mysql/ibdata1" pid=2311 comm="mysqld" requested_mask="r" denied_mask="r"

This tells you the program (profile), the path (name) and the access needed (requested_mask: r = read, w = write, k = lock, m = mmap…).

Fix: add a local override

Don’t edit the main profile file. Package updates replace it. Most profiles include a local file meant for your additions:

/etc/apparmor.d/local/usr.sbin.mysqld

Example: MySQL with its data directory moved to /data/mysql:

# /etc/apparmor.d/local/usr.sbin.mysqld
/data/mysql/ r,
/data/mysql/** rwk,

Reload that profile:

sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld
sudo systemctl restart mysql

Path rule syntax:

  • /dir/ — the directory itself
  • /dir/* — files directly inside it
  • /dir/** — everything below it, recursively
  • Permissions: r read, w write, k lock, m mmap-exec, ix execute inheriting the profile, l link

See move the MySQL data directory.

Complain mode for troubleshooting

sudo apt install apparmor-utils
sudo aa-complain /usr/sbin/mysqld       # log only
# reproduce the problem, collect DENIED/ALLOWED lines
sudo aa-enforce /usr/sbin/mysqld        # back to enforcing

aa-logprof reads the logs and suggests rules interactively. It’s helpful, but review each suggestion.

Quick diagnosis: if switching a profile to complain mode makes the problem disappear, AppArmor is the cause.

Disable a single profile (last resort)

sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld

Prefer a local override. Disabling a profile removes protection for that service. Turning off AppArmor entirely (systemctl disable apparmor) affects every confined program and isn’t recommended on servers.

Which programs are confined on a typical server?

Depending on the Ubuntu or Debian version and installed packages:

  • mysqld (MySQL)
  • tcpdump, man, lsb_release
  • /usr/sbin/named (BIND)
  • Snap applications (all snaps are confined with AppArmor, except classic ones)
  • Docker containers (the docker-default profile)
  • LXD containers

Nginx, Apache and PHP-FPM aren’t confined by default on Ubuntu, so they don’t trigger AppArmor denials unless you install extra profiles (apparmor-profiles).

Docker and AppArmor

Docker applies the docker-default profile to containers. Some workloads (FUSE, nested containers, certain debugging tools) need adjustments:

docker run --security-opt apparmor=unconfined …     # disables AppArmor for this container: use sparingly

A custom profile is safer for production.

AppArmor vs SELinux

AppArmor SELinux
Default on Ubuntu, Debian, SUSE RHEL, Rocky, AlmaLinux, Fedora
Model Path-based, per-program profiles Label-based, system-wide policy
Learning curve Gentler Steeper
Typical fix Add paths in /etc/apparmor.d/local/ semanage fcontext + restorecon, booleans

See SELinux basics.

Quick reference

Task Command
Status sudo aa-status
Denials journalctl -k | grep DENIED
Complain mode sudo aa-complain /path/to/bin
Enforce mode sudo aa-enforce /path/to/bin
Local override /etc/apparmor.d/local/<profile>
Reload profile sudo apparmor_parser -r /etc/apparmor.d/<profile>
Suggest rules sudo aa-logprof

Related: Linux server hardening and dmesg.

  • Linux
  • Security
  • Ubuntu