How to Use audit2why and audit2allow to Troubleshoot SELinux Denials

Use audit2why to get a plain-English explanation of an SELinux denial and audit2allow to generate and load a custom policy module that permits the blocked action.

You restarted a service and it immediately crashed

You deploy a custom application, restart the systemd unit, and it fails within seconds. The journal shows a permission denied error, but the file permissions look correct. You check the audit log and see a wall of avc: denied messages. The service is locked out by SELinux. This happens to almost every Fedora user who moves beyond the default package set. The denial is not a bug. It is a precise security boundary doing its job. The raw audit log reads like machine code. You need to translate it before you can fix it.

What is actually happening

SELinux does not rely on traditional Unix user and group permissions. It uses mandatory access control based on security contexts. Every process runs with a source context. Every file carries a target context. The kernel checks every access attempt against a compiled policy database. When a process tries to read, write, or execute a file outside its allowed context, the kernel drops the operation and writes an AVC denial to the audit log. Think of it like a corporate building. Unix permissions decide who gets into the building. SELinux decides which rooms you can enter once you are inside.

The audit2why and audit2allow utilities bridge the gap between the kernel's binary decision and a human-readable explanation. They live in the policycoreutils-python-utils package and parse the audit log directly. SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those summaries first, but always verify against the raw AVC record. The summary tool occasionally misinterprets complex multi-domain transitions. The raw audit log never lies.

Check the audit log before you guess. Half the time the symptom is a mislabeled file, not a missing policy rule.

The fix

You need the translation tools before you start debugging. Install them with dnf.

Here is how to pull the required utilities from the default repositories.

sudo dnf install policycoreutils-python-utils audit
# policycoreutils-python-utils provides audit2why and audit2allow
# audit provides ausearch and the audit daemon configuration
# --setopt=install_weak_deps=False prevents pulling in optional GUI tools

Once installed, pull the recent denials. The ausearch command is faster than grepping the raw log because it understands the audit record format and filters by message type.

Here is how to extract recent AVC denials from the audit subsystem.

sudo ausearch -m avc -ts recent
# -m avc filters for Access Vector Cache denial records
# -ts recent limits output to events since the last boot
# use -ts today or -ts now-2h for broader time windows

Pipe that output into audit2why. The tool parses the source context, target context, and requested permission, then matches it against known policy patterns.

Here is how to translate the raw AVC record into a plain-English explanation.

sudo ausearch -m avc -ts recent | audit2why
# reads standard input from ausearch
# matches scontext and tcontext against policy databases
# prints the missing rule or suggests a boolean toggle

The output will tell you exactly what is missing. It usually points to a missing type enforcement rule, a mislabeled file, or a boolean toggle that controls the behavior. Always read the explanation before writing new policy. SELinux denials often mask a simple configuration mistake. If your application is trying to read a configuration file that lives in a directory labeled user_home_t, the denial is correct. The file belongs in /etc with an etc_t label. Move the file or relabel it before generating a custom rule. Config files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Edit /etc/. Never edit /usr/lib/.

Check SELinux booleans next. Booleans are pre-built policy switches maintained by the Fedora SELinux team. They cover common administrative needs like allowing a web server to connect to a database or permitting a desktop application to access network printers.

Here is how to search for and enable an existing policy boolean.

getsebool -a | grep httpd
# lists all booleans and filters for httpd-related toggles
# output shows current runtime state and persistent state
sudo setsebool -P httpd_can_network_connect on
# -P writes the change to the persistent configuration
# prevents the setting from reverting on the next reboot

If a boolean matches your use case, enable it. The -P flag writes the change to the persistent configuration so it survives a reboot. Booleans are the safest path because they ship with the official policy and receive security updates automatically.

When no boolean applies and the file labels are correct, you need a custom policy module. audit2allow reads the AVC denial and writes a minimal type enforcement rule that permits the exact access pattern.

Here is how to generate a loadable policy module from recent denials.

sudo ausearch -m avc -ts recent | audit2allow -M myapp_fix
# -M specifies the module name for the generated files
# creates myapp_fix.te (source) and myapp_fix.pp (compiled)
# reads only the exact contexts from the piped input

