Linux Administration Lesson 30 – SELinux Basics | Dataplexa
Section III — Networking, Security & Storage

SELinux Basics

In this lesson

Mandatory access control SELinux modes Security contexts Resolving AVC denials Booleans and file contexts

SELinux (Security-Enhanced Linux) is a mandatory access control system built into the Linux kernel that adds a second layer of permission checks on top of the standard Unix file permissions. Even if a process runs as root, SELinux can prevent it from accessing files, network ports, or other resources it has not been explicitly granted. Originally developed by the NSA and shipped as standard on RHEL, Rocky Linux, and Fedora, SELinux is one of the most powerful and most frequently misunderstood security features in Linux.

DAC vs MAC — Why SELinux Exists

Linux's traditional permission model is called DAC (Discretionary Access Control) — the owner of a file decides who can access it. The weakness of DAC is that a compromised process running as a privileged user inherits all of that user's permissions. SELinux implements MAC (Mandatory Access Control) — the kernel enforces a policy that applies regardless of who owns a resource. A compromised nginx process cannot read /etc/shadow even if it is running as root, because SELinux policy says nginx is not allowed to access shadow password files.

DAC — Discretionary (traditional Unix) Process runs as root Check: does the user own the file or have rwx permission? Root can read: ✓ /etc/shadow ✓ /var/log/nginx/access.log ✓ /home/alice/.ssh/id_ed25519 Compromised nginx as root = full system access MAC — Mandatory (SELinux) Process has SELinux label: httpd_t Check 1: DAC (user permissions) Check 2: MAC (SELinux policy for httpd_t) nginx (httpd_t) can read: ✓ /var/www/html/ (httpd_sys_content_t) ✗ /etc/shadow (shadow_t) — DENIED ✗ /home/alice/.ssh/ — DENIED Compromised nginx cannot escape its label

Fig 1 — DAC checks ownership; MAC adds a second check based on SELinux labels regardless of user identity

Analogy: DAC is like a building where your employee badge grants access to all floors your job title allows. If someone steals your badge, they get all your access. MAC is like adding a second check at every door — a fingerprint scanner that verifies you are specifically authorised for that room, independent of your badge. Even a stolen badge cannot open a door you are not biometrically registered for.

SELinux Modes

SELinux operates in one of three modes. The mode determines whether policy violations are enforced or only logged — a crucial distinction when troubleshooting applications that were not written with SELinux in mind.

Enforcing

Policy violations are blocked and logged. The intended production mode. All access denials generate AVC (Access Vector Cache) denial messages in the audit log.

Permissive

Violations are logged but not blocked. Useful for troubleshooting — the application runs unhindered while all policy violations are recorded for analysis.

Disabled

SELinux is completely off. No logging, no enforcement. Requires a reboot to change from disabled to any other mode — filesystem relabelling is triggered on next boot.

# Check current SELinux mode and policy
sestatus
getenforce

# Temporarily switch to permissive (survives until reboot)
sudo setenforce 0    # 0 = permissive
sudo setenforce 1    # 1 = enforcing

# Permanently change mode — edit /etc/selinux/config
sudo sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
# Options: enforcing | permissive | disabled
# Changes take effect on next reboot

# Check current mode in the config file
cat /etc/selinux/config | grep "^SELINUX="

# Check which SELinux policy is loaded
cat /etc/selinux/config | grep "^SELINUXTYPE="
# Common policies: targeted (most systems), mls (multi-level security)

What just happened? sestatus confirmed SELinux is running in enforcing mode with the targeted policy. The targeted policy applies SELinux controls to specific high-risk daemons (nginx, postgresql, sshd, etc.) while allowing everything else to run unconfined — a practical balance between security and compatibility. The "Mode from config file" and "Current mode" both showing "enforcing" means no one has temporarily overridden the mode with setenforce.

Security Contexts — SELinux Labels

Every process, file, port, and user on a SELinux system carries a security context — a label with four fields that determines what the object can do and be accessed by. The most important field for day-to-day administration is the type field. SELinux policy rules are mostly expressed as "processes of type X may access resources of type Y."

system_u : object_r : httpd_sys_content_t : s0 User system_u SELinux user identity Role object_r for files; system_r for daemons Type ← most important httpd_sys_content_t determines access rules in policy Level (MLS) s0 sensitivity; s0 = unclassified Format: user:role:type:level · In targeted policy, type is what you manage day-to-day

Fig 2 — Security context anatomy: four colon-separated fields; type is the operationally significant field

# Show SELinux context of files — add -Z flag to standard commands
ls -laZ /var/www/html/
ls -Z /etc/shadow

