SELinux Troubleshooting Flowchart

Diagnosing Permission Denials

When SELinux blocks something on Fedora, a clear diagnostic sequence — checking audit logs, using audit2why, and applying targeted fixes — gets you unblocked without disabling SELinux entirely.

The service fails despite correct permissions

You moved your web root to /srv/www and restarted Apache. The browser returns a 403 Forbidden error. You check ls -l and the permissions are 755 owned by root. You check getenforce and it returns Enforcing. The logs show Permission denied. The standard Unix permissions are correct, but SELinux is blocking the access. This happens because SELinux tracks security contexts, not just ownership. A botched policy change can lock you out of critical services. Run the diagnosis steps below to identify the exact block and apply a targeted fix.

What SELinux is actually doing

Think of standard Unix permissions as a bouncer checking IDs at the door. If you have the right ID, you get in. SELinux is the building manager inside. Even if the bouncer lets you in, the manager checks your badge to see which rooms you can enter. A file might be readable by everyone, but if its SELinux label says "confidential document" and the process trying to read it has a "public web server" badge, the manager stops the access. The denial goes into the audit log, and the process gets a generic Permission denied error.

The label is called a context. A context looks like system_u:object_r:httpd_sys_content_t:s0. The type, the third field, is what matters 99 percent of the time. The type defines what the file is and what processes can touch it. If the type is wrong, the rules are wrong.

Focus on the type. The type determines the rules. If the type is wrong, the rules are wrong.

Install the analysis tools

Fedora includes basic SELinux tools by default, but the analysis utilities are in separate packages. Install them before you start debugging.

sudo dnf install setroubleshoot-server policycoreutils-python-utils
# setroubleshoot-server provides sealert and journal integration for plain-English explanations
# policycoreutils-python-utils provides semanage, restorecon, and audit2why for policy management

Fedora ships with setroubleshoot. It translates raw audit logs into readable messages. Always check journalctl -t setroubleshoot before digging into raw AVC messages. It often tells you exactly which command to run.

Diagnose the denial

Verify SELinux is enforcing and find the denial message. If the mode is Permissive, SELinux logs denials but does not block access. The issue is elsewhere.

getenforce
# Returns Enforcing, Permissive, or Disabled. Enforcing blocks access. Permissive logs but allows.

sudo journalctl -t setroubleshoot | tail -20
# -t filters by tag. setroubleshoot messages contain the plain-English summary and suggested fix.
# tail shows the most recent denials.

You will see a message like SELinux is preventing httpd from read access on the file /srv/www/index.html. The message includes a source, target, and suggested fix. If setroubleshoot is not installed or you need raw details, use the audit tools.

sudo ausearch -m avc -ts recent
# -m avc filters for Access Vector Cache denials. -ts recent shows entries since the last boot or log rotation.

sudo ausearch -m avc -ts recent | audit2why
# audit2why reads the denial and explains which policy rule triggered the block.
# It often suggests a boolean or context change.

Run journalctl -t setroubleshoot first. Read the actual error before guessing.

Apply the fix

Most denials fall into three categories: wrong file label, disabled boolean, or wrong port. Pick the strategy that matches the symptom.

Fix the file label

Files that have been moved or created in the wrong place get the wrong SELinux context. The context is based on the file path. If you moved a file, the label did not move with it.

ls -Z /srv/www/index.html
# -Z displays the SELinux context. Compare this to the expected context for a web document.

sudo restorecon -Rv /srv/www
# -R recurses into subdirectories. -v prints what changed.
# restorecon reads the file context rules and applies the correct label based on the path.

If the default label for a path is wrong, define a custom rule. Use semanage fcontext to persist the rule, then restorecon to apply it. Never use chcon for permanent fixes. chcon changes the label on disk but does not update the policy rules. A full relabel or restorecon will overwrite chcon changes.

sudo semanage fcontext -a -t httpd_sys_content_t "/data/web(/.*)?"
# fcontext adds a rule to the policy database. This survives relabels and reboots.

sudo restorecon -Rv /data/web
# Applies the new fcontext rule to the files on disk.

Use semanage fcontext for any directory outside the default paths. chcon is a bandage that falls off.

Enable a boolean

SELinux booleans toggle optional policy rules. Many common scenarios have a pre-built boolean. Check if a boolean exists before writing custom policy.

getsebool -a | grep httpd_can_network_connect
# Lists the current state of the boolean. off means the feature is disabled.

sudo setsebool -P httpd_can_network_connect on
# -P makes the change persistent across reboots. Without -P, the setting resets when the system restarts.

Allow a non-standard port

Services can only bind to ports that match their type. If you run a web server on port 9090, you must add that port to the allowed list.

sudo semanage port -l | grep http_port_t
# Lists the ports allowed for the http_port_t type. Default is usually 80, 443, 8080, 8443.

sudo semanage port -a -t http_port_t -p tcp 9090
# -a adds a new port. -t assigns the type. -p specifies the protocol.
# This updates the policy database so httpd can listen on port 9090.

Create a custom policy module

Generate a custom module only when no boolean, port, or context fix resolves the denial. Custom modules drift over time and complicate upgrades.

sudo ausearch -m avc -ts recent | audit2allow -M mycustompolicy
# audit2allow generates a .te file and a .pp module from the denials.
# -M names the module.

sudo semodule -i mycustompolicy.pp
# Installs the policy module into the running policy.
# Policy modules go into the kernel policy, not /etc. Do not edit files in /usr/share/selinux.

Run restorecon before you write a custom policy. 90 percent of denials are just a misplaced label.

Verify the result

Confirm the service starts and no new denials appear. Check the service status and the audit log.

sudo systemctl status httpd
# Check the active state and recent log lines. Look for "Active: active (running)".

sudo ausearch -m avc -ts recent
# Run this after the fix. An empty output means no new denials since the last check.

Reboot the system to test persistence. If the boolean or port rule vanishes, you forgot the -P flag or the policy module.

Common pitfalls

A few patterns cause repeated headaches. Avoid them to save time.

  • Editing /usr/lib instead of /etc: Config files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Edit /etc/. Never edit /usr/lib/. SELinux policy files follow the same rule. Do not edit policy sources in /usr/share/selinux. Use semodule to manage modules.
  • Forcing chcon: chcon is useful for quick tests, but it breaks on the next restorecon. Always prefer semanage fcontext for persistent changes.
  • Disabling SELinux: Setting SELINUX=disabled in /etc/selinux/config removes the security layer entirely. Use Permissive mode for debugging if you must, but switch back to Enforcing once the fix is applied.
  • Ignoring setroubleshoot: The setroubleshoot service runs in the background and generates the helpful journal messages. If you see raw AVC messages but no setroubleshoot output, check systemctl status setroubleshootd.

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

Decision matrix

Use restorecon when files were moved or copied and lost their correct label. Use setsebool when a service needs a capability that is disabled by default but supported by the policy. Use semanage port when a service must listen on a port that is not in the default list for its type. Use semanage fcontext when you are creating a new directory structure that needs a persistent label rule. Use audit2allow only when no boolean, port, or context fix resolves the denial. Use journalctl -t setroubleshoot as your first step to get a plain-English explanation.

Where to go next