Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

SELinux Basics for Administrators

By Kokil Thapa | Last reviewed: September 2026

Your Laravel app works locally but fails on the server with a cryptic permission error. The file permissions look correct. The owner is right. Yet Apache still cannot write to storage/ or connect to Redis. On many production Linux hosts, SELinux Basics for Administrators explain that gap. Security-Enhanced Linux adds a mandatory access layer on top of standard Unix permissions. I hit this regularly on Linux system administration work for Ubuntu servers running PHP-FPM and Apache. This guide walks through enforcement modes, contexts, booleans, and the audit trail you need to fix denials without turning SELinux off.

What is SELinux and why do Linux administrators need it?

SELinux is a Mandatory Access Control (MAC) framework built into the Linux kernel. Standard Unix permissions are Discretionary Access Control (DAC). With DAC, a file owner can chmod a sensitive config world-readable. SELinux ignores that wish when policy says otherwise.

Every process and file carries a security context. The kernel checks policy rules before allowing an action. A compromised web server process cannot read /etc/shadow just because it runs as root. Policy blocks it.

Red Hat Enterprise Linux, Fedora, AlmaLinux, Rocky Linux, and CentOS Stream ship with SELinux enabled by default. Ubuntu and Debian include the packages but often run permissive or disabled unless you enable it. On shared EC2 infrastructure where I deploy Ansible-provisioned PHP servers, SELinux surprises are a top post-deploy failure category.

SELinux MAC vs Unix DACProcesshttpd_t domainKernelAccess checkFile Objecthttpd_sys_rw_tStep 1: DAC checkowner, group, mode bitsStep 2: SELinux MACtype, class, allow rulesDeny wins if either layer blocks accesschmod 777 does not bypass SELinux policy
SELinux basics for administrators: MAC runs after DAC and can block access even when file mode bits allow it.

The SELinux project originated from NSA research and is maintained through the SELinux Project on GitHub. Red Hat integrates it deeply into RHEL ecosystems. Understanding that lineage helps—you are working with kernel-level policy, not a bolt-on antivirus tool.

Core components you will touch daily

  • Policy — compiled rules defining which domains may access which types.
  • Labels (contexts) — strings like system_u:object_r:httpd_sys_content_t:s0 on files and processes.
  • Booleans — runtime toggles that enable or disable grouped policy rules.
  • Audit subsystem — logs Access Vector Cache (AVC) denials when access is blocked.

How do SELinux enforcement modes work on production servers?

SELinux runs in one of three modes. Your first command on any unfamiliar server should be getenforce. It returns Enforcing, Permissive, or Disabled.

ModeBehaviorLogs denials?Production use
EnforcingBlocks policy violationsYesDefault on RHEL-family; use this in production
PermissiveAllows but logs would-be blocksYesTroubleshooting and policy development only
DisabledSELinux fully off in kernelNoAvoid; requires reboot to re-enable
# Check current mode
getenforce

# View config file (persists across reboots)
cat /etc/selinux/config

# Temporary switch to permissive (lost on reboot)
sudo setenforce 0

# Return to enforcing
sudo setenforce 1

On RHEL 9 and AlmaLinux 9, Enforcing is the shipped default. Ubuntu 22.04 and 24.04 include selinux-utils and policycoreutils packages. You must install them and set SELINUX=enforcing in /etc/selinux/config, then reboot. I prefer Permissive for a short burn-in period on new hosts. It surfaces denials without breaking traffic while you tune contexts.

Never leave Permissive on a public-facing production box long-term. It trains you to ignore logs while leaving the attack surface wide open. Treat Permissive as a diagnostic window measured in hours, not weeks.

What are SELinux contexts, types, and labels?

A security context has four fields: user, role, type, and level (MLS/MCS). Administrators spend most of their time on the type field. Process domains and file types must match policy for access to succeed.

# View file context
ls -Z /var/www/myapp/public/index.php

# Example output:
# system_u:object_r:httpd_sys_content_t:s0

# View process context
ps -eZ | grep httpd

The format is user:role:type:level. For targeted policy—the default on most servers—focus on type. A PHP-FPM pool running as httpd_t can read httpd_sys_content_t files. It cannot write to etc_t unless policy explicitly allows it.