# Show SELinux context of running processes
ps -eZ | grep nginx
ps auxZ | grep httpd

# Show SELinux context of network ports
sudo semanage port -l | grep http

# Show your current login context
id -Z

# Show SELinux context of a specific file
stat --printf="%C\n" /var/www/html/index.html

# View the full context of all files in a directory
ls -laZ /etc/ssh/

What just happened? The labels tell the full SELinux story: nginx processes run with type httpd_t. The web content files in /var/www/html/ have type httpd_sys_content_t. SELinux policy allows httpd_t to read httpd_sys_content_t files. The shadow file has type shadow_t — policy does not grant httpd_t access to shadow_t, so even root-owned nginx cannot read it.

Reading and Resolving AVC Denials

When SELinux blocks an action, it writes an AVC denial (Access Vector Cache denial) to the audit log at /var/log/audit/audit.log. These messages contain everything needed to understand what was blocked and how to fix it. The audit2why and audit2allow tools translate raw AVC messages into human-readable explanations and policy suggestions.

# View recent SELinux denials from the audit log
sudo ausearch -m AVC -ts recent
sudo ausearch -m AVC,USER_AVC -ts today

# View denials in the system journal
sudo journalctl | grep "SELinux is preventing"

# The setroubleshoot-server package provides the best human-readable explanations
sudo dnf install setroubleshoot-server -y
sudo sealert -a /var/log/audit/audit.log

# Use audit2why to explain a specific denial
sudo ausearch -m AVC -ts recent | audit2why

# Generate a policy module to allow a specific denial (use with caution)
sudo ausearch -m AVC -ts recent | audit2allow -M mypolicy
sudo semodule -i mypolicy.pp

# Check if SELinux is causing an application failure
# Step 1: switch to permissive temporarily
sudo setenforce 0
# Step 2: reproduce the error
# Step 3: if it works now, SELinux was the cause — check audit.log
# Step 4: return to enforcing and fix properly
sudo setenforce 1

What just happened? audit2why translated the raw AVC denial into a clear diagnosis: nginx (running as httpd_t) tried to read a config file from /root/, which has the label admin_home_t — a type nginx is not allowed to read. The solution is not to disable SELinux but to fix the file's location or label. Moving the config to /etc/nginx/ or relabelling it with chcon is the correct fix.

Managing File Contexts and Booleans

The two most common reasons SELinux blocks legitimate applications are: wrong file context labels (the file is in the wrong location or was copied without preserving the label), and a disabled Boolean (a named policy toggle that controls optional behaviour). Both have safe, targeted fixes that do not require disabling SELinux.

File Context Labels

Files copied from one location to another inherit the source directory's label, not the destination's expected label. Use restorecon to reset to the correct type for the destination path.

# Fix wrong label on a file restorecon -v /var/www/html/index.html # Fix entire directory tree restorecon -Rv /var/www/html/ # Show what label a path should have matchpathcon /var/www/html/index.html

SELinux Booleans

Booleans toggle optional policy rules on or off. Common examples: allow nginx to connect to the network, allow Apache to write to its home dir, allow FTP to access home directories.

# List all booleans getsebool -a | grep httpd # Toggle a boolean (runtime — lost on reboot) setsebool httpd_can_network_connect on # Toggle permanently setsebool -P httpd_can_network_connect on
# ── File context management ───────────────────────────────────────

# View the SELinux file context database — what type should /var/www/* have?
sudo semanage fcontext -l | grep "/var/www"

# Add a custom file context rule (for a non-standard web root)
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/myapp(/.*)?"
sudo restorecon -Rv /srv/myapp/

# Change a file's context temporarily (survives until restorecon is run)
sudo chcon -t httpd_sys_content_t /srv/myapp/index.html

# Reset a file to its correct default context
sudo restorecon -v /srv/myapp/index.html

# ── Boolean management ────────────────────────────────────────────

# Search booleans related to nginx/httpd
getsebool -a | grep httpd | grep -v "off$"   # show only enabled booleans

# Common booleans for web servers
getsebool httpd_can_network_connect          # allow nginx/Apache to make outbound connections
getsebool httpd_can_network_connect_db       # allow httpd to connect to databases
getsebool httpd_enable_homedirs              # allow httpd to serve from home directories

# Enable nginx to connect to a backend application server (e.g. Node.js, uWSGI)
sudo setsebool -P httpd_can_network_connect on

