Skip to content

SELinux: Architecture, Policies & Troubleshooting

Chapter 16: SELinux: Architecture, Policies & Troubleshooting

Section titled “Chapter 16: SELinux: Architecture, Policies & Troubleshooting”
  • Understand SELinux architecture and the Type Enforcement model
  • Know SELinux modes and how to manage them
  • Read and understand SELinux denials (AVC)
  • Write custom SELinux policies
  • Troubleshoot SELinux issues without disabling it

SELinux (Security-Enhanced Linux) is an advanced security guard for your system. Standard Linux security just checks if a user owns a file. SELinux checks a strict rulebook to see if a specific program is allowed to touch a specific file, stopping hackers even if they gain root access.

SELinux (Security-Enhanced Linux) is a Mandatory Access Control (MAC) system originally developed by the NSA and now maintained by Red Hat. It enforces security policies at the kernel level, even constraining root.

SELinux Core Concept: Everything Has a Label
─────────────────────────────────────────────
Every object in Linux gets a SELinux label (security context):
Files: system_u:object_r:httpd_sys_content_t:s0
Process: system_u:system_r:httpd_t:s0
Port: system_u:object_r:http_port_t:s0
Format: user:role:type:level
SELinux Policy defines:
"Can process with type httpd_t access file with type httpd_sys_content_t?"
If not explicitly allowed: DENIED (regardless of file permissions)
DAC says: "Can alice read this file?" (user-based)
SELinux says: "Can this process type access this resource type?" (type-based)

Type Enforcement (TE) — The Core Model
──────────────────────────────────────
SELinux Policy Rule:
allow httpd_t httpd_sys_content_t:file { read open getattr };
│ │ │ │ └── Operations allowed
│ │ │ └── Object class (file, dir, socket...)
│ │ └── Object type (the resource being accessed)
│ └── Subject type (the process type doing the access)
└── allow (permit this access)
Access Decision Flow:
nginx (httpd_t) tries to read /var/www/html/index.html
(httpd_sys_content_t)
┌─────────────────────────────────────────────────────┐
│ SELinux Access Vector Cache (AVC) │
│ │
│ Check: allow httpd_t httpd_sys_content_t:file read?│
│ Policy says: YES → GRANTED │
└─────────────────────────────────────────────────────┘
nginx (httpd_t) tries to read /etc/shadow (shadow_t)
┌─────────────────────────────────────────────────────┐
│ SELinux Access Vector Cache (AVC) │
│ │
│ Check: allow httpd_t shadow_t:file read? │
│ Policy says: NOT FOUND → DENIED │
│ AVC denial logged to audit.log │
└─────────────────────────────────────────────────────┘
Even if nginx runs as root and /etc/shadow is readable:
→ SELinux STILL BLOCKS it
→ This is the power of MAC

SELinux Modes
──────────────
Enforcing: Policy violations are BLOCKED and LOGGED
→ Production systems should use this
Permissive: Policy violations are only LOGGED (not blocked)
→ Use for debugging new policies
→ NEVER use in production for extended periods
Disabled: SELinux is completely off
→ All denials ignored, no logging
→ Switching from disabled → enforcing requires relabel
→ NEVER do this on production!
Terminal window
# ── Check SELinux Status ──────────────────────────────────
getenforce
# Enforcing / Permissive / Disabled
sestatus
# SELinux status: enabled
# SELinuxfs mount: /sys/fs/selinux
# SELinux mount point: /sys/fs/selinux
# Loaded policy name: targeted
# Current mode: enforcing
# Mode from config file: enforcing
# Policy MLS status: enabled
# Policy deny_unknown status: allowed
# Memory protection checking: actual (secure)
# Max kernel policy version: 33
# ── Change Mode Temporarily (survives until reboot) ───────
setenforce 0 # Switch to Permissive (for debugging)
setenforce 1 # Switch back to Enforcing
# ── Change Mode Permanently ───────────────────────────────
# /etc/selinux/config (or /etc/sysconfig/selinux)
SELINUX=enforcing # enforcing | permissive | disabled
# ── SELinux Policies (Targeted is the standard) ────────────
# targeted: only specific processes (httpd, sshd, etc.) are confined
# minimum: even fewer processes confined
# mls: Multi-Level Security (government, top secret / secret)
SELINUXTYPE=targeted

Terminal window
# ── View Security Contexts ────────────────────────────────
# File context
ls -laZ /var/www/html/
# drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 html/
# Process context
ps auxZ | grep httpd
# system_u:system_r:httpd_t:s0 root 1234 ...
# Port context
semanage port -l | grep http
# http_port_t tcp 80, 443, 488, 8008, 8009, 8443
# ── Change File Context ───────────────────────────────────
# Change context of a file
chcon -t httpd_sys_content_t /srv/mywebsite/index.html
# Change context recursively
chcon -R -t httpd_sys_content_t /srv/mywebsite/
# The CORRECT way: set the policy, then relabel
# (chcon is temporary, restorecon will reset it)
semanage fcontext -a -t httpd_sys_content_t "/srv/mywebsite(/.*)?"
restorecon -Rv /srv/mywebsite/
# ── Restore Default Contexts ──────────────────────────────
restorecon -v /etc/nginx/nginx.conf # Single file
restorecon -Rv /var/www/ # Recursive
# ── Full System Relabel (after disabling/re-enabling SELinux) ──
# Touch /.autorelabel and reboot
touch /.autorelabel
reboot
# System relabels all files on next boot (takes several minutes)

