How to Configure AppArmor Profiles and Enforce Security Policies on Ubuntu Server

In modern Linux server hardening, standard Discretionary Access Control (DAC)—relying solely on traditional file owner read, write, and execute permissions (rwxrwxrwx)—is insufficient to contain sophisticated exploits. If a daemonized web server or database running with root or elevated privileges is compromised through a remote buffer overflow, the attacker inherits full administrative authority across the entire filesystem. Ubuntu addresses this vulnerability through AppArmor, a kernel-level Mandatory Access Control (MAC) security module. By confining applications via strict path-based security profiles, AppArmor limits the capabilities of compromised binaries, preventing unauthorized directory traversals, raw socket creation, and arbitrary binary execution.

Understanding the AppArmor Confinement Architecture

AppArmor operates as a Linux Security Module (LSM) that enforces path-based access control. Unlike SELinux, which relies on complex cryptographic inode labels, AppArmor binds security policies directly to human-readable filesystem paths. The table below delineates the primary operational modes of AppArmor profiles:

Operational Confinement Mode Kernel Enforcement Action System Audit Logging Behaviour Primary Administrative Context
Enforce Mode Strictly blocks unauthorized system calls and filesystem access. Logs security violations to audit log (/var/log/audit/audit.log). Production environments; actively neutralises zero-day payloads.
Complain Mode Permits unauthorized actions without blocking execution. Logs all policy violations for administrative profiling. Staging and development; used to generate and tune baseline profiles without breaking apps.
Unconfined State Zero kernel policy enforcement. No security event logging. Standard binary execution outside AppArmor sandbox scope.

Auditing Active Profiles with aa-status

Before modifying security policies, verify the current confinement state across all server processes.

sudo aa-status

The console output summarizes:

  • Total number of profiles loaded in kernel memory.
  • Profiles operating in enforce mode (e.g., /usr/sbin/tcpdump, /usr/bin/man).
  • Profiles operating in complain mode.
  • Active running network processes and daemons currently confined by an enforcement policy.

Managing Profile Modes: Enforce vs. Complain

Ubuntu provides dedicated CLI tools inside the apparmor-utils package for shifting profile modes on the fly.

Step 1: Install Profile Utilities

sudo apt update && sudo apt install -y apparmor-utils apparmor-profiles

Step 2: Switching a Profile to Complain Mode

When deploying a new software update or reconfiguring an existing service (such as Nginx or BIND9), switch its profile to complain mode to test for compatibility issues without causing service outages:

sudo aa-complain /usr/sbin/nginx

Step 3: Returning a Profile to Enforce Mode

Once configuration updates are validated and log violations are accounted for, re-engage hard enforcement:

sudo aa-enforce /usr/sbin/nginx

To reload all active profiles directly from disk into the kernel without rebooting:

sudo systemctl reload apparmor

Authoring a Custom AppArmor Profile

All AppArmor profiles reside in /etc/apparmor.d/, named after the target executable’s full path with slashes converted into periods (e.g., the profile for /opt/customapp/bin/worker is stored as /etc/apparmor.d/opt.customapp.bin.worker).

1. Defining the Profile Anatomy

Create a custom profile for an internal Python microservice using a text editor:

sudo nano /etc/apparmor.d/opt.microservice.api

Populate the policy with granular capability declarations and path permissions:

#include <tunables/global>

profile custom_api /opt/microservice/api.py {
  #include <abstractions/base>
  #include <abstractions/python>

  # Network access rules
  network inet tcp,
  network inet6 tcp,

  # Deny raw socket creation to prevent sniffing
  deny network raw,

  # Read-only access to application scripts
  /opt/microservice/** r,

  # Read/Write access strictly to isolated temporary storage
  /opt/microservice/logs/*.log rw,
  /opt/microservice/tmp/ rw,
  /opt/microservice/tmp/** rwk,

  # Allow reading system TLS certificates
  /etc/ssl/certs/** r,
  /etc/ssl/certs/ca-certificates.crt r,

  # Explicitly forbid execution of system shells
  deny /bin/sh x,
  deny /bin/bash x,
  deny /bin/dash x,
}

Permission flags:

  • r: Read permission.
  • w: Write and append permission.
  • k: File locking permission.
  • x: Execution permission.
  • deny ...: Hard prohibition rule that supersedes conflicting allow directives.

2. Parsing and Loading the New Profile

Validate the syntax and load the profile into the running kernel:

# Parse and compile into the kernel
sudo apparmor_parser -r /etc/apparmor.d/opt.microservice.api

# Verify the profile is actively enforced
sudo aa-status | grep "custom_api"

Interactive Policy Generation with aa-genprof and aa-logprof

Rather than authoring complex path policies manually, administrators can utilize interactive profiling tools.

1. Generating a Baseline with aa-genprof

  1. Launch the profiling generator targeting the target application:
    sudo aa-genprof /usr/bin/node
  2. Leave the terminal open and switch to a secondary terminal.
  3. Exercise all core workflows of the Node.js application (serve HTTP traffic, process uploads, write logs). AppArmor logs every attempted system call in complain mode.
  4. Return to the generator terminal and press S to scan audit logs.
  5. The tool presents interactive prompts for each accessed resource: press A to allow, D to deny, or I to inherit abstraction rules.
  6. Press F to finish. The generated profile is automatically compiled and enforced.

2. Updating Existing Profiles with aa-logprof

Whenever an existing confined service encounters a legitimate access denial following an application update, run sudo aa-logprof to review audit rejections and append missing allowances into the profile cleanly.

Auditing Violations and Troubleshooting Disrupted Services

When an application behaves erratically or crashes with Permission denied despite valid Linux file ownership, inspect AppArmor audit trails:

  • Querying the Audit Log: Search for kernel rejection records:
    sudo grep -i "apparmor=\"DENIED\"" /var/log/audit/audit.log
    (Or inspect /var/log/syslog on systems without auditd).
  • Deciphering Denial Records: Inspect the operation=, profile=, name=, and requested_mask= fields. If requested_mask="w" and name="/var/run/app.pid", the profile lacks write access to its own PID file directory.
  • Temporarily Disabling a Problematic Profile: If a production service fails critically due to an AppArmor conflict:
    sudo ln -s /etc/apparmor.d/usr.sbin.nginx /etc/apparmor.d/disable/
    sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.nginx
    This immediately removes the profile from kernel memory without uninstalling the package.

Get the best tech tips delivered straight to your inbox.

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