Security Context StructureUserRoleTypeLevelsystem_u : object_r : httpd_sys_content_t : s0Process domainhttpd_t runs PHP-FPMFile type labelhttpd_sys_rw_content_tallowPolicy matches source domain to target typeWrong type on storage/ = silent write failure
SELinux context labels: the type field drives most allow/deny decisions on web application files.

Restoring default contexts

When you deploy code via Git or rsync, copied files may carry wrong contexts. Use restorecon before chasing chmod issues.

  1. Install the app under /var/www/ or your chosen path.
  2. Run sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/myapp/storage(/.*)?' if the path is non-standard.
  3. Apply labels: sudo restorecon -Rv /var/www/myapp.
  4. Verify with ls -Z on storage/ and bootstrap/cache/.

On a Laravel deployment, writable directories need httpd_sys_rw_content_t. Public assets need httpd_sys_content_t. I have seen teams spend hours fixing ownership when a two-second restorecon was the real fix. The support and maintenance tickets often start with "Permission denied" in the Laravel log.

How do you read SELinux denials and troubleshoot access issues?

When SELinux blocks an action, the kernel writes an AVC denial to the audit log. That log is your primary evidence. Do not guess. Read it.

# Watch denials in real time
sudo ausearch -m avc -ts recent

# Or tail the audit log directly
sudo tail -f /var/log/audit/audit.log | grep denied

# Generate a human-readable report
sudo sealert -a /var/log/audit/audit.log

A typical denial names the source context (scontext), target context (tcontext), object class, and permission requested. Example: httpd_t tried name_connect to redis_port_t and was denied. The fix is likely a boolean, not a chmod.

SELinux Denial Troubleshooting FlowApp fails silentlyCheck audit.log AVC lineWrong file typerestorecon / semanageMissing booleansetsebool -P name onNovel rule neededaudit2allow moduleRetest with SELinux EnforcingNever leave Permissive as the permanent fix
Production SELinux troubleshooting: read AVC denials first, then apply the smallest fix—context, boolean, or custom module.

Using booleans for common web stack fixes

Booleans toggle pre-written policy bundles. They are safer than writing custom modules when a standard boolean already exists.

# List booleans related to HTTP
getsebool -a | grep httpd

# Allow Apache/PHP to connect to Redis or Memcached
sudo setsebool -P httpd_can_network_connect 1

# Allow outbound DB connections (MySQL, PostgreSQL)
sudo setsebool -P httpd_can_network_connect_db 1

# Allow sending mail from PHP mail()
sudo setsebool -P httpd_can_sendmail 1

The -P flag makes the change persistent across reboots. Without it, your fix vanishes after the next restart. I have seen this bite teams during scheduled reboots after kernel updates.

When audit2allow is appropriate

Custom modules should be your last resort. First try restorecon and booleans. If policy genuinely lacks a rule—for example a non-standard binary path—pipe denials through audit2allow.

# Review what a module would contain (do not install blindly)
sudo ausearch -c 'php-fpm' --raw | audit2allow -w

# Build and load a custom module (scrutinize the .te file first)
sudo ausearch -c 'php-fpm' --raw | audit2allow -M myapp_php
sudo semodule -i myapp_php.pp

Overly broad custom modules can punch holes wider than you intend. Read the generated Type Enforcement file. If it allows unconfined_t transitions or broad allow * *:* patterns, stop and redesign. Pair this discipline with broader hardening work like ISO 27001 basics for engineers when your org needs formal security controls.

How do you configure SELinux for Apache, PHP-FPM, and databases?

Web stacks trigger the majority of SELinux tickets on servers I maintain. The pattern repeats: application code lands with wrong labels, or outbound network access is blocked by default policy.

Apache and PHP-FPM document roots

RHEL policy expects web content under /var/www/html or paths labeled httpd_sys_content_t. Laravel apps often live at /var/www/myapp. Define persistent context rules before your first deploy.

# Add persistent file context mapping
sudo semanage fcontext -a -t httpd_sys_content_t '/var/www/myapp/public(/.*)?'
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/myapp/storage(/.*)?'
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/myapp/bootstrap/cache(/.*)?'