Terminal window
# ── View AVC Denials ──────────────────────────────────────
# Raw audit log
cat /var/log/audit/audit.log | grep "type=AVC"
# Example AVC denial:
# type=AVC msg=audit(1705312800.123:456): avc: denied { read } for
# pid=1234 comm="nginx" name="secret.conf"
# dev="sda1" ino=5678
# scontext=system_u:system_r:httpd_t:s0
# tcontext=system_u:object_r:admin_home_t:s0
# tclass=file permissive=0
# Breaking down the denial:
# { read } = operation attempted
# comm="nginx" = which program
# name="secret.conf" = which file
# scontext=...httpd_t = process type (source)
# tcontext=...admin_home_t = file type (target)
# tclass=file = object class
# permissive=0 = was it blocked (0=blocked, 1=permissive/logged only)
# ── Human-readable with ausearch ──────────────────────────
ausearch -m avc -ts today # Today's denials
ausearch -m avc -ts recent # Recent denials
ausearch -m avc -comm nginx # nginx-specific denials
# ── Best tool: sealert (audit2why) ────────────────────────
# Install: yum install setroubleshoot-server
sealert -a /var/log/audit/audit.log
# Provides:
# - Human-readable explanation of why it was denied
# - Suggested fix commands
# - Whether an AVC boolean exists to fix it
# ── audit2allow: Generate policy from denials ─────────────
# Show what policy would allow the denied actions:
ausearch -m avc -ts today | audit2allow
# Generate a new policy module:
ausearch -m avc -ts today | audit2allow -M mynginx
# Creates: mynginx.te (type enforcement) + mynginx.pp (compiled policy)
# Install the new policy module:
semodule -i mynginx.pp

Booleans are pre-built policy switches that toggle groups of rules on/off.

Terminal window
# ── List all booleans ─────────────────────────────────────
getsebool -a # All booleans
getsebool -a | grep httpd # HTTP-related booleans
semanage boolean -l # With descriptions
# Common booleans for web servers:
# httpd_can_network_connect = allow nginx to make outbound connections
# httpd_can_network_connect_db = allow nginx to connect to databases
# httpd_use_nfs = allow nginx to serve files from NFS
# httpd_enable_cgi = allow nginx/apache to execute CGI scripts
# ── Set a Boolean ─────────────────────────────────────────
setsebool httpd_can_network_connect on # Temporary
setsebool -P httpd_can_network_connect on # Persistent (-P = permanent)
# ── Common Real-World Boolean Fixes ───────────────────────
# Problem: nginx can't connect to upstream app server
setsebool -P httpd_can_network_connect on
# Problem: nginx can't connect to PostgreSQL
setsebool -P httpd_can_network_connect_db on
# Problem: nginx serving files from /home/user/public_html
setsebool -P httpd_enable_homedirs on
# Problem: SELinux blocks SMTP on custom port
# (Better fix: add port to correct type)
semanage port -a -t smtp_port_t -p tcp 2525

Terminal window
# ── List port contexts ────────────────────────────────────
semanage port -l # All ports
semanage port -l | grep http # HTTP ports
# ── Allow service on custom port ─────────────────────────
# Example: nginx on port 8080 (already allowed: http_port_t includes 8080)
# Example: nginx on port 9000 (not in http_port_t by default)
semanage port -a -t http_port_t -p tcp 9000
semanage port -l | grep 9000 # Verify
# ── Delete a port from a type ─────────────────────────────
semanage port -d -t http_port_t -p tcp 9000
# ── Modify existing port type ─────────────────────────────
semanage port -m -t http_port_t -p tcp 9000

Terminal window
# Method 1: audit2allow (recommended for simple cases)
# Generate policy from denials
ausearch -m avc -ts today | audit2allow -M my_custom_policy
semodule -i my_custom_policy.pp
# Method 2: Write a .te file manually
cat > myapp.te << 'EOF'
module myapp 1.0;
require {
type httpd_t;
type myapp_exec_t;
type myapp_var_t;
class file { read write execute open };
class dir { read search };
}
# Allow httpd to execute myapp binary
allow httpd_t myapp_exec_t:file { read execute open };
# Allow httpd to read/write myapp data directory
allow httpd_t myapp_var_t:dir { read search };
allow httpd_t myapp_var_t:file { read write open };
EOF
# Compile and install
checkmodule -M -m -o myapp.mod myapp.te
semodule_package -o myapp.pp -m myapp.mod
semodule -i myapp.pp
# Verify module loaded
semodule -l | grep myapp

