How to Read and Understand SELinux Audit Logs on Fedora

Use ausearch and audit2why to find SELinux denials and generate a policy to fix them.

When an application fails without a clear error

You restart a web server after a routine update and the service immediately exits. The systemd status shows a clean failure with no obvious error. You check the application logs and find nothing. You suspect SELinux because it worked yesterday. You run getenforce and see Enforcing. Now you need to find exactly what was blocked, why it was blocked, and how to fix it without disabling security.

How the audit subsystem actually works

SELinux does not log failures in the application's own log files. It intercepts system calls at the kernel level and drops a record into the audit subsystem. Every time a process tries to access a file, socket, or port with a context that violates the loaded policy, the kernel writes an AVC denial. The audit daemon collects these records and stores them in /var/log/audit/audit.log.

Think of the audit log as a security guard's clipboard. The guard does not explain why they stopped you. They just write down the timestamp, the badge number, the door you tried to open, and the action you attempted. Your job is to read that entry, match it to the building's rules, and decide whether the rule is correct or whether you need a new key.

A raw AVC record contains six critical fields. The scontext shows the source process label. The tcontext shows the target file or port label. The tclass shows the object type. The perm field shows the exact capability that was denied. Fedora ships with the setroubleshoot daemon running by default. It watches the audit stream in real time and translates raw AVC denials into plain English. You will usually see these translations in journalctl -t setroubleshoot. The one-line summary tells you which process was blocked, what it tried to do, and often suggests a boolean or a file context fix. Read that summary before you reach for a policy generator.

Check the journal first. Read the actual error before guessing.

Finding and decoding the denial

Start by isolating the most recent denial. The ausearch command queries the audit log directly. It is faster and more precise than grepping raw text files. The audit log grows quickly on a busy system. Scanning it with grep wastes time and often returns false positives from unrelated services.

Here is how to pull the latest AVC denial and pipe it into the explanation tool.

sudo ausearch -m avc -ts recent | sudo audit2why
# -m avc filters for Access Vector Cache denial records only
# -ts recent limits the search to the last 24 hours to avoid scanning years of logs
# audit2why parses the raw audit fields and maps them to policy rules

The output will show the source process, the target file or port, the denied permission, and the expected context. If the output says the process needs httpd_sys_content_t but the file is labeled user_home_t, the fix is a label correction, not a policy change.

When you need to see the raw record for debugging, ausearch can print the unfiltered line. This is useful when you are matching a denial to a specific cron job or systemd unit.

Here is how to display the raw audit record with interpreted user IDs.

sudo ausearch -m avc -ts recent --interpret
# --interpret translates numeric user and role IDs to readable names
# the output shows the exact scontext, tcontext, and permission gap
# copy this line if you need to file a bug report with the package maintainer

A typical raw denial looks like this:

type=AVC msg=audit(1698765432.123:456): avc:  denied  { read } for  pid=1234 comm="python3" name="data.csv" dev="sda1" ino=789012 scontext=system_u:system_r:container_t:s0:c123,c456 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

The comm field shows the executable. The name field shows the target. The scontext and tcontext show the labels. The perm field shows read. The permissive=0 confirms the action was actually blocked. Match these fields to your application's expected behavior.

Run ausearch with a time range. Narrow the window before you analyze.

Applying the correct fix

When the denial involves a known toggle, use a boolean. Booleans are pre-defined switches in the SELinux policy that safely enable or disable specific behaviors without writing custom rules. They are maintained by Fedora and Red Hat. They survive package updates and do not require recompilation.

Here is how to check available booleans and enable one permanently.

sudo getsebool -a | grep container_use_devices
# list all booleans and filter for the container device access switch
sudo setsebool -P container_use_devices on
# -P writes the change to disk so it survives a reboot
# on is the standard value. true also works but on is preferred

