Measuring and reducing SELinux overhead in production: tuning the AVC cache, filtering log noise with dontaudit, and keeping the policy uniform and verified across many hosts through a policy-as-code approach.

After writing policies in CIL in episode 17 — the intermediate language that exposes how SELinux assembles rules — and examining the runtime via selinuxfs in episode 16, now it's time for the question every production engineer always asks: "How much overhead does SELinux add to my system, and how do I keep the policy healthy when the host count starts reaching dozens?"
SELinux works on every access: every time a process opens a file, connects a socket, or sends a signal, the kernel checks the policy. If every check had to traverse the whole rule tree from scratch, the overhead would be unreasonable. That's where the AVC (Access Vector Cache) comes in — the kernel stores check results in a cache so subsequent checks for the same combination become very cheap. You can even read this cache statistics directly from selinuxfs, which you got to know in episode 16.
This episode 18 focuses on two things: making SELinux as lightweight as possible (cache tuning, measuring overhead, trimming log noise) and making the SELinux policy something that can scale uniformly to many hosts. Let's start with the most misunderstood component: the cache.
The most honest analogy for the AVC is a librarian who already knows where the books are. The first visit to the shelf takes time — the librarian has to look. The next visit for the same book only takes a second because the result is already in their head. Same with SELinux: the first access to a combination of source context, target context, class, and permission is an expensive cache miss; the following accesses are almost invisible cache hits.
That's why SELinux overhead in production is generally small: production applications access the same objects repeatedly — config files, sockets, directories. After a few minutes of "warm-up", almost all checks become cache hits. The biggest overhead actually occurs on cold cache and on systems with a very large number of domains.
The first tool you should know is avcstat. It reads data directly from selinuxfs:
avcstat lookups hits misses allocs reclaims frees
1584002 1571892 12110 12110 0 240Let's read it together: out of 1,584,002 checks, 1,571,892 were cache hits — a ratio above 99%. Only 12,110 were misses. As long as this ratio stays that high, the cache is working well and nothing needs to be touched. Also notice how the empty reclaims column indicates the cache was never force-pruned because there was always space.
The SELinux cache has a size limit managed via avc_cache_threshold:
cat /proc/sys/fs/selinux/avc_cache_thresholdecho 1024 > /proc/sys/fs/selinux/avc_cache_thresholdThis value is the entry-count threshold that triggers cache pruning. The default is generally adequate. Increasing it can reduce misses on systems with very many domains, but at the cost of memory. Like all tuning: measure first, change only with evidence.
The simplest way to measure SELinux overhead: run a representative workload in enforcing mode, then compare it with permissive mode. This measurement should only be done in a non-critical environment:
time ./workload.shsetenforce 0
time ./workload.sh
setenforce 1Warning
Don't leave a production server in permissive mode longer than necessary — and never make permissive a permanent solution. The enforcing vs permissive comparison is only a diagnostic tool. If the measured overhead turns out to be large, investigate the cause (repeated denials indicating a wrong label, a wasteful audit loop, or a cache that's always cold because the application restarts too often) before thinking about disabling SELinux.
The measurement results are almost always reasonable: for realistic I/O workloads, the overhead is usually below a few percent. That's a small insurance cost compared to the price of a security compromise. If you see a much larger number, don't blame SELinux right away — check the labels, check the audit, then conclude.
Many denials are "known and intentional" — for example a process trying to read a file it genuinely shouldn't access, and the application keeps running fine after the denial. If all of them went into the audit log, log I/O would balloon and the log would become unreadable. That's what the dontaudit rule is for: it tells the kernel not to record specific denials.
In normal operations, leave dontaudit active. When you're hunting a "missing" denial — the application doesn't work but the log is empty problem — turn off dontaudit first, inspect, then turn it back on:
semodule -DBsemodule -BTip
Notice the flag combination: -D disables all dontaudit rules, -B rebuilds the policy. After semodule -DB, the audit log will flood — that's intentional. Use the troubleshooting pattern from episode 15: search for denials with ausearch -m avc, create rules only for what's truly needed, and let dontaudit cover the rest in production. That way the audit log stays signal, not just noise.
Now the part most often forgotten when SELinux starts being used in large infrastructure: keeping the policy uniform. If every admin "patches" the policy on their own machine directly with semanage, drift will appear within months: host A has different booleans than host B, and the same incident behaves differently on two machines.
The correct approach is policy-as-code: one source of truth in git, built, tested, then distributed to the whole fleet with automation tools — you'll find the details in episode 20. Baseline enforcement means every host must be verifiable to be in the same condition. That verification starts with simple things:
sestatussemodule -lmd5sum /etc/selinux/targeted/policy/policy.33By comparing the semodule -l output and the policy file hash on each host against baseline values, drift becomes immediately visible. Such comparison is easy to automate — for example with one Ansible playbook ensuring the module list is identical — and becomes one of the health checklists you'll encounter again in episode 22.
One mindset we want to instill in this episode: the SELinux cache and overhead are rarely a real bottleneck. Every time your instinct says "SELinux makes it slow", hold on. Measure with avcstat, measure the workload execution time, then decide. Nine times out of ten, the culprit isn't SELinux — but a wasteful audit loop, an application restarted too often so the cache is always cold, or unoptimized denials.
In this episode 18, you've understood that SELinux overhead in production is generally small thanks to the AVC cache, and that performance can be monitored (avcstat) and measured directly. You also learned to trim log noise with dontaudit, and — just as importantly — that scale is a management problem, not just configuration: one uniform, versioned, automatically distributed policy is the key for a fleet with dozens or even hundreds of hosts.
The key takeaways:
avcstat.setenforce 0 only for a short diagnosis) before tuning anything.semodule -DB for denial debugging, semodule -B to return to normal.After making sure SELinux doesn't weigh the system down, it's time to use it as an active isolation tool. In episode 19, we enter Sandbox & seunshare — how to run programs you don't trust inside an SELinux-based sandbox, complete with security notes on CVE-2026-59676 and CVE-2026-59677. See you there!