You see an AVC denial and the service won't start
You restart a web server and it crashes immediately. The logs show Permission denied. You check the firewall and the ports are open. You check the file permissions and ls -l looks correct. Then you find the real cause in the journal: avc: denied { name_connect } for pid=1234 comm="nginx". SELinux blocked the access. The standard Unix permissions are fine, but the security policy forbids the action. Disabling SELinux is the wrong move. You need to read the denial and fix the policy.
What's actually happening
SELinux adds a layer of Mandatory Access Control on top of standard Unix permissions. Unix permissions ask "Who owns this file?" SELinux asks "What is this process allowed to do with this file?" Every process, file, and socket has a security context. A context looks like system_u:system_r:httpd_t:s0. The type field, httpd_t here, is the key part for policy decisions.
When a process tries an action that violates the policy, the kernel blocks it and writes an Access Vector Cache (AVC) denial to the audit log. These raw AVC messages are cryptic. They list source and target contexts, operations, and classes, but they do not tell you which file caused the problem or how to fix it.
Think of Unix permissions as a building's keycard system. Your card gets you into the lobby and your office. SELinux is the security guard who checks what you are carrying. Even if you have the keycard to enter the server room, the guard stops you if you are trying to carry a flamethrower into a data center. The AVC denial is the guard writing a ticket. setroubleshoot is the dispatcher who reads the ticket and tells you exactly which rule was broken and how to adjust the policy.
The setroubleshootd daemon runs in the background and generates readable reports as denials occur. sealert is the command-line tool that queries the daemon or parses the audit log to show you those reports. Fedora includes setroubleshoot-server by default on Workstation and Server. If you are on a minimal install, you might need to install it.
SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. The summary often points directly to the fix command.
Run sealert before you guess. The report tells you the fix.
Install the analysis tools
Ensure the analysis tools are present and the daemon is running. Minimal spins often omit these packages to save space.
rpm -q setroubleshoot-server # Verify the package is installed. Minimal spins often omit this.
sudo dnf install setroubleshoot-server # Install if missing. Provides sealert and the setroubleshootd daemon.
sudo systemctl enable --now setroubleshootd # Start the daemon. It generates reports in real time as denials occur.
Find and read the denial
Run sealert to parse the audit log. This command groups related denials and assigns a unique UUID to each issue. The output lists the denial and suggests fixes ranked by confidence.
sudo sealert -a /var/log/audit/audit.log # Scan the audit log for AVC denials and generate a summary report.
The command prints a summary to the terminal. You will see output like this:
SELinux is preventing nginx from name_connect access on the tcp_socket port 5432.
***** Plugin catchall_boolean (89.3 confidence) suggests *****
If you want to allow nginx to can_network_relay
Then you must tell SELinux about this by enabling the 'can_network_relay' boolean.
Do
setsebool -P can_network_relay 1
***** Plugin catchall (11.7 confidence) suggests *****
...
The output includes a UUID for the denial. Use the UUID to get the detailed report. This view includes the exact source and target contexts, the affected file path, and the recommended commands.
sudo sealert -l 8a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d # Open the full report for the specific UUID. Replace the UUID with the one from the summary.
The detailed report explains why the denial happened. It shows the source process, the target object, and the operation. It also lists the suggested fixes. The suggestions are ranked by confidence. A boolean suggestion with high confidence is usually the safest path. A custom module suggestion is needed when no boolean exists.
Trust the boolean. If setroubleshoot suggests a boolean, use it. Booleans are designed for exactly this scenario and are safe to toggle.
Apply the fix
Choose the fix based on the report. Booleans are switches defined by the policy. They are safe and reversible. Modules add new rules. Use booleans first. If no boolean exists, use a module. Never use chcon for a permanent fix. chcon changes the file context in place. The next restorecon or package update will revert it. A module persists across reboots and relabels.
Generate a policy module when the fix requires a custom rule. The -m flag creates a module, compiles it, and installs it automatically. This is the standard way to add persistent policy exceptions.
sudo sealert -a /var/log/audit/audit.log -m # Generate and install a local policy module to fix the denial.
The command creates a module named after the denial, compiles it, and loads it into the kernel. The module is stored in /etc/selinux/target_modules/active/modules/. It survives reboots and policy updates.
Config files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Edit /etc/. Never edit /usr/lib/. The same rule applies to SELinux policy. Local modules go in /etc/selinux/target_modules/active/modules/. Do not modify the base policy in /usr/share/selinux/.
Under the hood, sealert uses audit2why and audit2allow. audit2why explains the denial. audit2allow generates the policy. sealert wraps these tools and adds confidence scoring and boolean checks. You rarely need to call audit2allow directly unless setroubleshoot is unavailable.
Snapshot the policy before applying changes. semodule -l shows what is installed. Keep a list of local modules so you can audit them later.
Verify it worked
Confirm the denial is gone and the service recovers. Check for new AVC denials and verify the service status.
sudo ausearch -m avc -ts recent # Search for AVC denials since the last policy load. An empty result means the fix worked.
systemctl status myapp.service # Verify the service is active and running.
If ausearch returns no results and the service is running, the fix is complete. If you see new denials, run sealert again to analyze the new issue.
Reboot before you debug. Half the time the symptom is gone after a clean boot with the new policy loaded.
Common pitfalls
sealert -a reads the entire audit log. You might see denials from a package update that happened three weeks ago. Use sealert -a with caution. Better to rotate the log or use ausearch to find recent denials first.
sudo auditctl -f # Rotate the audit log. This clears old denials from the current log buffer.
setroubleshoot uses source RPM metadata to map file paths to package names. If you see warnings about missing source RPMs, the report might lack file path details. Ensure your source repositories are enabled in dnf configuration.
Running in permissive mode logs denials but does not block them. This is useful for testing, but it leaves the system unprotected. Switch back to enforcing mode after debugging.
sudo setenforce 1 # Switch back to enforcing mode. This applies immediately without a reboot.
Never set SELINUX=disabled in /etc/selinux/config to fix a denial. That removes the security layer entirely. Fix the policy. If you are debugging, set SELINUX=permissive temporarily, but revert to enforcing once the issue is resolved.
When to use this vs alternatives
Use sealert -a when you want a quick overview of all denials in the audit log.
Use sealert -l <uuid> when you need the detailed explanation and fix commands for a specific denial.
Use sealert -m when you want to generate a local policy module to fix the denial permanently.
Use setsebool when the denial is caused by a boolean toggle and you want a global switch without writing custom policy.
Use chcon when you are testing a context change temporarily and accept that it will be lost on the next relabel.
Use audit2allow when setroubleshoot is not installed and you need to generate a policy module from raw AVC messages.
Use journalctl -t setroubleshoot when you want to see the one-line summaries generated by the daemon in real time.
Use restorecon when you suspect a file context is wrong and you want to reset it to the default defined by the policy.