If no boolean exists and the application legitimately needs access to a non-standard path, generate a targeted policy module. Never write a global policy that opens everything. Scope it to the exact process and file. The audit2allow tool reads denials and writes a .te source file. You compile it into a .pp package and install it.

Here is how to generate a minimal policy module from recent denials and install it.

sudo ausearch -m avc -ts recent | sudo audit2allow -M custom_app_fix
# -M names the module. audit2allow will create custom_app_fix.te and custom_app_fix.pp
sudo semodule -i custom_app_fix.pp
# -i installs the compiled policy package into the running SELinux store

The module loads immediately. You do not need to reboot. The new rules merge with the base policy and take effect on the next access attempt. If you need to adjust file labels permanently, use semanage fcontext instead of chcon. The chcon command changes labels in place but loses the change on the next filesystem relabel or package update. semanage writes the rule to the local file context database.

Here is how to set a persistent file context and apply it.

sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/custom(/.*)?"
# -a adds a new rule. -t sets the target type. The regex matches the directory and contents
sudo restorecon -Rv /var/www/custom
# -R recurses into subdirectories. -v prints every file that gets relabeled

Trust the package manager. Manual file edits drift, snapshots stay.

Verifying the resolution

Restart the failing service and watch the audit stream. A successful fix produces zero new AVC denials for that specific path or port. SELinux often blocks multiple actions in sequence. Fix them one at a time until the application runs cleanly.

Here is how to monitor the audit log in real time while you test the application.

sudo ausearch -m avc -ts recent --interpret
# run this in a separate terminal while you trigger the application
# press Ctrl+C to stop the stream once the service starts successfully

If the command returns no output after you restart the service, the denial is resolved. Cross-reference the service status with journalctl -xeu <unit>. The x flag adds explanatory text and the e flag jumps to the end. Most sysadmins type journalctl -xeu <unit> muscle-memory style. It shows recent log lines and state in one view. Always check status before restart.

Reboot before you debug. Half the time the symptom is gone.

Common pitfalls and what the error looks like

The most common mistake is disabling SELinux entirely when a single denial appears. Switching to permissive mode hides the symptom but leaves the system exposed to privilege escalation and container breakout. The audit log will still record the denials, but the kernel will allow the action. You lose the security guarantee without solving the root cause.

Another frequent error is editing files in /usr/lib/systemd/system/ or /usr/lib/selinux/. Those directories ship with packages. Your edits vanish on the next dnf upgrade. Always work in /etc/ or use the policy module system. The package manager controls /usr/lib/. You control /etc/.

You will sometimes see this exact string in the journal when setroubleshoot cannot find a matching solution:

SELinux is preventing /usr/bin/python3 from 'read' accesses on the file /home/user/data.csv.

The message includes a link to a local HTML report. Open that link in a browser. It contains the full context, the suggested boolean, and the exact restorecon or semanage fcontext command needed. Do not ignore the link. It is generated specifically for your system state.

Overly broad policy modules are the third pitfall. Running audit2allow -a without filtering pulls every denial from the last 24 hours, including ones from failed login attempts or cron jobs that already resolved. You end up with a policy that grants httpd_t access to /tmp and /var/log when it only needed one specific configuration directory. Always pipe a filtered ausearch into audit2allow. Scope the fix to the exact process and target.

Label drift after mv or cp is the fourth pitfall. Moving a file preserves its original SELinux context. The destination directory expects a different label. The application fails until you run restorecon. Run ls -Z to inspect labels before you assume the policy is broken.

Run journalctl first. Read the actual error before guessing.

When to use this vs alternatives

Use ausearch and audit2why when you need to diagnose a specific service failure and understand the exact permission gap. Use setsebool when the policy already includes a safe toggle for the behavior you need. Use semanage fcontext and restorecon when a file or directory has the wrong label after a move or copy operation. Use audit2allow only when no boolean or context fix exists and the application legitimately requires the access. Stay in enforcing mode during development. Switch to permissive only when you are debugging a complex deployment and cannot afford downtime.

Where to go next