SELinux stands for Security-Enhanced Linux.
It is a Linux security system that adds an extra layer of access control on top of normal Linux file permissions.
Traditional Linux permissions are based on users, groups, and file modes such as:
other
read
SELinux goes further. It controls what processes are allowed to do, even if normal Linux permissions would allow the action.
The main idea is:
Normal permissions ask:
Does this user have access?
SELinux asks:
Is this process type allowed to access this object type?
SELinux is especially useful on servers because it limits damage if a service is compromised.
For example, if a web server is hacked, SELinux can prevent the web server process from reading private user files, modifying system files, or accessing resources outside its allowed policy.
Linux normally uses Discretionary Access Control, or DAC.
SELinux adds Mandatory Access Control, or MAC.
With DAC, a user who owns a file can usually decide who can read or write it.
With MAC, the system policy can still block access, even if the file owner gives permission.
Example:
This is one of the most important SELinux concepts.
SELinux does not replace normal permissions. Both must allow the action.
Access allowed only if:
DAC allows it
AND
SELinux allows it
SELinux labels processes and resources.
These labels are called security contexts.
When a process tries to access a file, socket, port, or other object, SELinux compares the process context with the object context and checks the loaded policy.
+-------------------+
| Process |
| Example: httpd |
| Context: httpd_t |
+---------+---------+
|
| wants to read
v
+-------------------+
| File |
| /var/www/html |
| Context: |
| httpd_sys_content_t
+---------+---------+
|
v
+-------------------+
| SELinux Policy |
| Is httpd_t allowed|
| to read |
| httpd_sys_content_t?
+---------+---------+
|
v
+-------------------+
| Allow or Deny |
+-------------------+
The key decision is usually based on the type.
For processes, the type is often called a domain.
For files and other objects, the type is usually just called a type.
SELinux policy defines what domains can do to what types.
A simplified SELinux architecture looks like this:
+---------------------------------------------------+
| User Process |
| Example: web server, SSH daemon, app |
+----------------------+----------------------------+
|
| 1. Access request
| read, write, execute,
| bind port, connect socket
v
+---------------------------------------------------+
| SELinux Security Server |
| Policy Decision Point |
| |
| Checks loaded policy and security contexts |
+----------------------+----------------------------+
|
| 2. Allow or deny decision
v
+---------------------------------------------------+
| Object Manager and Access Vector Cache |
| Policy Enforcement Point |
| |
| Enforces decision and caches previous results |
+----------------------+----------------------------+
|
| 3. Operation allowed or blocked
v
+---------------------------------------------------+
| Linux Kernel |
+---------------------------------------------------+
The important parts are:
| Component | Description |
| Subject | The process trying to perform an action. |
| Object | The resource being accessed. |
| Policy | The SELinux rules that define permitted actions. |
| Decision | The result of policy evaluation: allow or deny. |
| AVC (Access Vector Cache) | Caches SELinux access decisions and records access denials for logging. |
A subject is usually a process.
Examples:
An object is something the process tries to access.
Examples:
Example:
Subject:
httpd process running as httpd_t
Object:
index.html labeled httpd_sys_content_t
Action:
read
SELinux checks whether httpd_t is allowed to read httpd_sys_content_t.
Every SELinux-labeled process and object has a security context.
A context usually has four parts:
SELinux user : role : type : level
Example file context:
system_u:object_r:httpd_sys_content_t:s0
Breakdown:
| Component | Description |
| system_u | SELinux user |
| object_r | SELinux role for objects |
| httpd_sys_content_t | SELinux type (domain/type label) |
| s0 | Sensitivity level (MLS/MCS security level) |
The type is usually the most important part for everyday troubleshooting.
In most common SELinux troubleshooting:
focus on the type.
To view process contexts:
ps -eZ | grep sshd
Example output:
system_u:system_r:sshd_t:s0-s0:c0.c1023 1234 ? 00:00:00 sshd
Interpretation:
To view file contexts:
ls -Z /var/www/html/index.html
Example output:
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
Interpretation:
SELinux does not only care about file paths.
It cares strongly about labels.
For example, two files may have the same normal permissions:
-rw-r--r-- index.html
-rw-r--r-- secret.txt
But they may have different SELinux types:
index.html httpd_sys_content_t
secret.txt user_home_t
A web server may be allowed to read httpd_sys_content_t but denied access to user_home_t.
This means a file can be readable by Unix permissions but still blocked by SELinux.
SELinux has three major modes:
In enforcing mode, SELinux policy is active.
Unauthorized actions are blocked and logged.
SELinux policy says deny
|
v
Action is blocked
|
v
Denial is logged
This is the normal recommended mode for production systems.
In permissive mode, SELinux does not block actions, but it still logs what would have been denied.
SELinux policy says deny
|
v
Action is allowed anyway
|
v
Denial is logged
Permissive mode is useful for troubleshooting because it lets you see SELinux problems without breaking the application.
It should usually be temporary.
In disabled mode, SELinux is turned off.
No SELinux policy is enforced, and SELinux denials are not logged.
This is usually not recommended.
A key difference:
SELinux is active but not enforcing.
Disabled:
Switching from disabled back to enabled may require relabeling the filesystem.
Use:
getenforce
Example outputs:
Enforcing
or:
Permissive
or:
Disabled
For more detail:
sestatus
Example output:
SELinux status: enabled
Current mode: enforcing
Mode from config file: enforcing
Policy name: targeted
Interpretation:
To switch to permissive mode temporarily:
sudo setenforce 0
or:
sudo setenforce Permissive
To switch back to enforcing:
sudo setenforce 1
or:
sudo setenforce Enforcing
Verify:
getenforce
Important:
The main configuration file is:
/etc/selinux/config
Open it:
sudo nano /etc/selinux/config
Example:
SELINUX=enforcing
SELINUXTYPE=targeted
Possible SELINUX values:
A reboot is usually required for this file to fully take effect.
Use caution before setting SELinux to disabled.
SELinux policy defines the rules.
Common policy types include:
For most systems, the common policy is:
targeted
Targeted policy confines selected services while allowing many normal user processes to run unconfined.
This is the default on many SELinux-enabled distributions.
Examples of commonly confined services:
Targeted policy is a good balance between security and usability.
Strict policy applies SELinux controls much more broadly.
It can provide stronger confinement but requires much more planning and administration.
It is more likely to break normal workflows if not carefully configured.
For most learners and administrators, targeted policy is the practical starting point.
Useful commands include:
Some commands may require packages such as:
Package names vary by distribution.
View file context:
ls -Z /path/to/file
View directory context:
ls -Zd /path/to/directory
Example:
ls -Zd /var/www/html
Output:
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 /var/www/html
View process context:
ps -eZ | grep httpd
or:
ps -eZ | grep nginx
Example:
system_u:system_r:httpd_t:s0 2345 ? 00:00:00 nginx
Interpretation:
The web service process is running in the httpd_t domain.
Some systems label Apache and Nginx web server processes using the same SELinux domain family, such as httpd_t.
Always check your actual system with ps -eZ.
chconThe chcon command changes a file or directory context directly.
Example:
sudo chcon -t httpd_sys_content_t /srv/mywebsite/index.html
This changes only the type.
To apply recursively:
sudo chcon -R -t httpd_sys_content_t /srv/mywebsite
Important:
Use chcon for quick tests, not usually for permanent configuration.
restoreconThe restorecon command restores files to their expected default SELinux context based on policy rules.
Example:
sudo restorecon -Rv /var/www/html
Options:
Example output:
Relabeled /var/www/html/index.html from unconfined_u:object_r:default_t:s0 to system_u:object_r:httpd_sys_content_t:s0
Interpretation:
semanage fcontextFor custom directories, use semanage fcontext.
Example:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/mywebsite(/.*)?"
Then apply it:
sudo restorecon -Rv /srv/mywebsite
This is the correct persistent approach for custom paths.
chcon vs semanage fcontextchcon: - quick manual label change - useful for testing - not persistent against relabeling
semanage fcontext: - persistent label rule - survives restorecon - best for permanent custom paths
A good rule:
SELinux booleans are policy switches.
They let administrators adjust policy behavior without writing a new policy module.
List all booleans:
getsebool -a
Filter for web server booleans:
getsebool -a | grep httpd
Example output:
httpd_can_network_connect --> off
httpd_enable_homedirs --> off
httpd_read_user_content --> off
Interpretation:
Example:
sudo setsebool httpd_can_network_connect on
This applies until reboot.
Use -P:
sudo setsebool -P httpd_can_network_connect on
The -P option makes it persistent.
Common booleans:
Only enable booleans that are actually needed.
SELinux can also control which services may bind to which network ports.
For example, a web server is normally allowed to bind to ports labeled as HTTP ports.
View HTTP port mappings:
sudo semanage port -l | grep http_port_t
Example output:
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443
Interpretation:
Add a custom HTTP port:
sudo semanage port -a -t http_port_t -p tcp 8081
If the port already exists under another type, modify instead:
sudo semanage port -m -t http_port_t -p tcp 8081
SELinux denials are usually logged as AVC messages.
AVC means Access Vector Cache.
Main log location:
/var/log/audit/audit.log
Search for SELinux denials from today:
sudo ausearch -m avc -ts today
Search by command name:
sudo ausearch -m avc -c nginx
or:
sudo ausearch -m avc -c httpd
Example AVC denial:
type=AVC msg=audit(1609459200.123:456): avc: denied { read } for pid=1234 comm="nginx" name="index.html" dev="sda1" ino=56789 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file
Breakdown:
Interpretation:
audit2whyUse:
sudo ausearch -m avc -ts today | audit2why
Example output:
type=AVC ... denied { read } ...
Was caused by:
Missing type enforcement rule.
Interpretation:
sealertIf setroubleshoot is installed:
sudo sealert -a /var/log/audit/audit.log
sealert can provide human-readable explanations and suggested fixes.
Use it as guidance, but do not blindly apply every suggestion.
audit2allowAs a last resort, you can generate a custom policy module from audit logs.
Example:
sudo grep nginx /var/log/audit/audit.log | audit2allow -M nginx_custom
sudo semodule -i nginx_custom.pp
Important warning:
A custom policy should be reviewed carefully.
When something fails and SELinux may be involved:
Useful commands:
getenforce
sestatus
ls -Z path
ps -eZ | grep service
sudo ausearch -m avc -ts recent
sudo restorecon -Rv path
sudo semanage fcontext -a -t TYPE "PATH_REGEX"
sudo setsebool -P boolean_name on
Learn how to check and temporarily change SELinux enforcement.
getenforce
Example output:
Enforcing
sudo setenforce 0
getenforce
Example output:
Permissive
Interpretation:
sudo setenforce 1
getenforce
Expected output:
Enforcing
Show how a service can fail because files have the wrong SELinux label.
A web server is configured to serve files from:
/srv/mywebsite
The file permissions are correct, but the site returns:
403 Forbidden
or the service logs show access denied.
Create test content:
sudo mkdir -p /srv/mywebsite
echo "Hello from SELinux test" | sudo tee /srv/mywebsite/index.html
Check context:
ls -Z /srv/mywebsite/index.html
Example output:
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 /srv/mywebsite/index.html
Interpretation:
sudo ausearch -m avc -ts recent -c nginx
or:
sudo ausearch -m avc -ts recent -c httpd
Example output:
type=AVC msg=audit(...): avc: denied { open } for pid=1234 comm="nginx" path="/srv/mywebsite/index.html" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file
Interpretation:
chconsudo chcon -R -t httpd_sys_content_t /srv/mywebsite
ls -Z /srv/mywebsite/index.html
Expected context:
unconfined_u:object_r:httpd_sys_content_t:s0
semanage fcontextsudo semanage fcontext -a -t httpd_sys_content_t "/srv/mywebsite(/.*)?"
sudo restorecon -Rv /srv/mywebsite
Example output:
Relabeled /srv/mywebsite/index.html from unconfined_u:object_r:default_t:s0 to system_u:object_r:httpd_sys_content_t:s0
chcon Is TemporaryShow why chcon is not the best permanent fix.
sudo chcon -R -t httpd_sys_content_t /srv/mywebsite
ls -Z /srv/mywebsite/index.html
Example output:
unconfined_u:object_r:httpd_sys_content_t:s0 index.html
Now run:
sudo restorecon -Rv /srv/mywebsite
If no persistent semanage fcontext rule exists, example output may show:
Relabeled /srv/mywebsite/index.html from unconfined_u:object_r:httpd_sys_content_t:s0 to unconfined_u:object_r:default_t:s0
Interpretation:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/mywebsite(/.*)?"
sudo restorecon -Rv /srv/mywebsite
Show how SELinux booleans control optional service behavior.
A web application needs to connect from the web server to a backend service or database over the network.
The application fails even though networking and firewall rules are correct.
getsebool httpd_can_network_connect
Example output:
httpd_can_network_connect --> off
sudo ausearch -m avc -ts recent | grep name_connect
Example output:
avc: denied { name_connect } for pid=2222 comm="nginx" dest=5432 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:postgresql_port_t:s0 tclass=tcp_socket
Interpretation:
Temporarily:
sudo setsebool httpd_can_network_connect on
Persistently:
sudo setsebool -P httpd_can_network_connect on
Verify:
getsebool httpd_can_network_connect
Expected output:
httpd_can_network_connect --> on
Booleans are safer than custom policy when the policy already provides a supported switch.
Show how SELinux controls network port usage.
A web server is configured to listen on port:
8081
The service fails to start.
sudo ausearch -m avc -ts recent | grep name_bind
Example output:
avc: denied { name_bind } for pid=3333 comm="nginx" src=8081 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket
Interpretation:
The web server tried to bind to TCP port 8081.
SELinux does not currently label that port as an allowed HTTP port.
sudo semanage port -l | grep http_port_t
Example output:
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443
Add port 8081 as an HTTP port:
sudo semanage port -a -t http_port_t -p tcp 8081
Verify:
sudo semanage port -l | grep http_port_t
Expected output includes:
8081
Restart service:
sudo systemctl restart nginx
or:
sudo systemctl restart httpd
Show how booleans can allow or deny service access to user home directories.
FTP login works, but users cannot access files in their home directories.
getsebool ftp_home_dir
Example output:
ftp_home_dir --> off
sudo ausearch -m avc -ts recent -c vsftpd
Example output:
avc: denied { read } for pid=4444 comm="vsftpd" name="notes.txt" scontext=system_u:system_r:ftpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file
Interpretation:
sudo setsebool -P ftp_home_dir on
Verify:
getsebool ftp_home_dir
Expected output:
ftp_home_dir --> on
SELinux booleans allow common optional behaviors without writing custom policy.
Understand the difference between permissive and enforcing behavior.
sudo setenforce 0
getenforce
Expected output:
Permissive
For example, use a web content file with the wrong label:
sudo chcon -R -t default_t /srv/mywebsite
Access it through the web server.
sudo ausearch -m avc -ts recent
Example output:
avc: denied { read } for pid=1234 comm="nginx" path="/srv/mywebsite/index.html" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=1
Interpretation:
sudo setenforce 1
Show the standard SELinux troubleshooting pattern.
A service fails with:
Permission denied
But normal permissions look correct:
ls -l /srv/mywebsite/index.html
Example output:
-rw-r--r--. root root 25 Jun 1 12:00 /srv/mywebsite/index.html
getenforce
Example:
Enforcing
ls -Z /srv/mywebsite/index.html
Example:
unconfined_u:object_r:default_t:s0 index.html
ps -eZ | grep nginx
Example:
system_u:system_r:httpd_t:s0 1234 ? 00:00:00 nginx
sudo ausearch -m avc -ts recent -c nginx
Example:
avc: denied { read } for comm="nginx" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file
Interpretation:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/mywebsite(/.*)?"
sudo restorecon -Rv /srv/mywebsite
audit2why Before Making ChangesUse audit tools to understand denials before fixing them.
sudo ausearch -m avc -ts recent | audit2why
Example output:
type=AVC ... denied { read } ...
Was caused by:
Missing type enforcement rule.
Possible mismatch between source and target contexts.
Interpretation:
Understand how custom policy modules are created and why they require caution.
Suppose a custom application myapp is denied access and there is no existing boolean or label fix.
Collect relevant denials:
sudo ausearch -m avc -ts recent -c myapp
Generate a policy module:
sudo ausearch -m avc -ts recent -c myapp | audit2allow -M myapp_local
This creates files such as:
Inspect the .te file before installing:
cat myapp_local.te
Example rule:
allow myapp_t var_log_t:file read;
Interpretation:
Install only after review:
sudo semodule -i myapp_local.pp
Perform a complete SELinux troubleshooting workflow.
A web server returns:
403 Forbidden
for content under:
/srv/mywebsite
ls -ld /srv/mywebsite
ls -l /srv/mywebsite/index.html
Example:
drwxr-xr-x. root root /srv/mywebsite
-rw-r--r--. root root index.html
Interpretation:
getenforce
Example:
Enforcing
ls -Zd /srv/mywebsite
ls -Z /srv/mywebsite/index.html
Example:
unconfined_u:object_r:default_t:s0 /srv/mywebsite
unconfined_u:object_r:default_t:s0 /srv/mywebsite/index.html
sudo ausearch -m avc -ts recent -c nginx
Example:
avc: denied { getattr open read } for comm="nginx" path="/srv/mywebsite/index.html" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/mywebsite(/.*)?"
sudo restorecon -Rv /srv/mywebsite
ls -Z /srv/mywebsite/index.html
Expected:
system_u:object_r:httpd_sys_content_t:s0 index.html
Restart or reload the web service if needed:
sudo systemctl reload nginx
or:
sudo systemctl reload httpd
Then test the page again.
sudo ausearch -m avc -ts recent -c nginx
If no new denial appears, the SELinux labeling issue is resolved.
Understand why relabeling may be needed after SELinux has been disabled.
SELinux was disabled for a while, then re-enabled. Some files may not have correct labels.
Create the autorelabel marker:
sudo touch /.autorelabel
sudo reboot
During boot, the system relabels files according to SELinux policy.
Interpretation:
Symptoms:
Check:
ls -Z path
sudo ausearch -m avc -ts recent
Fix:
sudo restorecon -Rv path
or for custom paths:
sudo semanage fcontext -a -t correct_type "path_regex"
sudo restorecon -Rv path
Symptoms:
Check booleans:
getsebool -a | grep service_name
Fix:
sudo setsebool -P boolean_name on
Symptoms:
Check:
sudo semanage port -l | grep service_port_type
Fix:
sudo semanage port -a -t correct_port_type -p tcp PORT
Possible causes:
Check:
getenforce
sudo systemctl status auditd
sudo journalctl -t setroubleshoot
sudo dmesg | grep -i avc
Better approach:
Disabling SELinux removes a major security layer.
Mode and status:
getenforce
sestatus
sudo setenforce 0
sudo setenforce 1
View contexts:
ls -Z file
ls -Zd directory
ps -eZ
ps -eZ | grep service
Fix file contexts:
sudo chcon -t TYPE file
sudo restorecon -Rv path
sudo semanage fcontext -a -t TYPE "path_regex"
sudo restorecon -Rv path
Booleans:
getsebool -a
getsebool -a | grep httpd
sudo setsebool boolean_name on
sudo setsebool -P boolean_name on
Ports:
sudo semanage port -l
sudo semanage port -l | grep http_port_t
sudo semanage port -a -t http_port_t -p tcp 8081
sudo semanage port -m -t http_port_t -p tcp 8081
Audit and troubleshooting:
sudo ausearch -m avc -ts today
sudo ausearch -m avc -ts recent
sudo ausearch -m avc -c nginx
sudo ausearch -m avc -ts recent | audit2why
sudo sealert -a /var/log/audit/audit.log
Custom policy:
sudo ausearch -m avc -ts recent -c myapp | audit2allow -M myapp_local
cat myapp_local.te
sudo semodule -i myapp_local.pp
Relabeling:
sudo touch /.autorelabel
sudo reboot
SELinux labs should be done carefully.
getenforce and sestatus to check SELinux mode, policy type, and status.ls -Z and ps -eZ to compare file contexts and process contexts./srv/mywebsite, add an index.html file, and inspect its default SELinux context.chcon, then run restorecon and observe whether the context changes back.semanage fcontext, apply it with restorecon, and verify the result with ls -Z.ausearch -m avc -ts recent.audit2why to explain an SELinux denial.getsebool -a | grep httpd, then explain what httpd_can_network_connect does.semanage port, then verify it appears under http_port_t.