Managing SELinux on Fedora: Confined Services, Custom Policies, and Troubleshooting Without Disabling Enforcement
Keep SELinux enforcing on Fedora. Fix denials by labeling paths with semanage fcontext, using booleans via setsebool -P, and writing narrow custom policy modules (reviewed, not raw audit2allow). Worked examples for httpd on /srv/webapp, remote DB connections, and a custom module.
07 Jan 2026, 03:52 UTC

The Problem: SELinux Blocks Legitimate Service Behavior
Fedora ships with SELinux in enforcing mode using the targeted policy. Network-facing services like httpd, named, and mysqld run confined, while users and most processes remain unconfined. When you deploy a service to a non-standard path or need it to connect to an external database, SELinux denies the action by default. The typical reaction—setting SELINUX=permissive or disabled—removes all confinement and creates a relabeling nightmare later. The practical approach: keep enforcement on, fix the specific denial with the narrowest change, and verify the result.
Worked Example: Serving a Web App from /srv/webapp
You have an httpd instance that must serve content from /srv/webapp instead of /var/www/html. The directory exists and the service user can read it, but requests return 403 Forbidden. The audit log shows an AVC denial for httpd_t reading var_t (the default label on /srv).
Step 1: Label the Directory Correctly
Add a persistent file-context rule and apply it:
# Run as root on the target host
semanage fcontext -a -t httpd_sys_content_t '/srv/webapp(/.*)?'
restorecon -Rv /srv/webapp
semanage fcontext writes to /etc/selinux/targeted/contexts/files/file_contexts.local, which takes precedence over package-provided contexts. The regex (/.*)? covers the directory and everything under it. restorecon -Rv relabels in place; use -n first for a dry-run on production systems.
Step 2: Verify the Label
ls -Z /srv/webapp
# Expected output:
system_u:object_r:httpd_sys_content_t:s0
If the label matches, httpd can now read the content. No policy module or boolean required.
When Labels Aren't Enough: Boolean Toggles
Your httpd now needs to connect to a PostgreSQL database on a remote host (port 5432). The targeted policy blocks outbound connections from httpd_t by default. Check the relevant boolean:
semanage boolean -l | grep httpd_can_network
# httpd_can_network_connect (off , off) Allow HTTPD to make network connections
Enable it persistently:
setsebool -P httpd_can_network_connect 1
The -P flag writes to /etc/selinux/targeted/modules/active/booleans.local so the setting survives reboots. Do not edit that file directly; it is overwritten on policy updates. Verify with getsebool httpd_can_network_connect.
Custom Policy Modules for Application-Specific Needs
An internal service myapp (running as myapp_t) needs to read /etc/myapp/secret.key and write to /var/log/myapp/. No existing boolean covers this. Create a minimal custom module:
Step 1: Capture the Denials
# Reproduce the failure, then:
ausearch -c 'myapp' --raw | audit2allow -M myapp_local
This generates myapp_local.te (source) and myapp_local.pp (compiled module). The _local suffix avoids collisions with future distribution policy updates.
Step 2: Review and Narrow the Generated Rules
Open myapp_local.te. audit2allow often produces overly broad rules like:
allow myapp_t self:capability sys_admin;
Replace with specific type transitions and file permissions. Example refined .te:
policy_module(myapp_local, 1.0)
gen_require(
type myapp_t;
type etc_t;
type var_log_t;
)
# Read specific config file
allow myapp_t etc_t:file { read open getattr };
# Write logs
allow myapp_t var_log_t:dir { add_name remove_name write };
allow myapp_t var_log_t:file { create write append };
# If myapp_t doesn't exist yet, define it:
# type myapp_t;
# init_daemon_domain(myapp_t)
# ...
Step 3: Compile, Package, and Install
checkmodule -M -m -o myapp_local.mod myapp_local.te
semodule_package -o myapp_local.pp -m myapp_local.mod
semodule -i myapp_local.pp
Confirm it loaded:
semodule -l | grep myapp_local
# myapp_local 1.0
Troubleshooting Workflow
- Check mode:
getenforcemust returnEnforcing. - Find recent denials:
ausearch -m avc -ts recentshows raw records;sealert -a /var/log/audit/audit.loggives human-readable explanations with suggested fixes (boolean, type change, or custom module). - Pick the narrowest fix: boolean > file-context relabel > custom module. Avoid
audit2allowoutput without review. - Test the fix: Reproduce the operation; verify no new AVCs appear with
ausearch -m avc -ts today | tail -5.
Limits and Common Mistakes
- Never disable enforcement as a first resort. Accumulated mislabeled files make re-enforcement painful. If you must test in permissive, set
SELINUX=permissivetemporarily in/etc/selinux/configand reboot, but plan to return to enforcing. restorecon -R /on production is risky. It relabels thousands of files and can trigger service restarts. Userestorecon -n(dry-run) or test on a staging clone.- Custom modules can conflict with policy upgrades. After a
selinux-policy-targetedupdate, runsemodule -l | grep -v ^[0-9]to list non-base modules and review each for duplicated or conflictingallowrules. - Boolean edits outside
setsebool -Pare lost. Manual changes tobooleans.localare overwritten. Use onlysetsebool -Porsemanage boolean -m. - File-context precedence is regex-dependent. Rules in
file_contexts.localwin over package contexts, but ambiguous regex matches can be overridden by a laterrestorecon. Keep patterns specific. - Immutable variants (Silverblue/Kinoite) require layered packages.
/etc/selinuxis writable but/usris not. Install custom modules viarpm-ostree installlayered packages or a systemd service that runssemodule -iat boot.
Container Workloads
Podman runs containers with type container_t by default. For multi-level separation without disabling SELinux, use:
podman run --security-opt label=type:container_runtime_t ...
# or for MCS categories:
podman run --security-opt label=level:s0:c123,c456 ...
This keeps confinement active while isolating container processes from each other and the host.
Verification Checklist
getenforce→Enforcingsemanage boolean -l | head -20→ expected booleans show(on, on)or(off, off)ls -Z /srv/webapp→system_u:object_r:httpd_sys_content_t:s0semodule -l | grep myapp_local→ module listed with versionausearch -m avc -ts today | tail -5→ no new denials after fix
Summary
Keep SELinux in enforcing mode. Fix denials by labeling non-standard paths with semanage fcontext, enabling documented booleans with setsebool -P, and writing narrow custom policy modules (reviewed, not raw audit2allow output) for application-specific needs. Verify each change with the checklist above. This preserves the security posture Fedora ships with while letting your services run as intended.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.