Configure SELinux on Fedora

Fedora runs SELinux in enforcing mode by default, and the recommended way to configure it is to adjust booleans, file contexts, and policies rather than disabling it.

Story / scenario opener

You rebooted your Fedora machine after a routine update and your web server refuses to start. The logs show a permission denied error, but the file permissions look correct. You check the service status and see a red [FAILED] next to httpd.service. The culprit is almost certainly SELinux. It is not trying to break your system. It is doing exactly what it was designed to do.

What's actually happening

SELinux stands for Security-Enhanced Linux. It runs as a kernel module that enforces mandatory access controls. Traditional Linux permissions only check the user and group. SELinux adds a third dimension: the security context. Every process and every file gets a label. The policy defines which labels are allowed to interact. Think of it like a building with strict visitor badges. Even if you have the right key to open a door, you cannot enter a restricted floor without the correct badge. Fedora ships with SELinux in Enforcing mode by default. That means unauthorized actions are blocked immediately and logged. Most users panic and disable it. That removes the safety net entirely. You can keep the safety net and just learn how to adjust the rules.

Check the current state before you change anything. Run getenforce to see the active mode. Run sestatus -v to see the full policy version and loaded modules. Trust the package manager. Manual file edits drift, snapshots stay.

The fix or how-to

You need three tools to work with SELinux daily: status checks, context repair, and boolean toggles. Start by installing the diagnostic utilities if you do not have them yet.

Here is how to install the core SELinux management packages and the human-readable reporting daemon.

sudo dnf install policycoreutils-python-utils setroubleshoot-server
# policycoreutils-python-utils provides sealert and audit2allow
# setroubleshoot-server runs the daemon that translates kernel AVC denials
# into plain English messages in /var/log/messages

The setroubleshootd service logs human-readable denial explanations to /var/log/messages and can send desktop notifications on Fedora Workstation. You should always check journalctl -xeu setroubleshootd after a denial. The x flag adds explanatory text and the e flag jumps to the end. Most sysadmins type journalctl -xeu <unit> muscle-memory style.

When a service fails, your first move is to switch to Permissive mode. Permissive mode logs violations but does not block them. This lets you see exactly what is failing without locking yourself out of a running system.

Here is how to toggle Permissive mode at runtime and restore Enforcing mode afterward.

sudo setenforce 0
# Switches the kernel to Permissive mode immediately
# Changes are temporary and revert to the config file value on reboot
# Use this only for active troubleshooting, not as a permanent state

sudo setenforce 1
# Restores Enforcing mode immediately
# Blocks unauthorized actions again and resumes logging denials
# Always run this after you identify the root cause

If you need to change the default mode permanently, edit the configuration file. Config files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Edit /etc/. Never edit /usr/lib/.

Here is how to update the persistent SELinux configuration safely.

sudo sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config
# Replaces the existing SELINUX line with the new value
# Preserves comments and other configuration directives
# Requires a full reboot to take effect

# Revert to enforcing when ready
sudo sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
# Restores the default Fedora security posture
# Do not set SELINUX=disabled unless you are running legacy hardware
# Disabling removes labels from the filesystem and breaks re-enablement

File context mismatches cause the majority of SELinux denials. Moving files to non-standard directories, cloning repositories, or downloading archives strips the default labels. You need to restore the expected contexts.

Here is how to inspect current labels and restore the correct defaults.

ls -Z /var/www/html/index.html
# Prints the file path alongside its SELinux context
# Format: user:role:type:level
# The type field is what the policy actually checks

sudo restorecon -Rv /var/www/html
# Recursively resets contexts to match the policy defaults
# The -v flag prints every file that gets relabeled
# Always prefer restorecon over manual chcon for standard paths

If you intentionally place files in a non-standard location, you can assign a custom context. Custom contexts are temporary by default. They survive a single reboot but get overwritten by the next restorecon run. Use semanage fcontext for persistent custom labels, but only when you understand the policy implications.

Here is how to apply a temporary custom context to a non-standard web root.

sudo chcon -t httpd_sys_content_t /srv/mywebsite
# Assigns the httpd_sys_content_t type to the directory
# Allows the Apache process to read files in this location
# Changes are runtime-only and reset after a full relabel

