Debian 13: AppArmor blocks your service — allow what you need without disabling security

Was this helpful?

Your service starts fine in staging. Then Debian 13 in production turns it into a brick: “Permission denied,” missing files that definitely exist, sockets that refuse to bind,
mysterious failures that vanish the instant you stop AppArmor. Congratulations, you’ve met a security control doing its job—just not yet doing your job.

This is the playbook I use when AppArmor cages a daemon: identify the exact denial, map it to the profile that enforced it, add the narrowest possible rule, and prove the fix.
Disabling AppArmor is not “temporary.” It’s how temporary becomes permanent.

What AppArmor is (and why Debian 13 makes it feel stricter)

AppArmor is a Linux Security Module that confines programs using profiles. A profile says what a program may read, write, execute, and which capabilities it can use.
If the program attempts something not allowed, the kernel denies it and logs the denial. That’s it. No magic, no “AI security,” just policy and enforcement.

If you’re coming from the “we run everything as root in a container” school of thought, AppArmor is a wake-up call. Containers are not permissions. Root in a container still
needs the host kernel to say “yes.” AppArmor is part of that “yes/no.”

On Debian 13 you’ll typically hit AppArmor when:

  • You deploy a custom service that touches files outside standard locations.
  • You add a new plugin, backup target, or TLS key path under /srv or /mnt.
  • You switch a daemon from TCP to Unix sockets (or the other way around).
  • You move logs to a new directory because “we’re being tidy.” (Famous last words.)
  • You tighten systemd hardening and accidentally change the process environment in a way the profile didn’t anticipate.

The mental model: AppArmor is not “blocking your service.” Your service is asking for something it was never granted. Your job is to grant the minimum necessary and verify
you didn’t widen the blast radius.

One quote worth keeping on your wall—because it’s operations in one sentence:
Hope is not a strategy. (attribution: paraphrased idea often associated with Gen. Gordon R. Sullivan; used widely in engineering and operations)

Fast diagnosis playbook

When a service fails and you suspect AppArmor, don’t “just try stuff.” Do these three checks in order. It’s faster, and it leaves evidence behind.

1) Confirm AppArmor is active and enforcing

If AppArmor is disabled globally, you’re chasing a different problem. If it’s enabled but the profile is in complain mode, you’ll get logs but no enforcement—also a different problem.

2) Identify the denial event and the profile name

The denial log line tells you the profile= and the target path, operation, and requested permissions. That log line is your change request. No denial line, no policy change.

3) Decide: fix the profile, change the service behavior, or change the file layout

If the service is trying to read a secret from a world-readable directory, that’s not “an AppArmor bug.” It’s a layout problem. If the service needs access, add a narrow rule.
If it shouldn’t need access, fix the service.

Quick rule of thumb: one denial usually means one missing rule. Ten denials in ten different paths usually means the binary executed isn’t what you think it is, or the profile is wrong.

Hands-on tasks: commands, outputs, decisions (12+)

Everything below is runnable on a Debian host with AppArmor installed. Replace service names and paths, but keep the method. The method is the point.

Task 1: Check if AppArmor is enabled in the kernel

cr0x@server:~$ cat /sys/module/apparmor/parameters/enabled
Y

What it means: Y means the AppArmor LSM is enabled in the kernel.
Decision: If it’s N or the file doesn’t exist, stop. You’re not being blocked by AppArmor on this boot.

Task 2: Verify AppArmor service health

cr0x@server:~$ systemctl status apparmor
● apparmor.service - Load AppArmor profiles
     Loaded: loaded (/lib/systemd/system/apparmor.service; enabled; preset: enabled)
     Active: active (exited) since Tue 2025-12-28 08:11:29 UTC; 2h 4min ago
       Docs: man:apparmor(7)
    Process: 512 ExecStart=/lib/apparmor/apparmor.systemd reload (code=exited, status=0/SUCCESS)

What it means: Profiles were loaded successfully.
Decision: If this is failed, fix that first; otherwise any profile edits won’t apply.

Task 3: List loaded profiles and their mode

cr0x@server:~$ sudo aa-status
apparmor module is loaded.
46 profiles are loaded.
44 profiles are in enforce mode.
2 profiles are in complain mode.
0 profiles are in kill mode.

What it means: You have real enforcement.
Decision: If the relevant profile is in complain mode, your service may still work while logging denials; decide if you want to switch to enforce after fixing rules.

