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:
rread,wwrite,klock,mmmap-exec,ixexecute inheriting the profile,llink
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-defaultprofile) - 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.