How to Configure AppArmor Profiles, Enforce Modes, and Audit Logs in Ubuntu Server

In modern Linux server administration, traditional discretionary access control (DAC)—governed by standard file permissions, ownership, and POSIX capabilities—often proves insufficient against targeted application exploits. If a network daemon running under elevated privileges or a service user is compromised, DAC mechanisms struggle to prevent unauthorized traversals across file paths or socket interfaces. AppArmor (Application Armor) implements Mandatory Access Control (MAC) directly within the Linux kernel, binding granular security profiles to individual binary paths regardless of system user execution contexts.

Ubuntu Server ships with AppArmor enabled by default, protecting essential network daemons like BIND, MySQL, and Nginx. However, effectively hardening production infrastructure requires systems engineers to author bespoke confinement profiles, switch profiles between complain and enforce modes, interpret audit telemetry, and prevent application regressions. This guide provides an operational guide to configuring AppArmor on Ubuntu Server, generating profiles with aa-genprof, enforcing confinement, and auditing system denials via auditd.

Architectural Overview: AppArmor Profile States & Subsystems

AppArmor operates via path-based confinement using Linux Security Modules (LSM). Understanding profile execution states ensures that security enforcement does not inadvertently disrupt production availability.

Profile State Kernel Behaviour Audit Log Activity Operational Role
Enforce Mode Strictly blocks unauthorised operations; raises fatal EACCES errors to processes. Logs all policy violations as critical ALLOWED=no denials. Production deployment; guarantees that compromised daemons cannot exceed baseline rules.
Complain Mode Allows unauthorized operations to succeed despite policy violations. Logs policy deviations as warning ALLOWED=yes audit entries. Staging, testing, and profiling; used to train profiles against real-world traffic.
Disabled Profile is unloaded from the kernel LSM registry entirely. No security checks or logging executed. Debugging severe daemon conflicts or retiring deprecated services.
Kill Mode Immediately terminates the offending task via SIGKILL upon first policy violation. Logs fatal violation and task termination. High-security bastions and untrusted payload isolation environments.

Prerequisites and Environment Verification

Verify that your Ubuntu Server deployment has the AppArmor kernel subsystem and administrative utilities installed:

  • Operating System: Ubuntu Server 22.04 LTS or 24.04 LTS.
  • Administrative Privileges: Root or sudo access on the target server.
  • Essential Packages: The apparmor-utils and auditd packages must be installed for profiling and log aggregation.

Run the following command to verify operational status and install the profiling toolkit:

# Check AppArmor kernel module status
sudo aa-status

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

Step 1: Inspecting Current Kernel Confinement Status

The aa-status utility provides immediate visibility into loaded profiles and active processes under confinement:

sudo aa-status

The command output outlines:

  • Total loaded profiles: The count of compiled profiles in the kernel.
  • Profiles in enforce mode: Number of actively protected endpoints.
  • Profiles in complain mode: Number of monitored endpoints.
  • Processes running under confinement: Daemon PIDs bound to specific rules.

Active profile definitions reside in plain text within /etc/apparmor.d/. For example, the profile for the MySQL daemon is located at /etc/apparmor.d/usr.sbin.mysqld.

Step 2: Generating a Bespoke Profile with aa-genprof