Task 4: Confirm whether a specific service’s process is confined

cr0x@server:~$ systemctl show -p MainPID myservice.service
MainPID=1842
cr0x@server:~$ sudo cat /proc/1842/attr/current
/usr/bin/myservice (enforce)

What it means: The process is confined by the /usr/bin/myservice profile in enforce mode.
Decision: Edit the profile that matches what you see in attr/current, not what you think should be used.

Task 5: Pull recent AppArmor denials from journald

cr0x@server:~$ sudo journalctl -k --since "30 min ago" | grep -i apparmor | tail -n 5
Dec 28 10:01:22 server kernel: audit: type=1400 audit(1766916082.123:311): apparmor="DENIED" operation="open" class="file" profile="/usr/bin/myservice" name="/etc/myservice/secret.key" pid=1842 comm="myservice" requested_mask="r" denied_mask="r" fsuid=998 ouid=0

What it means: Your service tried to open a file for read and got denied.
Decision: You either allow read access to exactly that file (or directory pattern), or you move the secret into a path already allowed by policy. Don’t guess—use the denial.

Task 6: Use the audit log if journald is noisy or rotated

cr0x@server:~$ sudo grep -i "apparmor=\"DENIED\"" /var/log/audit/audit.log | tail -n 3
type=AVC msg=audit(1766916082.123:311): apparmor="DENIED" operation="open" class="file" profile="/usr/bin/myservice" name="/etc/myservice/secret.key" pid=1842 comm="myservice" requested_mask="r" denied_mask="r"

What it means: Same denial, from the audit subsystem.
Decision: If you don’t have auditd installed, consider it for servers where you need durable security logging; journald alone can be enough, but it’s not always retained.

Task 7: Locate the profile file on disk

