🏷️ SELinux Contexts

SELinux Series: Part 1 — Basics  |  Part 2 — Contexts  |  Part 3 — Booleans  |  Part 4 — Troubleshooting

What are SELinux Contexts?

Every file, process, port, and user on an SELinux system carries a security context — a label that SELinux uses to make access control decisions. When a process tries to access a file, SELinux compares their contexts against the loaded policy. If the policy does not explicitly allow the access, it is denied — regardless of Unix permissions.

Understanding contexts is the key to understanding SELinux. Most SELinux problems come down to a mismatch between a process's context and a file's context. Fixing the context fixes the problem.

Anatomy of a Security Context

A context is four fields separated by colons:

system_u:system_r:httpd_t:s0
system_u
SELinux User

Maps to a set of allowed roles. Not the same as a Linux user.

system_r
Role

Groups types a user or process is allowed to enter.

httpd_t
Type ⭐ Most Important

The primary enforcement label. Policy rules are written in terms of types.

s0
Level (MLS)

Sensitivity level. Always s0 in targeted policy.

The Type is what matters most. In the targeted policy, nearly all enforcement decisions are based on the type field alone. When you see httpd_t that means "the Apache web server process." When you see httpd_sys_content_t that means "content Apache is allowed to serve." The policy says: httpd_t may read httpd_sys_content_t.

Examples

1
View File Contexts — ls -Z

The -Z flag added to ls shows SELinux context:

# Show context for files in current directory
ls -Z

# Show context for a specific file
ls -Z /var/www/html/index.html

# Long listing with context
ls -lZ /var/www/html/

# Show context for a directory
ls -dZ /var/www/html/

# Combine with other options
ls -laZ /etc/httpd/conf/
ls -lZ /var/www/html/ output:
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.html -rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 style.css -rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 upload.php
⚠️ Spot the problem: upload.php has context admin_home_t — it was likely copied from a home directory. Apache (httpd_t) cannot read admin_home_t files. It needs to be httpd_sys_content_t. Fix with restorecon or chcon.
2
View Process Contexts — ps -Z

The -Z flag also works with ps to show process contexts:

# Show context for all processes
ps -eZ

# Show context for a specific process
ps -eZ | grep httpd
ps -eZ | grep sshd

# Full listing with context
ps -eZf | grep nginx

# Show your current shell context
ps -Z $$
ps -eZ | grep httpd output:
system_u:system_r:httpd_t:s0 1234 ? 00:00:02 httpd system_u:system_r:httpd_t:s0 1235 ? 00:00:00 httpd system_u:system_r:httpd_t:s0 1236 ? 00:00:00 httpd
💡 id -Z for your context: To see the SELinux context of your current session use id -Z:
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Logged-in users typically run as unconfined_t — they are not confined by SELinux policy in the targeted model.
3
Restore Default Context — restorecon

restorecon resets file contexts back to what the policy says they should be. This is the correct fix for most context problems:

# Restore context on a single file
sudo restorecon /var/www/html/upload.php

# Restore recursively on a directory
sudo restorecon -R /var/www/html/

# Verbose — show what changed
sudo restorecon -Rv /var/www/html/

# Dry run — show what WOULD change without changing it
sudo restorecon -Rvn /var/www/html/