# Apply to existing files
sudo restorecon -Rv /var/www/myapp

If PHP-FPM runs in its own domain on your distro, check with ps -eZ | grep php-fpm. Some setups run pools as httpd_t; others use php-fpm_t. The domain determines which booleans and types apply. Match your fix to the actual domain shown in the AVC line.

Database and cache connectivity

Local MySQL sockets usually work without extra booleans. Remote PostgreSQL or Redis on another host needs network connect permissions. Redis on port 6379 maps to redis_port_t in policy.

  • Enable httpd_can_network_connect_db for remote SQL clients.
  • Enable httpd_can_network_connect for Redis, Memcached, or HTTP API callbacks.
  • For custom ports, you may need semanage port -a -t http_port_t -p tcp 8080.

On sister sites sharing a Deployer pipeline—legal-tech portals like those in my Notary Kathmandu portfolio deployment—the same SELinux context rules get baked into Ansible tasks. That prevents each release from reintroducing mislabeled upload directories.

Nginx versus Apache considerations

Nginx runs as httpd_t on RHEL when installed from official repos, but Ubuntu packaging differs. Always verify the running domain. Nginx proxying to PHP-FPM adds a domain transition: Nginx reads static files, FPM executes PHP, FPM may write sessions or cache files. Each step needs correct target types.

For containerised workloads on Kubernetes clusters, SELinux on the node still matters. Pod sandboxes use container domains. Host-level SELinux must allow container runtime access to volumes and networks. That is a separate topic, but the audit mindset stays the same.

Web Stack SELinux Booleanshttpd_t / php-fpmweb server processRedis / APInetwork_connectMySQL remotenetwork_connect_dbSendmailcan_sendmailUpload dirsys_rw_content_tchmod 777 does not replace these fixessetsebool -P persists; setsebool alone does not
SELinux basics for administrators running PHP apps: booleans and writable content types fix most outbound and storage denials.

What SELinux mistakes do administrators make in production?

The worst mistake is disabling SELinux entirely. Editing /etc/selinux/config to disabled removes kernel hooks. Re-enabling requires a full policy relabel, which can take hours on large file trees. A reboot alone is not enough.

Second worst: setting Permissive permanently and calling it done. You lose MAC protection while still running audit overhead. Fix the policy instead.

Practical habits that prevent repeat incidents

  1. Include restorecon in deployment scripts after rsync or Git checkout.
  2. Log into audit during staging deploys and click through critical user flows.
  3. Document every setsebool and semanage fcontext rule in your runbook.
  4. Test post-deploy with getenforce confirming Enforcing, not Permissive.
  5. Store AVC snippets in ticket comments so the next engineer sees the exact denial.

File ownership mistakes and SELinux mistakes look identical in application logs. Both say "Permission denied." Train your team to run ls -Z alongside ls -la. The Red Hat RHEL 9 Using SELinux guide remains the best long-form reference for targeted policy.

Monitoring helps catch silent degradation. Pair audit review with Prometheus Alertmanager alerting on error-rate spikes after deploys. A sudden 403 or 500 burst after a release often correlates with context drift on new files.

For database-heavy apps, remember SELinux is separate from database user grants. MySQL GRANT permissions and SELinux httpd_can_network_connect_db are both required for remote connections. Fixing one without the other still fails. Similar layered checks apply when tuning MongoDB administration on SELinux-enabled hosts with custom data paths.

Hosting clients on budget shared VPS plans sometimes lack audit tools. Install audit, setroubleshoot-server, and policycoreutils-python-utils on day one. The package cost is zero. The debugging time saved is measured in hours per incident. If you outsource server work, confirm your provider runs Enforcing and knows how to tune it—not just how to disable it.

Security posture connects to broader site reliability. A hardened server supports speed optimization work because you can enable caching and outbound API calls deliberately rather than leaving ports and permissions wide open. SELinux gives you a policy paper trail. That matters when a client asks what happens if the web process is compromised.

When parsing complex AVC lines, a regex tester helps build patterns for log filtering scripts. I use simple awk and grep in practice, but regex validation saves typos in production log parsers.