sudo chcon -Rt httpd_sys_content_t /srv/mywebsite
# Applies the type recursively to all existing files
# Useful for quick testing before committing to a permanent rule
# Verify with ls -Z before restarting the service

Booleans act as on/off switches for specific policy behaviors. They let you enable features like network access or home directory serving without writing custom modules.

Here is how to list, search, and toggle SELinux booleans safely.

getsebool -a | grep httpd
# Filters the full boolean list for Apache-related switches
# Helps you find the right toggle without reading hundreds of lines
# Boolean names follow a service_can_action pattern

sudo setsebool -P httpd_can_network_connect on
# Enables the boolean persistently across reboots
# The -P flag writes the change to the policy store
# Runtime changes without -P vanish after a restart

When standard booleans and context fixes do not resolve a denial, you can generate a custom policy module. This is a last resort. Custom modules bypass the default security boundaries. Review every line before installing.

Here is how to extract recent denials and compile them into a loadable module.

sudo ausearch -m avc -ts recent
# Queries the audit subsystem for Access Vector Cache denials
# Shows the raw kernel messages with source and target contexts
# Use this to verify the exact process and file involved

sudo ausearch -m avc -ts recent | audit2allow -M mypolicy
# Parses the audit log and generates a policy module source
# Creates mypolicy.te and mypolicy.pp in the current directory
# Always review mypolicy.te before loading it into the kernel

sudo semodule -i mypolicy.pp
# Installs the compiled policy module into the active policy
# Takes effect immediately without a reboot
# Remove with semodule -r mypolicy if it causes instability

Run restorecon before you guess. Half the time the symptom is gone.

Verify it worked

You need to confirm that the service starts cleanly and that SELinux is no longer blocking it. Check the service status first. It shows recent log lines and the current state in one view. Always check status before restart.

Here is how to verify the service is running and that no new denials appear.

sudo systemctl status httpd.service
# Shows the active state, recent journal lines, and reload count
# Look for Active: active (running) and zero failed dependencies
# If it fails, read the last three lines before changing anything

sudo ausearch -m avc -ts recent
# Confirms no new Access Vector Cache denials were generated
# An empty output means the policy now allows the operation
# Run this immediately after starting the service

If the service starts and the audit log stays clean, your fix is solid. Reboot the machine to confirm the configuration survives a full cycle. Reboot before you debug. Half the time the symptom is gone.

Common pitfalls and what the error looks like

The dnf upgrade command will refuse to proceed and print Error: Transaction test error: package python3-3.12.x conflicts with python3-3.13.y. The conflict is intentional. Read the next paragraph before forcing. This is not an SELinux error, but it shows how Fedora protects you from breaking dependencies. SELinux errors look different. They appear in the journal as avc: denied { read } for pid=... comm="httpd" name="index.html" dev="sda1" ino=... scontext=... tcontext=... tclass=file. The scontext is the source process. The tcontext is the target file. The tclass is the object type. If you see [FAILED] Failed to start NetworkManager.service during boot, your network configuration probably references a missing interface name. SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux.

Another common mistake is editing /usr/lib/systemd/system/ unit files. Those files are owned by packages. Your edits vanish on the next dnf upgrade --refresh. dnf upgrade --refresh is the normal weekly maintenance command. dnf system-upgrade is for crossing major Fedora releases. They are different commands. Don't conflate them. Always drop custom overrides in /etc/systemd/system/ instead. The same rule applies to SELinux. Edit /etc/selinux/config. Never touch /usr/lib/selinux/.

Running chcon on a directory without -R leaves child files unlabeled. The parent gets the right type, but the process still hits denials on the children. Always verify with ls -Z after labeling. Trust the package manager. Manual file edits drift, snapshots stay.

When to use this vs alternatives

Use Permissive mode when you are actively diagnosing a failing service and need to see the exact denial without interrupting uptime. Use Enforcing mode when you are running production workloads and want mandatory access controls to block unauthorized actions. Use restorecon when files have been moved, copied, or downloaded into standard system directories. Use chcon when you need a quick temporary override for testing a non-standard path. Use booleans when the policy already supports the feature you need, such as allowing a service to connect to the network or read home directories. Use audit2allow when no boolean or standard context matches your use case and you must generate a custom policy module. Stay on the default Enforcing posture if you only deviate from the defaults occasionally.

Where to go next