# Restore after moving files from home directory to web root
sudo cp ~/myapp/* /var/www/html/myapp/
sudo restorecon -Rv /var/www/html/myapp/
restorecon -Rv output:
Relabeled /var/www/html/upload.php from unconfined_u:object_r:admin_home_t:s0 to system_u:object_r:httpd_sys_content_t:s0
💡 restorecon is almost always the right fix. When a file has the wrong context, don't use chcon — use restorecon. It sets the context to what the policy defines as correct for that path, and the fix survives a relabel. chcon changes are temporary and will be wiped by a relabel.
4
Change Context Manually — chcon

chcon changes a file's context directly. Use it for testing and troubleshooting — not as a permanent fix:

# Change the type of a file
sudo chcon -t httpd_sys_content_t /var/www/html/upload.php

# Change type recursively
sudo chcon -R -t httpd_sys_content_t /var/www/html/myapp/

# Copy context from a reference file
sudo chcon --reference=/var/www/html/index.html /var/www/html/upload.php

# Change user field
sudo chcon -u system_u /var/www/html/upload.php

# Change full context at once
sudo chcon -u system_u -r object_r -t httpd_sys_content_t /var/www/html/upload.php
⚠️ chcon changes are not persistent. A filesystem relabel (touch /.autorelabel + reboot, or restorecon -R /) will reset any chcon changes back to the policy defaults. Use chcon to test — use semanage fcontext + restorecon to make it permanent.
5
Persistent Custom Contexts — semanage fcontext

When your files live in a non-standard path, add a persistent context rule with semanage fcontext then apply it with restorecon:

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

# Apply the rule
sudo restorecon -Rv /srv/myapp/

# Add rule for writable web content (uploads)
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/srv/myapp/uploads(/.*)?"
sudo restorecon -Rv /srv/myapp/uploads/

# List all custom fcontext rules
sudo semanage fcontext -l | grep myapp

# Delete a custom rule
sudo semanage fcontext -d "/srv/myapp(/.*)?"

# List ALL fcontext rules (very long)
sudo semanage fcontext -l | less
semanage fcontext -l | grep myapp:
/srv/myapp(/.*)? all files system_u:object_r:httpd_sys_content_t:s0 /srv/myapp/uploads(/.*)? all files system_u:object_r:httpd_sys_rw_content_t:s0
The correct permanent workflow:
  1. semanage fcontext -a -t TYPE "/path(/.*)?" — add the rule
  2. restorecon -Rv /path/ — apply the rule to existing files
  3. New files created under /path/ will inherit the correct context automatically
6
Common Type Labels — What They Mean

Knowing the most common type labels saves enormous troubleshooting time:

# Query what type a path should have
sudo semanage fcontext -l | grep "^/var/www"
sudo semanage fcontext -l | grep "^/etc/httpd"
sudo semanage fcontext -l | grep "^/var/log/httpd"
Type LabelMeaningTypical Path
httpd_sys_content_tWeb content Apache can read and serve/var/www/html/
httpd_sys_rw_content_tWeb content Apache can read AND writeUpload directories
httpd_config_tApache configuration files/etc/httpd/conf/
httpd_log_tApache log files/var/log/httpd/
sshd_key_tSSH host key files/etc/ssh/ssh_host_*
ssh_home_tUser SSH keys and config~/.ssh/
var_log_tGeneric log files/var/log/
etc_tGeneric config files in /etc/etc/
admin_home_tFiles in root or admin home dirs/root/, ~/
user_home_tRegular user home directory files/home/user/
tmp_tTemporary files/tmp/
bin_tSystem binaries/usr/bin/, /bin/
mysqld_db_tMySQL/MariaDB data files/var/lib/mysql/
postgresql_db_tPostgreSQL data files/var/lib/pgsql/
7
Check Port Contexts — semanage port

Processes can only bind to ports that match their type. If you run a service on a non-standard port, you must add it to the SELinux port context:

# List all port contexts
sudo semanage port -l

# Find what type is assigned to port 80 and 443
sudo semanage port -l | grep http

# Add a non-standard port for Apache (e.g., 8080)
sudo semanage port -a -t http_port_t -p tcp 8080

# Add a non-standard SSH port
sudo semanage port -a -t ssh_port_t -p tcp 2222

# Modify an existing port entry
sudo semanage port -m -t http_port_t -p tcp 8443

# Delete a custom port rule
sudo semanage port -d -t http_port_t -p tcp 8080

# List only http-related ports
sudo semanage port -l | grep http_port_t
semanage port -l | grep http_port_t:
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
Classic problem: You configure Apache or Nginx to listen on port 8080 but it fails to start. SELinux is blocking the bind because http_port_t does not include 8080 by default. Fix: sudo semanage port -a -t http_port_t -p tcp 8080
8
Real-World Workflow — New Web App Deployment

A complete context workflow for deploying a new application:

#!/bin/bash
# Deploy myapp to /srv/myapp — set correct SELinux contexts

APP_DIR="/srv/myapp"
APP_UPLOAD="$APP_DIR/uploads"
APP_PORT=8080

# 1. Create directories
sudo mkdir -p "$APP_DIR" "$APP_UPLOAD"

# 2. Deploy application files
sudo cp -r /home/deploy/myapp/* "$APP_DIR/"

# 3. Add persistent SELinux context rules
sudo semanage fcontext -a -t httpd_sys_content_t "${APP_DIR}(/.*)?"
sudo semanage fcontext -a -t httpd_sys_rw_content_t "${APP_UPLOAD}(/.*)?"

# 4. Apply the rules
sudo restorecon -Rv "$APP_DIR"

# 5. Add the custom port
sudo semanage port -a -t http_port_t -p tcp $APP_PORT

# 6. Verify contexts look correct
ls -lZ "$APP_DIR"
ls -lZ "$APP_UPLOAD"
sudo semanage port -l | grep $APP_PORT

echo "SELinux context setup complete."
Verification output:
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.php -rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 config.php drwxrwxr-x. root www system_u:object_r:httpd_sys_rw_content_t:s0 uploads/

Quick Reference

CommandWhat it does
ls -Z FILEShow SELinux context of file(s)
ls -lZ DIR/Long listing with contexts
ls -dZ DIR/Show context of directory itself
ps -eZ | grep svcShow process context
id -ZShow current user's SELinux context
restorecon FILERestore file to policy-defined context
restorecon -Rv DIR/Restore recursively, verbose
restorecon -Rvn DIR/Dry run — show what would change
chcon -t TYPE FILETemporarily change file type (testing only)
chcon --reference=REF FILECopy context from reference file
semanage fcontext -lList all file context rules
semanage fcontext -a -t TYPE "/path(/.*)?"Add persistent context rule
semanage fcontext -d "/path(/.*)?"Delete a context rule
semanage port -lList all port context rules
semanage port -a -t TYPE -p tcp PORTAdd a port to a type

chcon vs restorecon vs semanage fcontext

ToolPersistent?When to use
chcon❌ No — lost on relabelQuick testing only
restorecon✅ Yes — applies policy rulesFix wrong context on existing files
semanage fcontext + restorecon✅ Yes — adds new policy ruleNon-standard paths not covered by default policy

← Back to SELinux Index ↑ Back to EXPANDED