The command generates two files. The .te file contains the human-readable policy source. The .pp file is the compiled binary module. Open the .te file and verify the rule matches your intent. Do not blindly load a module that grants broad access. If the generated rule allows read and write on a directory you only intended to read, edit the .te file and recompile it with audit2allow -M myapp_fix -m myapp_fix.

Here is how to install the compiled module and confirm it is active.

sudo semodule -i myapp_fix.pp
# -i installs the module into the running policy store
# the kernel applies the rule immediately without a reboot
sudo semodule -l | grep myapp_fix
# lists all loaded modules and filters for your custom name
# confirms the module survived the installation step

Load the module into the running policy. The kernel applies it immediately. You do not need to restart the service or reboot the system.

File labeling is the most common root cause of repeated denials. Applications expect files in specific directories with specific contexts. When you copy files manually or deploy from a custom path, the default context follows the parent directory. Use restorecon to apply the correct label based on the compiled file context database.

Here is how to fix incorrect file labels and persist the change.

sudo restorecon -Rv /srv/myapp
# -R recurses into subdirectories
# -v prints every file that gets relabeled
# applies labels from the compiled fcontext database
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/myapp(/.*)?"
# -a adds a new persistent rule to the policy database
# -t sets the target security context for matching paths
# prevents future relabels from overwriting your custom path

The semanage fcontext command adds a persistent rule to the policy database. Without it, a full system relabel or package update will overwrite your manual changes. Always pair semanage with restorecon.

Verify it worked

Restart the failing service and watch the audit log. A successful fix produces zero new AVC denials for that process. Check the service status to confirm it stayed up.

Here is how to confirm the service is running and the denial is gone.

sudo systemctl restart myapp.service
# restarts the unit to trigger the previously blocked access
sudo journalctl -xeu myapp.service
# -x adds explanatory text to journal entries
# -e jumps to the end of the log
# -u filters for the specific unit name
sudo ausearch -m avc -ts recent | grep myapp
# confirms no new AVC records appeared after the restart

If the service crashes again, pull the new denial. The original rule might have been too narrow, or a secondary dependency is now blocked. SELinux enforces every access point. Fix them one by one.

Restart the service after every policy change. The kernel does not retroactively apply rules to already-running processes.

Common pitfalls and what the error looks like

The most frequent mistake is treating SELinux as a nuisance to bypass. Setting the mode to permissive stops the denials but leaves the system exposed to the exact vulnerabilities SELinux was designed to block. You will see SELinux is preventing myapp from read access on the file config.ini in the setroubleshoot logs. That message is a summary. The raw AVC record in /var/log/audit/audit.log contains the full context. Always work from the raw record.

Another trap is generating a module for a mislabeled file. If you run audit2allow on a denial caused by a file sitting in /tmp with a tmp_t label, the tool will happily write a rule that allows your application to read tmp_t files. That rule will work until someone else drops a malicious script in /tmp and your application executes it. Relabel the file to its proper context instead. The policy should describe where files belong, not where you accidentally dropped them.

Watch for the permissive=1 flag in audit output. That means the denial was logged but not enforced. It happens when a specific domain is running in permissive mode or when you temporarily set the global mode. Do not build production policy around permissive output. Switch to enforcing mode before generating modules.

A third pitfall is ignoring the tclass field. The denial might block read on a file object, but your application also needs open or getattr. audit2allow usually catches these, but manual edits to .te files often miss them. Let the tool generate the full rule set. Verify it, then load it.

Never disable SELinux to fix a broken deployment. Investigate the denial first. The audit log is a map, not a roadblock.

When to use this versus alternatives

Use booleans when the Fedora policy already includes a toggle for your service. Use restorecon and semanage fcontext when files are sitting in the wrong directory or carry an incorrect security label. Use audit2allow when you are running a custom application that requires access patterns not covered by the base policy. Use permissive mode only during initial development or debugging when you need to observe access patterns without blocking the application. Stay in enforcing mode for production. Trust the package manager. Manual file edits drift, snapshots stay.

Where to go next