Loading content...
Understanding SELinux policy is one thing. Running it effectively in production—across hundreds or thousands of systems, through software updates, across development-to-production workflows—is another challenge entirely.
Policy management encompasses the operational reality of SELinux:
This page bridges the gap between SELinux knowledge and SELinux operations. You'll learn the workflows, tools, and practices that make SELinux manageable at scale.
By the end of this page, you will understand systematic troubleshooting workflows for SELinux issues, policy update and deployment strategies, audit and monitoring approaches, infrastructure-wide policy governance, and how to balance security requirements with operational needs.
When something breaks and SELinux might be involved, follow this systematic process. Resist the temptation to setenforce 0 as the first step—that tells you nothing useful.
1234567891011121314151617181920212223
# Check for recent AVC denials related to your service$ sudo ausearch -m avc -ts recent -c <service-name> # Example: Apache isn't serving content$ sudo ausearch -m avc -ts recent -c httpd----time->Thu Jan 16 10:15:23 2025type=AVC msg=audit(1642324523.456:789): avc: denied { read } for pid=5678 comm="httpd" name="page.html" dev="sda1" ino=123456 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:tmp_t:s0 tclass=file permissive=0 # OUTPUT = SELinux is definitely involved# NO OUTPUT = SELinux is likely NOT the issue (check journalctl, app logs) # Quick check: any AVC in last 10 minutes$ sudo ausearch -m avc -ts recent | tail -20 # If you suspect dontaudit is hiding denials$ sudo semodule -DB # Disable dontaudit temporarily$ <reproduce-the-issue>$ sudo ausearch -m avc -ts recent -c <service>$ sudo semodule -B # Re-enable dontaudit1234567891011121314151617181920212223
# Get human-readable explanation$ sudo ausearch -m avc -ts recent -c httpd | audit2why type=AVC msg=audit(...): avc: denied { read }... Was caused by: Missing type enforcement (TE) allow rule. # OR: If setroubleshoot is installed (desktop/full installs)$ sudo sealert -l "*" # List all alerts$ sudo sealert -a /var/log/audit/audit.log # Analyze audit log # Common causes audit2why reports:# 1. "Missing TE allow rule" - Policy doesn't permit this access# → Either mislabeled file or missing policy## 2. "Constraint violation" - Rule exists but constraint denies# → Usually MLS or user mismatch## 3. "The boolean X was set incorrectly"# → Enable the boolean## 4. "The source type is not allowed to execute the target"# → Domain transition issueMost denials fall into three categories:
Category A: Mislabeled File
$ ls -Z /var/www/html/page.html
-rw-r--r--. root root unconfined_u:object_r:tmp_t:s0 page.html
$ matchpathcon /var/www/html/page.html
/var/www/html/page.html system_u:object_r:httpd_content_t:s0
# MISMATCH! File has tmp_t, should be httpd_content_t
# Solution: restorecon -v /var/www/html/page.html
Category B: Non-Standard Path
$ ls -Z /opt/website/index.html
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 index.html
$ matchpathcon /opt/website/index.html
/opt/website/index.html system_u:object_r:default_t:s0
# File has correct label for path, but path has no web rule
# Solution: semanage fcontext -a -t httpd_content_t '/opt/website(/.*)?'
# restorecon -Rv /opt/website/
Category C: Legitimate Policy Need
# Apache needs to connect to backend database
$ sesearch --allow -s httpd_t -c tcp_socket -p name_connect | grep mysql
# No output - no rule exists
# Solution: Check if a boolean exists
$ getsebool httpd_can_network_connect_db
httpd_can_network_connect_db --> off
# Enable it:
$ sudo setsebool -P httpd_can_network_connect_db on
Experience reveals patterns—the same issues recur across environments. Here's a field guide to the most common SELinux problems and their solutions.
| Symptom | Likely Cause | Diagnostic | Solution |
|---|---|---|---|
| Web server can't read files in DocumentRoot | Files moved (mv) from elsewhere, retained wrong label | ls -Z /var/www/html/ shows non-httpd type | restorecon -Rv /var/www/html/ |
| App can't write to log directory | Log dir has wrong type | ls -Zd /var/log/myapp/ | Create proper fcontext rule |
| FTP uploads can't be served by web | Uploaded files get ftpd type | ls -Z shows ftp types | setsebool -P ftpd_anon_full_access on or relabel |
| Samba shares inaccessible | Share directory not labeled for Samba | ls -Zd /srv/samba/ | semanage fcontext -a -t samba_share_t '/srv/samba(/.*)?' |
| Container can't access host volume | Host path has incompatible label | ls -Z /host/path | Use :z or :Z in Docker volume mount |
| Symptom | Likely Cause | Diagnostic | Solution |
|---|---|---|---|
| Service can't bind to non-standard port | Port not assigned to service type | semanage port -l | grep <port> | semanage port -a -t <type> -p tcp <port> |
| App can't connect to database | Boolean disabled OR no rule | getsebool | grep network | setsebool -P httpd_can_network_connect_db on |
| Web proxy can't connect upstream | Default: web can't make outbound | sesearch --allow -s httpd_t -c tcp_socket | setsebool -P httpd_can_network_connect on |
| SSH on alternate port fails | Port not in ssh_port_t | semanage port -l | grep ssh | semanage port -a -t ssh_port_t -p tcp <port> |
| Symptom | Likely Cause | Diagnostic | Solution |
|---|---|---|---|
| Service starts but is unconfined | Binary not labeled as entry point | ls -Z /path/to/binary | Add file context for _exec_t type |
| Script can't execute | Script has wrong exec type | ls -Z script.sh | chcon -t bin_t script.sh or proper type |
| CGI scripts won't run | Scripts not labeled appropriately | ls -Z /var/www/cgi-bin/ | Check httpd_enable_cgi boolean, labels |
| Cron job fails with permission denied | Cron runs in system_cronjob_t | Check AVC for cronjob_t denials | Create policy for cron context |
Most issues resolve with restorecon, semanage fcontext, or setsebool. Write custom policy only when: (1) no boolean exists for legitimate access, (2) you've confirmed this isn't a labeling issue, and (3) you're deploying a custom application without existing policy.
SELinux policy is a living artifact—it evolves with the system. Distribution updates, new software, and changing requirements all drive policy changes.
Linux distributions provide policy updates as packages:
# RHEL/CentOS/Fedora
sudo dnf update selinux-policy selinux-policy-targeted
# Ubuntu/Debian (AppArmor default, but SELinux available)
sudo apt install selinux-policy-targeted
Updates may:
Local customizations persist across updates because they're stored separately:
/var/lib/selinux/targeted/active/
├── modules/100/ # Distribution modules (base priority)
│ ├── base/
│ ├── httpd/
│ └── ...
├── modules/400/ # Local modules (higher priority)
│ └── my-custom/ # Your custom modules
├── file_contexts.local # Custom file contexts
├── booleans.local # Boolean overrides
└── seusers.local # User mappings
Higher priority modules take precedence. Your local modules (400) override distribution modules (100).
12345678910111213141516171819202122232425
# List all installed modules with priorities$ semodule -lfull100 apache 1.16.0 100 auditd 1.12.0100 base 3.19.0...400 my-webapp 1.0.0 # ← Local module at priority 400 # Install module at specific priority$ sudo semodule -i my-webapp.pp -X 400 # Remove a module$ sudo semodule -r my-webapp # Export local customizations for backup$ sudo semanage export > selinux-customizations.txt # View the export$ cat selinux-customizations.txtboolean -m -1 httpd_can_network_connectfcontext -a -t httpd_content_t '/srv/www(/.*)?'port -a -t http_port_t -p tcp 8081 # Import on another system$ sudo semanage import < selinux-customizations.txtBefore deploying policy changes to production:
Test in permissive mode first
sudo setenforce 0
# Deploy and test changes
sudo ausearch -m avc -ts today # Verify no unexpected denials
sudo setenforce 1
# Verify functionality in enforcing mode
Use a staging environment
Know the rollback procedure
# Remove problematic module
sudo semodule -r bad-module
# Revert to previous policy state (if tracked)
sudo semanage import < previous-state.txt
# Nuclear option: reinstall distribution policy
sudo dnf reinstall selinux-policy-targeted
Setting SELINUX=disabled in /etc/selinux/config destroys file labels. After re-enabling, you must relabel the entire filesystem. Always use permissive mode for testing, never disabled.
In infrastructure-as-code environments, SELinux policy should be managed declaratively alongside other configuration.
Ansible SELinux Modules:
123456789101112131415161718192021222324252627282930313233343536373839404142434445
# Set SELinux mode- name: Ensure SELinux is enforcing ansible.posix.selinux: policy: targeted state: enforcing # Manage booleans- name: Allow HTTPD to connect to network ansible.posix.seboolean: name: httpd_can_network_connect state: yes persistent: yes # Manage file contexts- name: Set file context for custom web dir community.general.sefcontext: target: '/srv/webapp(/.*)?' setype: httpd_content_t state: present notify: Restore SELinux contexts # Apply file contexts- name: Restore SELinux contexts ansible.builtin.command: cmd: restorecon -Rv /srv/webapp # Manage ports- name: Allow HTTP on port 8080 community.general.seport: ports: 8080 proto: tcp setype: http_port_t state: present # Install custom policy module- name: Deploy custom SELinux module ansible.builtin.copy: src: files/myapp.pp dest: /tmp/myapp.pp notify: Install SELinux module - name: Install SELinux module ansible.builtin.command: cmd: semodule -i /tmp/myapp.pp when: custom_policy_deployed | default(false)SELinux secures container workloads:
# Docker/Podman with SELinux
# By default, containers run in container_t domain
$ podman run -it --rm alpine sh
$ ps -eZ | grep alpine
system_u:system_r:container_t:s0:c123,c456 ... sh
# Volume mounting with SELinux
# :z = shared label (multiple containers)
# :Z = private label (single container)
$ podman run -v /host/data:/container/data:z ...
# Kubernetes security contexts
# spec.securityContext.seLinuxOptions
1234567891011121314151617181920212223
apiVersion: v1kind: Podmetadata: name: myappspec: securityContext: seLinuxOptions: # Assign specific SELinux context to pod level: "s0:c123,c456" # MCS categories for isolation containers: - name: myapp image: myapp:latest securityContext: seLinuxOptions: type: "container_t" # Or custom type if policy exists volumeMounts: - name: data mountPath: /data volumes: - name: data hostPath: path: /srv/myapp-data # Host path must be labeled appropriately!Store SELinux policy sources (.te, .if, .fc files) and customization exports (semanage export) in version control. Use CI/CD to compile and validate policy before deployment. Treat policy as code.
Security requires visibility. SELinux provides extensive auditing capabilities that should be monitored in production.
AVC messages are the primary source of SELinux security telemetry:
1234567891011121314151617181920212223242526272829
# Real-time AVC monitoring$ sudo ausearch -m avc --start recent -i# -i interprets UIDs/GIDs to names # Watch for denials as they happen$ sudo tail -f /var/log/audit/audit.log | grep AVC # Summary of denials by process$ sudo ausearch -m avc -ts today | aureport -aAVC Report===============================================# date time comm perm source target class===============================================1. 01/16/25 10:23:45 httpd read httpd_t tmp_t file2. 01/16/25 10:45:12 sshd write sshd_t var_t file... # Count denials by type$ sudo ausearch -m avc -ts today | audit2why | sort | uniq -c | sort -rn 47 Missing type enforcement (TE) allow rule. 3 The boolean httpd_can_network_connect was set incorrectly. # Check audit configuration$ sudo auditctl -l | grep avc# Ensure AVC auditing is enabled # Examine audit log size and rotation$ ls -lh /var/log/audit/$ cat /etc/audit/auditd.conf | grep max_log_fileForward SELinux events to centralized logging (SIEM) for security monitoring:
# rsyslog configuration for SELinux events
# /etc/rsyslog.d/selinux.conf
:msg, contains, "avc:" /var/log/selinux-avc.log
:msg, contains, "avc:" @siem-server:514
Track these metrics in production:
| Metric | Meaning | Alerting Threshold |
|---|---|---|
| AVC denials per hour | Overall SELinux activity | Baseline + 3σ |
| Denials by domain | Per-service issues | Any for critical services |
| Permissive mode alerts | Security degradation | Any occurrence |
| Policy load events | Policy changes | Any unexpected |
| Boolean changes | Configuration drift | Any unexpected |
| Constraint violations | User/MLS issues | Any occurrence |
12345678910111213141516171819202122232425262728
# Comprehensive status for monitoring scripts$ sestatusSELinux status: enabledSELinuxfs mount: /sys/fs/selinuxSELinux root directory: /etc/selinuxLoaded policy name: targetedCurrent mode: enforcingMode from config file: enforcingPolicy MLS status: enabledPolicy deny_unknown status: allowedMemory protection checking: actual (secure)Max kernel policy version: 33 # Check if any processes are unconfined (security gap)$ ps -eZ | grep unconfined_t | grep -v bash | grep -v sshd# Output = potentially concerning; investigate # Verify expected processes are in expected domains$ ps -eZ | grep httpd_tsystem_u:system_r:httpd_t:s0 1234 ? 00:00:05 httpd # Check for label issues on critical paths$ find /var/www -type f ! -context '*httpd*' 2>/dev/null# Output = mislabeled files in web root # Validate policy hasn't been tampered with$ semodule -l | md5sum# Compare to known-good baselineAny production system dropping to permissive mode should trigger immediate alerts. Permissive mode means SELinux isn't protecting you—it's just logging. Configure monitoring to alert when 'getenforce' returns 'Permissive' on any production system.
SELinux adds security checks to every access. Understanding its performance impact helps make informed decisions.
Typical overhead: 1-5%
SELinux's Access Vector Cache (AVC) makes most operations essentially free:
Overhead is highest during:
# Compare with and without SELinux (careful! permissive only)
$ time <workload>
$ sudo setenforce 0
$ time <workload>
$ sudo setenforce 1 # ALWAYS re-enable
# Monitor AVC cache effectiveness
$ cat /sys/fs/selinux/avc/cache_stats
lookups hits misses
2157483 2156321 1162
# Hit rate: 2156321/2157483 = 99.95%
Increase AVC cache size for workloads with diverse access patterns:
# Default is typically 512 entries
$ cat /sys/fs/selinux/avc/cache_threshold
512
# Increase for better hit rates
$ echo 2048 | sudo tee /sys/fs/selinux/avc/cache_threshold
# Make persistent via sysctl
# /etc/sysctl.d/selinux.conf
kernel.selinux.avc.cache_threshold = 2048
Audit performance can be significant with high denial rates:
# If audit logging is causing I/O pressure
# (only reduce if denials are expected and documented)
# Consider rate limiting in audit configuration
# /etc/audit/auditd.conf
freq = 50 # Flush every 50 records instead of immediately
| Workload Type | Typical Overhead | Notes |
|---|---|---|
| Web server | 1-2% | High cache hits due to repetitive file access |
| Database server | 2-3% | Moderate; varied data access patterns |
| Build/compile | 3-5% | Many unique file accesses, more cache misses |
| Container orchestration | 2-4% | Container creation involves many new contexts |
| I/O intensive | 1-3% | Per-access overhead is small relative to I/O |
| CPU intensive | <1% | Few access checks relative to computation |
A 1-5% overhead is a small price for mandatory access control that contains breaches. Organizations routinely accept larger overheads for less security value (anti-virus can add 10%+). Don't disable SELinux for performance; optimize if needed.
SELinux at scale requires governance—processes that ensure security, consistency, and change control.
Centralized Model:
Delegated Model:
Hybrid Model:
SELinux policy changes should follow change management:
The biggest governance challenge is culture. If SELinux is seen as an obstacle, people will bypass it. Build a culture where SELinux is a partner: provide good tooling, fast response to legitimate needs, and education on its value.
We've covered the operational aspects of running SELinux in production. Let's consolidate the key insights:
Module Complete:
You've completed the comprehensive SELinux module! From MAC fundamentals through policy structure, contexts and labels, type enforcement, to production policy management—you now have the deep understanding required to effectively deploy, manage, and troubleshoot SELinux on any Linux system.
You've mastered SELinux—from conceptual foundations to operational reality. You understand MAC principles, policy architecture, labeling, type enforcement, and production management. This knowledge positions you to implement defense-in-depth security on any Linux system.