SELinux Troubleshooting Decision Tree
──────────────────────────────────────
Application failing?
Check: getenforce
├── Disabled → SELinux not the issue
└── Enforcing/Permissive
setenforce 0 (Permissive mode)
Retry the failing operation
├── Still fails → NOT a SELinux issue
│ Check file permissions, service config
└── Now works → SELinux is blocking it
ausearch -m avc -ts today
sealert -a /var/log/audit/audit.log
Is there a boolean that fixes it?
getsebool -a | grep <service>
├── Yes → setsebool -P boolean_name on
└── No → Is it a file context issue?
├── Yes → semanage fcontext + restorecon
└── No → audit2allow to create policy
REVIEW POLICY before applying!
setenforce 1 (back to Enforcing)
Verify fix works in Enforcing mode
Terminal window
# Troubleshooting one-liner:
# 1. Check for recent denials
ausearch -m avc -ts recent 2>/dev/null | audit2why
# 2. Set permissive for one domain (not the whole system!)
# Only put specific process type in permissive:
semanage permissive -a httpd_t # httpd processes permissive only
semanage permissive -d httpd_t # Remove domain-specific permissive
# This is much better than setenforce 0 for the whole system!

Q1: What is the difference between Enforcing and Permissive mode in SELinux?

Answer: In Enforcing mode, SELinux actively blocks policy violations — processes cannot access resources the policy doesn’t allow, and denials are logged. This is the production mode. In Permissive mode, SELinux logs all policy violations but does NOT block them — everything runs as if SELinux were disabled. Permissive is used for debugging new policies to see what would be denied without actually breaking the application. Critical distinction: Permissive is NOT the same as Disabled — the policy is still loaded and evaluated, and the AVC cache is still populated. Never run production servers in Permissive mode.

Q2: An application works fine when SELinux is in Permissive mode but fails in Enforcing mode. How do you fix this without disabling SELinux?

Answer: (1) Check AVC denials: ausearch -m avc -ts today | audit2why or sealert -a /var/log/audit/audit.log — this gives human-readable explanations and fix suggestions. (2) Check if there’s an existing boolean: getsebool -a | grep <service_name>. If yes, setsebool -P boolean_name on. (3) If it’s a file context problem: ls -laZ <file> to check context, then semanage fcontext -a -t correct_type "/path(/.*)?" and restorecon -Rv /path. (4) If it’s a port problem: semanage port -a -t service_port_t -p tcp <port>. (5) If none of above: audit2allow -M mypolicy to generate a custom policy. Always review generated policies before applying.

Q3: What does this AVC denial mean? avc: denied { write } for pid=1234 comm="php-fpm" scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_t:s0 tclass=file

Answer: PHP-FPM (running with type httpd_t) is trying to write to a file with type var_t. The SELinux policy doesn’t allow httpd_t to write to var_t files. Most likely, a file in /var/ has the wrong SELinux type — it should be httpd_sys_rw_content_t or similar for web-writable files. Fix: check the file: ls -laZ /path/to/file, then relabel it: semanage fcontext -a -t httpd_sys_rw_content_t "/path/to/dir(/.*)?" and restorecon -Rv /path/to/dir.


ConceptKey Point
SELinux typeEvery process and file has a type label
Type EnforcementPolicy defines which types can access which
AVCAccess Vector Cache — logs denials
BooleanPre-built policy switches
audit2allowGenerates policy from AVC denials
restoreconRestores file contexts to policy defaults
semanageManages persistent SELinux settings

Chapter 14 (Security Fundamentals).


Advantages: Mandatory Access Control stops 0-day exploits, even root is restricted. Disadvantages: Extremely complex policy language, often disabled by frustrated admins.


  • Disabling SELinux entirely (setenforce 0 permanently) instead of fixing the policy.
  • Running audit2allow -M blindly without understanding the security implications.
  • Forgetting to restorecon after moving files.

SymptomCauseDiagnosisFix
Application gets permission denied but standard permissions are correctSELinux blocking accessgrep AVC /var/log/audit/audit.logUse restorecon or semanage to set correct context, or set an SELinux boolean

Objective: Manage SELinux contexts and booleans.

Terminal window
# 1. Check SELinux status
sestatus
# 2. View file contexts
ls -Z /var/www/html/
# 3. View process contexts
ps -eZ | grep nginx
# 4. View and change a boolean
getsebool -a | grep httpd_can_network_connect
setsebool -P httpd_can_network_connect 1

  1. Move a web page from your home directory to /var/www/html. Why does the web server get permission denied? Fix it using restorecon.
  2. Use audit2allow to generate a custom policy module for a blocked action, review the generated .te file, and load it.

  • SELinux is a MAC system that labels files and processes.
  • Even root is constrained by SELinux policies.
  • Contexts have User:Role:Type:Level. Type enforcement (TE) is the most common.
  • Booleans allow toggling policy rules at runtime without recompiling.

  • SELinux Coloring Book
  • Red Hat SELinux Guide


Last Updated: July 2026