What just happened? httpd_can_network_connect was off — explaining why nginx could not proxy requests to a backend service on another port. Setting it to on with -P made the change permanent. The semanage fcontext output confirmed the expected label for /var/www/html — any file placed there should have httpd_sys_content_t.

SELinux Administration — Common Scenarios

The following patterns cover the most frequent SELinux administration scenarios encountered in production. Each has a correct fix that maintains security — and an incorrect shortcut that administrators should recognise and avoid.

Wrong file type
File copied to web root but nginx cannot read it

Wrong fix: setenforce 0. Right fix: restorecon -Rv /var/www/html/ to restore the correct label for that path.

Non-standard port
Nginx on port 8443 denied — port not in http_port_t

Right fix: Add the port to the httpd port type: sudo semanage port -a -t http_port_t -p tcp 8443

Custom web root
Application files in /srv/app/ instead of /var/www/html/

Right fix: Register the new path: semanage fcontext -a -t httpd_sys_content_t "/srv/app(/.*)?" then restorecon -Rv /srv/app/

Proxy blocked
Nginx reverse proxy to Node.js backend getting 502

Right fix: Enable the network connect boolean: setsebool -P httpd_can_network_connect on. This is the most common SELinux issue on RHEL web servers.

# Complete workflow — nginx on a new Rocky Linux server serving /srv/myapp/

# 1. Register the correct context for the non-standard web root
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/myapp(/.*)?"

# 2. Apply labels
sudo restorecon -Rv /srv/myapp/

# 3. If nginx needs to proxy to a backend service
sudo setsebool -P httpd_can_network_connect on

# 4. If nginx needs to connect to a database (PHP apps, etc.)
sudo setsebool -P httpd_can_network_connect_db on

# 5. Verify no remaining denials
sudo ausearch -m AVC -ts today | audit2why

# Full audit: relabel the entire filesystem (useful after major changes — slow!)
# sudo touch /.autorelabel && sudo reboot

Never Set SELinux to Disabled to "Fix" Application Problems

Disabling SELinux — changing SELINUX=enforcing to SELINUX=disabled — strips away a substantial layer of kernel-level protection and requires a full filesystem relabelling on re-enablement. It is never the correct solution for a specific application problem. The correct approach is always: read the AVC denial, use audit2why to understand it, and fix the file context or enable the relevant boolean. Temporarily switching to permissive with setenforce 0 is acceptable during troubleshooting, but must always be followed by returning to enforcing mode with the proper fix applied.

Lesson Checklist

I understand why MAC is stronger than DAC — a compromised process cannot exceed its SELinux type label regardless of its Unix user identity
I use getenforce / sestatus to check the current mode, and I only use setenforce 0 temporarily during troubleshooting — never as a permanent fix
I read file and process contexts with ls -Z and ps -eZ, and I understand the four context fields — especially the type field
I use ausearch -m AVC | audit2why to diagnose denials, and I fix them with restorecon, semanage fcontext, or setsebool -P
I know the most common RHEL web server fix: setsebool -P httpd_can_network_connect on when nginx cannot proxy to a backend

Teacher's Note

The most important habit to develop with SELinux is to always run ausearch -m AVC -ts recent | audit2why before reaching for setenforce 0. In 95% of cases the denial message will point to a missing boolean or a wrong file context — both fixable in under a minute. The remaining 5% are genuinely novel access patterns that warrant a custom policy module via audit2allow. Disabling SELinux entirely is never in either category.

Practice Questions

1. You deploy a new application on a Rocky Linux 9 server. The application files are in /opt/myapp/public/ and nginx is configured to serve from that directory. Nginx returns 403 Forbidden. ls -Z /opt/myapp/public/ shows the type is default_t. Walk through the complete fix.

2. A colleague suggests disabling SELinux to fix an nginx proxy issue on a production RHEL server, arguing "it's just one server and it's inside the firewall." Give a technical counter-argument explaining what SELinux protects against that the firewall does not, and describe the two-minute correct fix for the most common nginx proxy problem.

3. Explain the difference between using chcon to change a file's SELinux context and using semanage fcontext followed by restorecon. Which should you use in production and why?

Lesson Quiz

1. An nginx process is running as root and tries to read /etc/shadow. SELinux is in enforcing mode. What happens?

2. What does SELinux permissive mode do differently from enforcing mode?

3. A RHEL server running nginx as a reverse proxy to a Node.js app on port 3000 returns 502 Bad Gateway. curl localhost:3000 works. What is the most likely SELinux cause and fix?

Up Next

Lesson 31 — Linux Security Best Practices

Hardening Linux servers against attack — audit tools, intrusion detection, kernel parameters, and security frameworks