Compare SELinux work with standard hardening on domain registration and hosting setups. UFW, fail2ban, and SELinux stack together. UFW blocks network ports. SELinux limits what a process can do after it binds to an allowed port. You need both layers on public Laravel and WordPress hosts I deploy for Nepal businesses.

WordPress on RHEL with custom upload paths triggers the same httpd_sys_rw_content_t requirements as Laravel storage/. WooCommerce and media-heavy sites need persistent semanage fcontext entries for wp-content/uploads. Document those paths in your WordPress development handover notes so the next admin does not strip labels during a migration.

Before major OS upgrades—say Ubuntu 22.04 to 24.04—run sestatus and archive your boolean list with getsebool -a > /root/selinux-booleans-backup.txt. Major upgrades occasionally reset custom booleans. Your backup makes diffing fast. The same discipline applies to website migration projects where the target server enforces policy the source server ignored.

If you build internal admin panels that shell out to system commands, beware sebool and semanage are root-only. Your application should not need them at runtime. Fix contexts at deploy time instead. Application-level security belongs in enterprise application development policy, not in live SELinux mutation from PHP.

Read the Gentoo SELinux Tutorials wiki when you need distro-neutral explanations of type enforcement concepts. Red Hat docs are operationally focused. Gentoo's tutorials explain the theory behind domains and roles clearly.

Key Takeaways

  • Run getenforce first on every unfamiliar server; production should stay Enforcing after tuning.
  • Read AVC denials in /var/log/audit/audit.log before changing chmod, ownership, or firewall rules.
  • Fix file paths with semanage fcontext plus restorecon, not recursive chmod 777.
  • Use setsebool -P for standard web-to-Redis, DB, and mail connectivity blocks.
  • Reserve audit2allow custom modules for genuinely novel binaries after reviewing generated rules.
  • Bake SELinux context restoration into deploy scripts alongside Composer and PHP-FPM reload steps.

People Also Ask

Should I disable SELinux on my production web server?

No. Disabling SELinux removes a kernel-level safety net that contains compromised daemons. Fix denials with correct contexts and booleans instead. Disabling also requires a reboot and a painful relabel if you re-enable later.

What is the difference between chmod permissions and SELinux contexts?

chmod controls DAC—owner, group, and other read/write/execute bits. SELinux contexts add MAC—policy decides whether a process domain may access a file type regardless of mode bits. Both layers must allow the operation.

How do I make SELinux changes survive a reboot?

Use setsebool -P for booleans and semanage fcontext for persistent file labels. Temporary setenforce 0 and non-P booleans reset after restart. Context rules from semanage persist in policy store.

Why does my app work in Permissive but fail in Enforcing?

Permissive logs denials without blocking them. Enforcing applies the same rules but denies access. Your app depends on operations policy forbids—network connects, file writes, or socket binds—that Permissive masked.

Build servers that stay secure after deploy day

SELinux basics for administrators boil down to reading denials, labeling files correctly, and toggling the right booleans. The skill pays off every time a deploy lands without a midnight chmod panic. If your production stack runs Laravel, WordPress, or custom PHP on Enforcing Linux and you want deploy scripts that handle contexts from day one, contact us for server hardening and ongoing Linux administration. You can also browse the Adventure Third Pole Trek deployment and other portfolio projects shipped on hardened Ubuntu infrastructure, or read more on the blog and about me page for the full engineering background behind this work.

Frequently Asked Questions

SELinux is Mandatory Access Control built into the Linux kernel. Standard Unix permissions are Discretionary Access Control—an owner can chmod a file world-readable. SELinux runs after DAC and can block access even when mode bits allow it. Every process and file carries a security context, and compiled policy rules decide whether an action is permitted regardless of ownership.

Red Hat Enterprise Linux, Fedora, AlmaLinux, Rocky Linux, and CentOS Stream enable SELinux by default, typically in Enforcing mode on current releases like RHEL 9. Ubuntu and Debian include SELinux packages but often run Permissive or Disabled unless you install selinux-utils and policycoreutils, set SELINUX=enforcing in /etc/selinux/config, and reboot.