To confine an unconfined custom daemon (such as a bespoke Python API service or Go web application), utilize the automated profiling engine aa-genprof:

  1. Ensure your target application binary exists (e.g., /opt/myapp/bin/myapp).
  2. Initiate profiling:
    sudo aa-genprof /opt/myapp/bin/myapp
    
  3. aa-genprof loads a baseline profile into complain mode and pauses, prompting you to exercise the application.
  4. In a separate terminal session, exercise all standard workflows of the application:
    • Start the service: sudo systemctl start myapp.
    • Send HTTP requests, trigger file uploads, generate logs, and connect to database endpoints.
  5. Return to the aa-genprof prompt and press S to scan the system audit log for accessed paths.
  6. Review each discovered resource access. aa-genprof presents interactive prompts:
    • Press A to Allow access.
    • Press D to Deny access.
    • Press G to Glob paths (e.g., converting /var/log/myapp/debug.log into /var/log/myapp/*.log).
  7. Press F to Finish and save the compiled profile to /etc/apparmor.d/opt.myapp.bin.myapp.

Step 3: Syntax Analysis and Manual Profile Hardening

Profiles created by automated tools should be manually inspected and hardened. Open the generated file:

sudo nano /etc/apparmor.d/opt.myapp.bin.myapp

A structured, production-hardened profile adheres to the following syntax layout:

#include <tunables/global>

profile myapp /opt/myapp/bin/myapp flags=(attach_disconnected) {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include <abstractions/ssl_certs>

  # Network socket allowances
  network inet stream,
  network inet6 stream,

  # File read and execute permissions for runtime dependencies
  /opt/myapp/bin/myapp mr,
  /opt/myapp/lib/** r,
  /opt/myapp/templates/** r,

  # Writable state directories with strict append/write bounds
  /var/log/myapp/*.log w,
  /var/run/myapp.pid rw,

  # Explicit denials (block access to sensitive system paths)
  deny /etc/shadow r,
  deny /etc/ssh/** rwklx,
  deny /root/** rwklx,
}

Permission Flag Reference

Flag Operation Allowed Security Implication
r Read access Allows inspecting file contents and directories.
w Write access Allows file creation, truncation, and deletion.
a Append access Restricts write operations exclusively to appending data (ideal for log files).
l Link access Allows hardlink creation.
k Lock access Permits POSIX and flock file locking.
m Memory map execute Required for binaries and shared libraries using PROT_EXEC memory mapping.
px / Px Profile Transition Exec Executes another binary under a separate AppArmor profile.
ix Inherit Exec Executes child process while maintaining the parent’s current confinement boundaries.

Step 4: Transitioning Profiles Between Complain and Enforce Modes

Once a profile is written or updated, compile and toggle its runtime operational state using dedicated management commands:

# Place profile into Complain Mode (monitoring only, no blocking)
sudo aa-complain /etc/apparmor.d/opt.myapp.bin.myapp

# Verify status confirms complain mode
sudo aa-status | grep myapp

# Recompile and reload profile without rebooting
sudo apparmor_parser -r /etc/apparmor.d/opt.myapp.bin.myapp

# Promote profile to Enforce Mode (strict blocking active)
sudo aa-enforce /etc/apparmor.d/opt.myapp.bin.myapp

# Disable a profile completely
sudo aa-disable /etc/apparmor.d/opt.myapp.bin.myapp

Step 5: Auditing Denials and Tuning with aa-logprof

When an application violates an active enforce profile, the kernel logs the event to /var/log/audit/audit.log (or /var/log/syslog if auditd is not running).

To inspect real-time AppArmor denials:

# Filter audit log for AppArmor security denials
sudo ausearch -m AVC -ts recent

# Alternatively, filter standard kernel messages
sudo dmesg | grep -i apparmor

A typical denial log entry appears as follows:

type=AVC msg=audit(1698064920.124:412): apparmor="DENIED" operation="open" 
profile="myapp" name="/etc/environment" pid=18492 comm="myapp" 
requested_mask="r" denied_mask="r" fsuid=1001 ouid=0

Instead of manually parsing denial records, run aa-logprof to automatically ingest audit logs and update profile definitions interactively:

# Ingest recent denials and update profiles interactively
sudo aa-logprof

aa-logprof identifies each denial, prompts you to determine whether the access pattern represents legitimate application evolution or a security intrusion, and patches the profile file in /etc/apparmor.d/ accordingly.

Troubleshooting Common AppArmor Deployment Failures

Issue 1: Service Fails to Start with “Permission Denied” Despite Correct POSIX Ownership

Root Cause: The application profile is in Enforce Mode and lacks an explicit path entry for a newly configured configuration directory or temporary cache path.

Remediation: Check sudo dmesg | grep -i denied to confirm the exact path blocked by the kernel. Place the profile into complain mode via sudo aa-complain <profile_path>, test service startup, update the profile rules, and re-enable enforce mode.

Issue 2: Profile Fails to Compile During apparmor_parser Reload

Root Cause: Syntax errors, missing terminating commas, or mismatched curly braces inside the profile file in /etc/apparmor.d/.

Remediation: Test profile syntax without applying it to the kernel:

# Syntax check without loading
sudo apparmor_parser -d /etc/apparmor.d/opt.myapp.bin.myapp

Examine the debug output for the exact line number where the parser encountered the invalid token.

Issue 3: High Audit Log Volume Exhausting Storage

Root Cause: A profile in complain mode is monitoring a high-throughput network daemon that generates millions of audit events per hour.

Remediation: Add explicit deny rules for harmless high-volume noise (such as frequent access to /proc/sys/kernel/random/uuid) or complete the profiling lifecycle promptly and transition the service to enforce mode.

Get the best tech tips delivered straight to your inbox.

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