cr0x@server:~$ sudo grep -R "profile /usr/bin/myservice" -n /etc/apparmor.d | head -n 2
/etc/apparmor.d/usr.bin.myservice:12:profile /usr/bin/myservice flags=(attach_disconnected) {

What it means: The profile lives in /etc/apparmor.d/usr.bin.myservice.
Decision: Edit this file (or a local include) rather than inventing a new profile name.

Task 8: Generate rules interactively with aa-logprof

cr0x@server:~$ sudo aa-logprof
Reading log entries from /var/log/audit/audit.log.
Updating AppArmor profiles in /etc/apparmor.d.
Profile:  /usr/bin/myservice
Execute:  /usr/bin/myservice
Severity: unknown

[(A)llow]/(D)eny/(I)gnore/(N)ew/(G)lob/(Q)uit

What it means: AppArmor parsed your denial and is offering a policy change.
Decision: Prefer Allow only when the access is legitimate and expected. If you’re unsure, choose Ignore, reproduce the issue with more logging, and revisit.

Task 9: Reload a single profile after editing

cr0x@server:~$ sudo apparmor_parser -r /etc/apparmor.d/usr.bin.myservice

What it means: No output is normal on success.
Decision: If you see parse errors, fix syntax before restarting the service. A broken profile can leave you with an old policy still loaded.

Task 10: Confirm the new rule is loaded (and not just edited)

cr0x@server:~$ sudo aa-status | grep -n "/usr/bin/myservice"
21:   /usr/bin/myservice

What it means: The profile is loaded. It doesn’t prove your new rule is in effect, but it proves the profile exists and is active.
Decision: Now restart the service and verify the denial stops.

Task 11: Restart the service and watch logs live

cr0x@server:~$ sudo systemctl restart myservice.service
cr0x@server:~$ sudo journalctl -u myservice.service -n 50 --no-pager
Dec 28 10:06:41 server myservice[2011]: Loaded TLS key from /etc/myservice/secret.key
Dec 28 10:06:41 server myservice[2011]: Listening on /run/myservice.sock
Dec 28 10:06:41 server systemd[1]: Started myservice.service - My Service.

What it means: The service now reads the key and binds its socket.
Decision: Keep the rule. If it still fails, go back to the kernel/audit denial line—your service log is not authoritative about AppArmor.

Task 12: Prove you didn’t introduce a flood of new denials

cr0x@server:~$ sudo journalctl -k --since "5 min ago" | grep -i "apparmor=\"DENIED\"" | tail -n 10

What it means: Ideally: no output.
Decision: If new denials appear, you fixed one path but the service has more needs. Iterate with the same discipline: one denial, one minimal rule.

Task 13: Temporarily switch a profile to complain mode (for diagnosis)

cr0x@server:~$ sudo aa-complain /usr/bin/myservice
Setting /usr/bin/myservice to complain mode.

What it means: The profile logs but doesn’t enforce. This can get a stuck incident moving while you gather evidence.
Decision: Use complain mode as a diagnostic step, not a lifestyle. Set a reminder to revert to enforce after updating the profile.

Task 14: Put it back into enforce mode

cr0x@server:~$ sudo aa-enforce /usr/bin/myservice
Setting /usr/bin/myservice to enforce mode.

What it means: Enforcement is back.
Decision: If the service breaks again, you missed an allow rule. That’s expected—go harvest the new denials and keep tightening.

Task 15: Find “which file is missing” problems that are actually confinement

cr0x@server:~$ sudo strace -f -e trace=file -p 1842 2>&1 | head -n 8
openat(AT_FDCWD, "/etc/myservice/secret.key", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/etc/ssl/certs/ca-certificates.crt", O_RDONLY|O_CLOEXEC) = 3

What it means: The kernel is denying file access. That aligns with AppArmor denials.
Decision: When devs insist “the file exists,” show them the syscall returning EACCES and the audit line. It ends the debate fast.

Task 16: Validate the unit’s execution path is what the profile expects

cr0x@server:~$ systemctl cat myservice.service
# /etc/systemd/system/myservice.service
[Service]
ExecStart=/usr/local/bin/myservice --config /etc/myservice/config.yaml

What it means: The binary is in /usr/local/bin, not /usr/bin.
Decision: If the loaded profile is for /usr/bin/myservice but the service runs /usr/local/bin/myservice, you’re editing the wrong profile. Fix the profile match or path.

How to read denials like an adult

An AppArmor denial line looks intimidating until you learn which fields matter. Here’s what to focus on:

  • profile= the policy that made the decision. This is the file you will edit.
  • operation= what the process attempted: open, create, unlink, connect, bind, ptrace, mount, etc.
  • name= the target path (for file operations) or object name.
  • requested_mask= what the process asked for, like r (read), w (write), k (lock), m (memory map), x (execute).
  • denied_mask= what was refused (usually matches requested_mask for a clean denial).
  • comm= command name; useful when wrappers or shell scripts are involved.
  • pid= lets you tie it back to systemd, strace, and the process tree.

The denial is not a suggestion. It is the exact evidence of what was blocked.
Your process can fail for many reasons; only change AppArmor when the kernel says AppArmor denied it.

Joke #1: If you “fix” AppArmor by turning it off, you didn’t fix AppArmor—you fixed your conscience by removing it.

Denial types you’ll see most in real services

File reads: configs, certs, credentials, plugin files. Usually easiest to fix with a narrow r rule.

File writes: logs, pidfiles, caches, databases. This is where people get sloppy. Don’t allow writes to broad directories “just to make it work.”

Unix sockets: binding in /run, connecting to Docker/containerd sockets, talking to system services. AppArmor can restrict both creation and connect operations.

Signals and ptrace: health checks, watchdogs, debuggers. These often show up during incident response when someone attaches tooling to a confined process.

Allow what you need: profile edits that don’t turn into “allow everything”

The right profile change is boring. It grants one capability, one path, one operation, and nothing else. The wrong profile change is “/** rw,” which is basically
buying a safe and leaving it open because the keypad is annoying.

Prefer narrow file rules over directory wildcards

If the service needs exactly one secret key:

cr0x@server:~$ sudo sed -n '1,80p' /etc/apparmor.d/usr.bin.myservice
#include <tunables/global>

profile /usr/bin/myservice flags=(attach_disconnected) {
  #include <abstractions/base>
  /usr/bin/myservice mr,

  /etc/myservice/secret.key r,
}

What it means: The profile allows read access to one file. That’s easy to reason about during audits and easy to revert.
Decision: Choose the smallest matching path. Only use directory globs when the service truly needs multiple rotating files.

Make writes intentional: logs, state, cache

A typical daemon needs:

  • Read-only config under /etc
  • Writable state under /var/lib
  • Writable logs under /var/log (or stdout to journald)
  • Runtime socket or pid under /run

Align the filesystem layout to those expectations. If you insist on writing state into /etc or /opt, you’ll fight AppArmor forever.
And you’ll deserve it.

Use abstractions, but don’t hide behind them

AppArmor abstractions (like abstractions/base) can be useful. They also make it easy to accidentally allow more than you need.
My rule: include abstractions when they are a clear match to the service’s role, then add explicit rules for the unique parts.

Don’t confuse “works” with “safe”

You can always make it work by adding wide rules. You’re here because you run production systems, and production systems have enemies: mistakes, attackers, and future you.
AppArmor helps with all three, but only if you don’t sabotage it.

Joke #2: “I temporarily set it to complain mode” is the security version of “I’ll just hold this live wire for a second.”

Systemd, services, and which profile actually applies

Most AppArmor incidents in Debian shops aren’t “AppArmor is too strict.” They’re “we edited the wrong profile” or “systemd runs a different binary than we think.”
Systemd adds wrappers: environment files, pre-start scripts, dynamic users, runtime directories.

Know the execution chain

AppArmor usually attaches by executable path. If your unit runs a shell wrapper that then execs the real binary, you may be confining the shell, not the daemon. Or nothing at all.
The proof is always in /proc/<pid>/attr/current.

Beware of /usr/local vs /usr/bin

Debian packaging tends to put managed binaries in /usr/bin. Hand-rolled deployments love /usr/local/bin.
If your profile is named for one but you execute the other, you’ll chase ghosts.

Systemd hardening can change the symptom

Options like ProtectSystem=, ReadOnlyPaths=, and PrivateTmp= can also cause “permission denied,” but those are not AppArmor denials.
This is why the fast diagnosis starts with the kernel denial line. You need the right culprit before you swing a wrench.

Three corporate mini-stories from the trenches

Incident #1: The wrong assumption (“It’s just a filesystem permission problem”)

A mid-sized company rolled out a Debian upgrade across a fleet running an internal API gateway. One region started returning sporadic 503s.
The on-call did what most of us do under pressure: looked for the obvious.

The gateway logs said it couldn’t read its TLS key. The file existed, ownership looked right, and a quick sudo -u gateway cat /path/to/key worked.
Someone concluded it was a race condition in deployment and re-ran the config management job.
The problem persisted. Naturally, they tried adding more retries.

Eventually a calmer engineer checked the kernel log and found AppArmor denials for operation="open" on that key path.
The key had been moved from the old location into a new directory that matched their tidy naming conventions—but not the AppArmor profile.

The “aha” moment was uncomfortable: all the Unix permissions were correct. AppArmor was the layer denying access, and it didn’t care that manual cat worked
because the manual test was performed outside the confined context.

The fix was a single rule allowing read access to the new key path. The lasting fix was cultural: never accept “permission denied” as a filesystem-only diagnosis
on a system with mandatory access control enabled. Check the denial line first.

Incident #2: The optimization that backfired (log path consolidation)

Another team wanted to “optimize” log shipping. Instead of journald, they redirected multiple services to write JSON logs under a shared directory on a fast disk,
then tailed them with an agent. It looked neat: one place for all the logs, consistent rotation, happy dashboards.

On Debian 13, several services started failing during startup with generic IO errors. Engineers chased disk performance, then SELinux (they didn’t run SELinux),
then systemd sandboxing. The failures were inconsistent because only certain code paths wrote logs early in startup.

The root cause was AppArmor profiles that allowed writing to each service’s standard log path, but not to the new shared directory.
The “optimization” had silently changed the write target for multiple daemons. AppArmor did exactly what it was supposed to do: stop a confined service from writing anywhere it wants.

The naive fix was to add broad write access to the shared directory for every service. That would have made log consolidation work—and also created a cross-service data tampering surface.
One compromised service could overwrite another’s logs, which is a delightful gift to an attacker who enjoys covering tracks.

The correct fix was to avoid shared writable directories across unrelated daemons, or to use per-service subdirectories with narrow allow rules and ownership controls.
The team ended up using journald for most services and only offloading what needed local JSON for specific tooling.
Less neat on disk. Much neater in risk posture.

Incident #3: The boring practice that saved the day (pre-change deny harvesting)

A fintech team ran a payment processor with a strict change process. Their approach was painfully unglamorous: before turning on AppArmor enforcement for a service,
they ran it in complain mode in production for a day, harvested denials, reviewed them, and then flipped to enforce.

During a routine dependency update, the service started accessing a new CA bundle path due to changes in the TLS stack.
In complain mode, the denials were logged immediately. Nothing broke yet, and there was no customer impact.

They added a narrow read rule for that CA bundle location, reloaded the profile, and re-ran smoke tests.
Only then did they enable enforce mode. When the change hit the full fleet, it was a non-event.

The practice wasn’t clever. It didn’t require heroics. It did require discipline: treat policy as a production artifact, test it like code, and roll it out like code.
The result was predictability, which is what you actually want at 3 a.m.

Common mistakes: symptom → root cause → fix

1) Service says “file not found,” but the file exists

Symptom: The app logs ENOENT or “missing file,” but you can see the file on disk.

Root cause: AppArmor denies the open, and the application maps the error poorly (or you’re looking at the wrong path due to symlinks/chroot).

Fix: Confirm with journalctl -k or /var/log/audit/audit.log; allow the exact path or fix the service to read from an allowed location.

2) You edited a profile and nothing changed

Symptom: You add rules, restart the service, still denied.

Root cause: The edited file isn’t the loaded profile, or the profile wasn’t reloaded, or the process is running a different executable path.

Fix: Check /proc/<pid>/attr/current. Reload with apparmor_parser -r. Validate the systemd ExecStart binary path.

3) “Fix” was to disable AppArmor and the incident ended (for now)

Symptom: Turning off AppArmor makes everything work.

Root cause: Policy is missing legitimate accesses. Disabling enforcement removes guardrails entirely.

Fix: Use complain mode briefly if needed, collect denials, update the profile, and return to enforce.

4) Service can’t bind its Unix socket under /run

Symptom: Startup fails when creating or binding /run/myservice.sock.

Root cause: Profile doesn’t allow creating that socket path, or systemd runtime directory differs from expected.

Fix: Add rules for /run/myservice.sock with correct permissions; ensure RuntimeDirectory= matches the path you allow.

5) After adding a wildcard, the service works but security review blocks release

Symptom: A reviewer rejects the profile due to broad rules like /etc/** r or /** rw.

Root cause: Overbroad policy written under pressure.

Fix: Replace with explicit file rules, per-directory rules for owned paths, and minimal capabilities. Reproduce denials to find the specific needs.

6) DNS, outbound HTTP, or DB connections fail but no file denials appear

Symptom: Network calls time out or get denied; app logs are vague.

Root cause: AppArmor can restrict network operations depending on profile and features; or the issue is systemd sandboxing/firewall.

Fix: Search denials for operation="connect" or network. If no AppArmor denials, inspect systemd unit hardening and firewall rules.

7) You see denials for a process name you don’t recognize

Symptom: Denials reference comm="(something)" not your service.

Root cause: Your service execs helper binaries (curl, openssl, shell scripts), and the profile restricts execution.

Fix: Decide whether the service should exec helpers at all. If yes, allow specific binaries with correct execute modes; if no, remove that behavior from the service.

Checklists / step-by-step plan

Checklist A: When production is down and you need signal fast

  1. Check confinement: /proc/<pid>/attr/current. If it says unconfined, stop blaming AppArmor.
  2. Pull kernel denials: journalctl -k filtered for apparmor="DENIED".
  3. Correlate timestamp with service failure in journalctl -u.
  4. Decide the smallest legitimate allow rule. If you can’t justify it, don’t add it.
  5. Reload the profile, restart service, confirm denials stop.
  6. Set a timer to revert any complain mode used during the incident.

Checklist B: Safe policy development loop (the one that keeps you employed)

  1. Put the service profile in complain mode on a canary host.
  2. Exercise the service: startup, steady state, backups, rotation, upgrades, failover.
  3. Run aa-logprof and review each rule like it’s a firewall change.
  4. Prefer explicit file paths; avoid shared writable directories.
  5. Reload the profile, switch to enforce on the canary, rerun tests.
  6. Roll out to a small slice, monitor denials, then expand.
  7. Store profile changes in your normal change system (Git, CI, reviews). Treat them as code.

Checklist C: A sane layout that reduces AppArmor friction

  • Configs: /etc/myservice/ (read-only at runtime except deliberate cases)
  • Secrets: /etc/myservice/ or /var/lib/myservice/ (read-only, tight ownership)
  • State: /var/lib/myservice/ (writable)
  • Cache: /var/cache/myservice/ (writable, disposable)
  • Logs: journald preferred; otherwise /var/log/myservice/ per service
  • Sockets/pids: /run/myservice/ created by systemd RuntimeDirectory=

Interesting facts & historical context (short and concrete)

  • AppArmor started life as a commercial product from Immunix; it later entered the mainline Linux ecosystem via Novell’s involvement.
  • Unlike SELinux’s label-based model, AppArmor is path-based: policies reference filesystem paths, which makes them readable and also vulnerable to path quirks if you’re sloppy.
  • AppArmor profiles support “complain mode,” which logs violations without enforcement—useful for building policies from observed behavior.
  • The Linux Security Modules (LSM) framework is what allows AppArmor, SELinux, and others to hook into the kernel’s permission checks.
  • AppArmor policies can be “hat”-based (subprofiles) for temporarily changing confinement in parts of a program, though this is less common in modern service deployments.
  • Debian’s adoption patterns historically differed from Ubuntu’s; Ubuntu made AppArmor a default pillar early, which influenced tooling and community practice.
  • Systemd’s rise changed the troubleshooting workflow: journald became a primary place to see denials, not just classic syslog files.
  • AppArmor can restrict more than files: signals, dbus interactions, and certain kernel features show up as denials in real incidents.

FAQ

1) How do I know it’s AppArmor and not Unix permissions?

Look for a kernel/audit denial line with apparmor="DENIED". If it exists at the same time as the failure, it’s AppArmor.
If it doesn’t exist, you’re probably dealing with filesystem permissions, systemd sandboxing, or application bugs.

2) Should I use complain mode in production?

Yes, briefly and deliberately: complain mode is good for gathering denials without breaking traffic.
But treat it like a change window tool. Once rules are correct, go back to enforce.

3) Why does sudo -u myuser cat /path work, but the service can’t read the file?

Because the service is confined and your manual test is usually not. AppArmor decisions depend on the confined process context, not just UID/GID.
Verify with /proc/<pid>/attr/current.

4) What’s the safest way to add access to secrets?

Allow read access to exactly the secret file (not the entire directory), and keep ownership and mode tight.
If you need rotation, allow the minimal glob that matches the rotation scheme (for example, a specific basename pattern).

5) Can I just allow /etc/** r to stop these problems?

You can, and it will “work,” and it will also quietly allow reading unrelated configs and secrets.
If a service is compromised, broad read access makes data theft easier. Be precise instead.

6) My service runs from /usr/local/bin. Why doesn’t the packaged profile apply?

Most profiles attach to an executable path. If the profile is for /usr/bin/myservice but you execute /usr/local/bin/myservice,
you’re not using the profile you think you are. Either adjust the unit to the expected path or create/modify the correct profile for the actual binary.

7) I fixed one denial, and now I see another. Is that normal?

Yes. Services often fail fast on the first blocked operation. Once you unblock that, the next missing permission shows up.
Iterate until startup and steady-state both run clean in enforce mode.

8) How do I avoid repeating this every upgrade?

Treat profiles as part of the service, not as an afterthought. Store changes in version control, test them in CI where possible,
and run canaries in complain mode after upgrades to harvest new behavior before enforcing fleet-wide.

9) Is AppArmor “weaker” than SELinux?

Different model, different trade-offs. AppArmor’s path-based approach is often more approachable and faster to adopt for service-level confinement.
“Weaker” usually means “less uniformly deployed and less strictly maintained,” not inherently incapable.

Conclusion: next steps you can actually do today

If Debian 13 is blocking your service, don’t disable AppArmor. Use it as a debugging tool and a safety belt.
Find the denial. Identify the enforcing profile. Add the minimum rule that matches the legitimate behavior. Reload. Prove it with logs.

Practical next steps:

  1. Pick one failing service and capture its denial lines from journalctl -k or /var/log/audit/audit.log.
  2. Verify confinement via /proc/<pid>/attr/current, then locate the matching file under /etc/apparmor.d/.
  3. Run aa-logprof, accept only rules you can explain, reload the profile, and retest.
  4. Once stable, ensure the profile is in enforce mode and monitor for new denials after changes.

The goal isn’t to make AppArmor shut up. The goal is to make your service behave predictably under confinement—because predictability is what keeps incidents small.

← Previous
DNS SERVFAIL from Upstream: How to Prove Your Provider Is the Problem
Next →
ZFS Snapshots: The Superpower That Can Also Fill Your Pool

Leave a comment