How to Configure AppArmor Profiles, Complain Mode, and Security Enforcement on Ubuntu Server

Traditional Linux Discretionary Access Control (DAC) models rely exclusively on file ownership and permission bits (rwxrwxrwx). Under DAC, if an attacker successfully exploits a vulnerability in a web server or database running under a service account, the compromised process inherits all rights possessed by that user, allowing lateral movement across accessible directories. Ubuntu Server addresses this security deficit natively through AppArmor, a lightweight Mandatory Access Control (MAC) Linux Security Module (LSM).

Unlike SELinux, which relies on complex inode labeling and type enforcement rules, AppArmor binds security policies directly to filesystem executable pathnames. By declaring explicit profiles stored in /etc/apparmor.d/, administrators can restrict a compromised process so it can only read specified configuration files, bind to authorized network sockets, and access declared directories—even if the underlying process is running with root privileges.

Architectural Overview: AppArmor Profile Modes and Enforcement Mechanics

AppArmor mediates access requests between userland binaries and the Linux VFS layer based on three operational profile modes:

Profile State Kernel Behaviour Upon Violation Logging Action Deployment Phase
Enforce Mode Access is denied immediately with EACCES (Permission Denied). Security audit event logged to auditd or syslog. Production deployment; strict security containment.
Complain Mode Access is permitted, allowing the process to continue execution normally. Security audit event logged with flag apparmor="ALLOWED". Development and baseline discovery; profiling new application behaviour.
Unconfined AppArmor does not monitor or intercept the process. No security events or audit trails generated. Standard system utilities not bound to an active security profile.

AppArmor Profile Syntax and Permission Tokens

Profiles are plain text files named after the path of the program, replacing slashes with dots (e.g. /usr/sbin/nginx becomes /etc/apparmor.d/usr.sbin.nginx). Key permission modifiers include:

Permission Token Access Capability Granted
r Read data from the target file or directory.
w Write data or truncate existing file contents.
a Append-only write access (ideal for application log files).
k File locking permissions.
px / Px Discrete execution under a named child profile (scrubs environment variables if capitalised).
ix Inherit the parent process’s AppArmor confinement profile upon execution.
network inet stream Permits creation of IPv4 TCP stream sockets.
capability net_bind_service Permits binding to privileged ports below 1024 without granting full root capabilities.

Installing AppArmor Utilities and Auditing Active Status

Ubuntu Server ships with AppArmor active by default. Install the administrative userland management suite to unlock automated profiling tools:

# Install AppArmor administrative utilities and audit daemon
sudo apt update
sudo apt install -y apparmor-utils apparmor-profiles auditd

# Inspect overall AppArmor confinement status
sudo aa-status

The aa-status utility provides an executive summary detailing how many profiles are loaded, which daemons operate in enforce mode, and which processes remain unconfined.

Creating a Custom AppArmor Profile Using aa-genprof

Follow this procedure to create an AppArmor profile for a custom Python microservice listening on port 8080 (/opt/microservice/app.py).

Step 1: Initiate Interactive Profile Generation

Launch the automated profiling wizard:

sudo aa-genprof /usr/bin/python3

aa-genprof creates an initial baseline profile in complain mode and directs you to exercise the application in another terminal window.

Step 2: Exercise Application Functionality

Open a secondary terminal session and stimulate normal application workloads (start the service, hit API endpoints, trigger log outputs):

python3 /opt/microservice/app.py &
curl -I http://127.0.0.1:8080/api/health
curl -X POST http://127.0.0.1:8080/api/ingest -d '{"event":"test"}'

Step 3: Scan Audit Logs and Commit Profile Rules

Return to the aa-genprof prompt and press S to scan audit logs. The utility presents each requested filesystem and network capability:

  1. For read access to configuration files (e.g. /opt/microservice/config.json), press A to Allow.
  2. For write access to logs (e.g. /var/log/microservice/*.log), select Allow with append (a) access.
  3. Once all prompts are resolved, press F to Finish and save the profile.

Transitioning Between Complain and Enforce Modes

Administrators can dynamically transition individual profiles between evaluation and active security enforcement without restarting services.

Step 1: Switching to Complain Mode for Testing

# Place Nginx into complain mode
sudo aa-complain /etc/apparmor.d/usr.sbin.nginx

# Verify status change
sudo aa-status | grep nginx

Step 2: Promoting to Strict Enforce Mode

Once audit logs reveal no unexpected denials during baseline testing, enforce security boundaries:

# Activate enforce mode
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx

# Reload the profile into the running kernel
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx

Troubleshooting AppArmor Violations and Denial Logs

Consult this diagnostic reference table to debug unexpected permission errors caused by AppArmor confinement:

Symptom / Error Root Cause Remediation Procedure
Service fails with Permission denied despite valid chmod 777 permissions. AppArmor is actively blocking access in enforce mode because the target path is omitted from the profile. Inspect audit logs using sudo grep "apparmor=\"DENIED\"" /var/log/audit/audit.log. Identify the denied path and append rule to /etc/apparmor.d/<profile>.
apparmor_parser: Unable to add "/etc/apparmor.d/...": syntax error Missing trailing semicolons, unescaped regex patterns, or mismatched brackets in the profile manifest. Lint the profile syntax using sudo apparmor_parser -d /etc/apparmor.d/<profile>. Ensure every permission rule ends with a semicolon (;).
Application cannot bind to low-numbered port (e.g. 80, 443). Profile is missing the Linux capability declaration capability net_bind_service. Add capability net_bind_service, inside the profile body and reload via sudo apparmor_parser -r.
Temporary files in /tmp fail to open after process restart. Profile allows static filenames but fails to accommodate random temporary file suffixes. Use AppArmor globbing patterns: /tmp/app_*.tmp rw, or owner /tmp/** rw,.
Profile changes do not take effect after saving the file. Kernel memory has not been reloaded with the updated manifest. Force an immediate kernel reload using sudo apparmor_parser -r /etc/apparmor.d/<profile> or restart the systemd service via sudo systemctl reload apparmor.

By defining explicit Mandatory Access Control boundaries with AppArmor, Ubuntu administrators protect infrastructure from privilege escalation and zero-day containment breaches, guaranteeing resilient system security.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.