Enforcing blocks policy violations and logs denials. Permissive allows actions but logs what would have been blocked—use only for short troubleshooting windows. Disabled turns SELinux off in the kernel entirely; re-enabling requires a full policy relabel and reboot, not just flipping a switch back.

Run getenforce. It returns Enforcing, Permissive, or Disabled and tells you immediately whether SELinux can explain cryptic permission errors. Pair it with cat /etc/selinux/config to see what persists across reboots. On production Laravel or WordPress hosts I maintain, this is step one before chasing chmod or ownership.

Copied or deployed files often carry wrong SELinux contexts even when owner and mode bits are fine. Writable Laravel directories like storage/ and bootstrap/cache/ need httpd_sys_rw_content_t, not just correct Unix permissions. Run ls -Z alongside ls -la. A two-second restorecon frequently fixes what teams spend hours debugging as ownership problems.

Check /var/log/audit/audit.log for AVC denials—they name source context, target context, object class, and permission requested. Use ausearch -m avc -ts recent or tail the log filtered for denied. sealert -a /var/log/audit/audit.log produces human-readable reports. Read the denial first; do not guess with chmod or by disabling SELinux.

A security context has four fields: user, role, type, and level. On targeted policy—the default on most servers—the type field drives most decisions. Process domains like httpd_t must match file types like httpd_sys_content_t for access to succeed. View file labels with ls -Z and running process domains with ps -eZ | grep httpd.

Define persistent rules with semanage fcontext—for example mapping /var/www/myapp/storage to httpd_sys_rw_content_t—then apply labels with restorecon -Rv /var/www/myapp. Verify with ls -Z on storage/ and bootstrap/cache/. Include restorecon in deployment scripts so each release does not reintroduce mislabeled upload or cache directories.

Booleans are runtime toggles for grouped policy rules—safer than custom modules when a standard boolean exists. Common web fixes include httpd_can_network_connect for Redis or Memcached, httpd_can_network_connect_db for remote MySQL or PostgreSQL, and httpd_can_sendmail for PHP mail(). Always use setsebool -P so changes survive reboots; without -P, fixes vanish after kernel updates.

Custom modules should be your last resort. First try restorecon and existing booleans. If policy genuinely lacks a rule—for a non-standard binary path—review generated rules with audit2allow -w before loading. Read the Type Enforcement file carefully; overly broad modules allowing unconfined_t transitions or wildcard patterns punch holes wider than intended.

Label public web roots httpd_sys_content_t and writable paths httpd_sys_rw_content_t. Local MySQL sockets usually work without extra booleans; remote PostgreSQL or Redis on another host needs httpd_can_network_connect_db or httpd_can_network_connect respectively. Redis on port 6379 maps to redis_port_t. Check ps -eZ | grep php-fpm—pools may run as httpd_t or php-fpm_t, and the AVC line tells you which booleans apply.

No. Disabling SELinux in /etc/selinux/config removes kernel hooks; re-enabling requires a full policy relabel that can take hours on large file trees. Fix contexts, booleans, or targeted modules instead. The worst production habit is permanent Permissive mode—it removes MAC protection while still running audit overhead without actually securing the host.

Ubuntu 22.04 and 24.04 need selinux-utils and policycoreutils to enable enforcing mode. For debugging, install audit, setroubleshoot-server, and policycoreutils-python-utils on day one—the package cost is zero, but the debugging time saved is measured in hours per incident. Budget VPS plans sometimes ship without audit tools, which makes AVC diagnosis painful.

They stack as separate layers. UFW blocks network ports; SELinux limits what a process can do after binding to an allowed port. Database GRANT permissions and SELinux httpd_can_network_connect_db are both required for remote SQL connections—fixing one without the other still fails. You need firewall, MAC, and application-level permissions aligned on public-facing PHP hosts.

Before major upgrades like Ubuntu 22.04 to 24.04, run sestatus and archive your boolean list with getsebool -a redirected to a backup file—upgrades occasionally reset custom booleans. Document every setsebool and semanage fcontext rule in your runbook. During migrations, the target server may enforce policy the source ignored; diff your backup to restore network-connect and writable-path rules quickly.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: