Learn how to implement mandatory access controls with SELinux for web servers and databases. Configure custom security contexts, create application-specific policies, and harden your infrastructure with advanced SELinux rules.
Prerequisites
- Root or sudo access
- Basic understanding of Linux permissions
- Web server and database knowledge
- Command line experience
What this solves
SELinux (Security-Enhanced Linux) provides mandatory access control that confines applications and system processes to minimal privileges, preventing unauthorized access even if an application is compromised. This tutorial covers installing SELinux on Debian-based systems, configuring security contexts for web applications and databases, creating custom policies, and implementing advanced security hardening rules.
Step-by-step installation
Install SELinux on Debian-based systems
SELinux comes pre-installed on RHEL-based distributions but requires manual installation on Debian-based systems.
sudo apt update
sudo apt install -y selinux-policy-default selinux-utils policycoreutils
sudo apt install -y setools-console selinux-policy-dev
Configure GRUB to enable SELinux
Add SELinux parameters to the kernel boot options to enable SELinux on system startup.
GRUB_CMDLINE_LINUX="selinux=1 security=selinux"
Update GRUB configuration and reboot:
sudo update-grub
sudo reboot
Set SELinux to permissive mode initially
Start with permissive mode to log policy violations without blocking operations during initial setup.
sudo setenforce 0
echo "SELINUX=permissive" | sudo tee /etc/selinux/config
Verify SELinux status:
sestatus
getenforce
Install and configure web server with SELinux contexts
Install Apache and configure proper SELinux contexts for web content.
sudo apt install -y apache2
sudo systemctl enable --now apache2
Configure SELinux contexts for web directories
Set proper security contexts for web content directories and files.
# Set context for web content directory
sudo semanage fcontext -a -t httpd_exec_t "/var/www/html(/.*)?"
sudo semanage fcontext -a -t httpd_config_t "/etc/apache2(/.*)?"
sudo restorecon -Rv /var/www/html
sudo restorecon -Rv /etc/apache2
Create a custom web application directory with proper contexts:
sudo mkdir -p /opt/webapps/myapp
sudo semanage fcontext -a -t httpd_exec_t "/opt/webapps/myapp(/.*)?"
sudo restorecon -Rv /opt/webapps/myapp
sudo chown -R www-data:www-data /opt/webapps/myapp
sudo chmod -R 755 /opt/webapps/myapp
Configure SELinux boolean settings for web services
Enable necessary boolean settings for web server functionality.
# Allow Apache to connect to network
sudo setsebool -P httpd_can_network_connect on
Allow Apache to connect to databases
sudo setsebool -P httpd_can_network_connect_db on
Allow Apache to send emails
sudo setsebool -P httpd_can_sendmail on
Allow Apache to execute CGI scripts
sudo setsebool -P httpd_enable_cgi on
View current boolean settings:
getsebool -a | grep httpd
Install and configure database with SELinux
Install PostgreSQL and configure SELinux contexts for database operations.
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresql
Configure SELinux contexts for database directories
Set proper security contexts for PostgreSQL data directories and configuration files.
# Set contexts for PostgreSQL directories
sudo semanage fcontext -a -t postgresql_db_t "/var/lib/postgresql(/.*)?"
sudo semanage fcontext -a -t postgresql_etc_t "/etc/postgresql(/.*)?"
sudo restorecon -Rv /var/lib/postgresql
sudo restorecon -Rv /etc/postgresql
Configure custom database directory with proper contexts:
sudo mkdir -p /opt/database/backups
sudo semanage fcontext -a -t postgresql_db_t "/opt/database(/.*)?"
sudo restorecon -Rv /opt/database
sudo chown -R postgres:postgres /opt/database
sudo chmod -R 700 /opt/database
Create custom SELinux policy for application
Generate a custom policy module for your specific application requirements.
# Create policy directory
sudo mkdir -p /etc/selinux/local
cd /etc/selinux/local
Create a custom policy file for web application database connections:
policy_module(myapp, 1.0)
require {
type httpd_t;
type postgresql_t;
type postgresql_port_t;
class tcp_socket name_connect;
class file { read write create unlink };
}
Allow web server to connect to PostgreSQL
allow httpd_t postgresql_port_t:tcp_socket name_connect;
allow httpd_t postgresql_t:tcp_socket { read write };
Allow web server to access custom log files
allow httpd_t admin_home_t:file { read write create unlink };
Compile and install custom policy
Compile the custom policy module and load it into the SELinux system.
# Compile policy module
sudo checkmodule -M -m -o myapp.mod myapp.te
sudo semodule_package -o myapp.pp -m myapp.mod
Install policy module
sudo semodule -i myapp.pp
Verify installation
sudo semodule -l | grep myapp
Configure SELinux port labels
Define custom port labels for applications running on non-standard ports.
# Add custom port for web application
sudo semanage port -a -t http_port_t -p tcp 8080
Add custom port for database connection
sudo semanage port -a -t postgresql_port_t -p tcp 5433
View current port assignments
sudo semanage port -l | grep http
sudo semanage port -l | grep postgresql
Create SELinux user mappings
Configure SELinux user mappings for better access control.
# Create SELinux user for web applications
sudo semanage user -a -R "webadm_r system_r" webadm_u
Map system user to SELinux user
sudo semanage login -a -s webadm_u webapp
View user mappings
sudo semanage login -l
Configure audit logging for SELinux
Enable detailed audit logging to monitor SELinux policy violations and access attempts.
sudo apt install -y auditd
sudo systemctl enable --now auditd
Configure audit rules for SELinux:
# SELinux audit rules
-w /etc/selinux/ -p wa -k selinux-config
-w /usr/sbin/semanage -p x -k selinux-manage
-w /usr/sbin/setsebool -p x -k selinux-booleans
sudo systemctl restart auditd
Switch to enforcing mode
After testing in permissive mode, switch to enforcing mode for full security.
# Check for any policy violations in permissive mode
sudo ausearch -m avc -ts recent
Switch to enforcing mode
sudo setenforce 1
echo "SELINUX=enforcing" | sudo tee /etc/selinux/config
Advanced SELinux troubleshooting
Analyze SELinux denials
Use audit2why and audit2allow to understand and resolve policy violations.
# Analyze recent denials
sudo ausearch -m avc -ts recent | audit2why
Generate policy rules for denials
sudo ausearch -m avc -ts recent | audit2allow -M myapp_fix
sudo semodule -i myapp_fix.pp
Monitor SELinux violations in real-time
Set up real-time monitoring of SELinux policy violations.
# Monitor audit log in real-time
sudo tail -f /var/log/audit/audit.log | grep AVC
Use sealert for user-friendly messages
sudo sealert -a /var/log/audit/audit.log
Verify your setup
# Check SELinux status
sestatus
Verify contexts are correctly applied
ls -Z /var/www/html
ls -Z /var/lib/postgresql
Check loaded policy modules
sudo semodule -l | grep myapp
Verify boolean settings
getsebool -a | grep httpd
Test web server functionality
sudo systemctl status apache2
curl -I http://localhost
Test database connectivity
sudo -u postgres psql -c "SELECT version();"
Check for recent denials
sudo ausearch -m avc -ts today | wc -l
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Web server can't access files | Incorrect file contexts | sudo restorecon -Rv /var/www/html |
| Database connection denied | Missing boolean setting | sudo setsebool -P httpd_can_network_connect_db on |
| Custom port access denied | Port not labeled correctly | sudo semanage port -a -t http_port_t -p tcp PORT |
| Policy compilation fails | Syntax errors in .te file | Check syntax with checkmodule -M -m myapp.te |
| Application logs show denials | Missing policy rules | Use audit2allow to generate rules |
| SELinux not enforcing | GRUB configuration missing | Add selinux=1 security=selinux to GRUB_CMDLINE_LINUX |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# SELinux Configuration Script for Web Applications and Databases
# Supports Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS, RHEL
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Error handling
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
# Revert SELinux to original state if changed
if [[ -n "${ORIGINAL_SELINUX:-}" ]]; then
echo "SELINUX=$ORIGINAL_SELINUX" > /etc/selinux/config
fi
}
trap cleanup ERR
# Usage message
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -d, --domain DOMAIN Domain name for web application (default: localhost)"
echo " -a, --app-dir DIR Custom application directory (default: /opt/webapps/myapp)"
echo " -h, --help Show this help message"
exit 1
}
# Parse arguments
DOMAIN="localhost"
APP_DIR="/opt/webapps/myapp"
while [[ $# -gt 0 ]]; do
case $1 in
-d|--domain)
DOMAIN="$2"
shift 2
;;
-a|--app-dir)
APP_DIR="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
usage
;;
esac
done
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root or with sudo${NC}"
exit 1
fi
# Detect distribution and set package manager
if [[ ! -f /etc/os-release ]]; then
echo -e "${RED}Cannot detect OS. /etc/os-release not found.${NC}"
exit 1
fi
source /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
WEB_SERVICE="apache2"
WEB_CONFIG_DIR="/etc/apache2"
WEB_USER="www-data"
DB_SERVICE="postgresql"
DB_CONFIG_DIR="/etc/postgresql"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
WEB_SERVICE="httpd"
WEB_CONFIG_DIR="/etc/httpd"
WEB_USER="apache"
DB_SERVICE="postgresql"
DB_CONFIG_DIR="/var/lib/pgsql"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
WEB_SERVICE="httpd"
WEB_CONFIG_DIR="/etc/httpd"
WEB_USER="apache"
DB_SERVICE="postgresql"
DB_CONFIG_DIR="/var/lib/pgsql"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${BLUE}Configuring SELinux for $PRETTY_NAME${NC}"
echo -e "${BLUE}Domain: $DOMAIN${NC}"
echo -e "${BLUE}App Directory: $APP_DIR${NC}"
# Store original SELinux state
if [[ -f /etc/selinux/config ]]; then
ORIGINAL_SELINUX=$(grep "^SELINUX=" /etc/selinux/config | cut -d'=' -f2)
fi
echo -e "${GREEN}[1/12] Updating package repositories...${NC}"
$PKG_UPDATE
echo -e "${GREEN}[2/12] Installing SELinux packages...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL selinux-policy-default selinux-utils policycoreutils
$PKG_INSTALL setools-console selinux-policy-dev
else
$PKG_INSTALL policycoreutils-python-utils setools-console
$PKG_INSTALL selinux-policy-devel
fi
echo -e "${GREEN}[3/12] Configuring GRUB for SELinux...${NC}"
if ! grep -q "selinux=1" /etc/default/grub; then
sed -i 's/GRUB_CMDLINE_LINUX="[^"]*/& selinux=1 security=selinux/' /etc/default/grub
if command -v update-grub >/dev/null 2>&1; then
update-grub
elif command -v grub2-mkconfig >/dev/null 2>&1; then
grub2-mkconfig -o /boot/grub2/grub.cfg
fi
fi
echo -e "${GREEN}[4/12] Setting SELinux to permissive mode...${NC}"
mkdir -p /etc/selinux
echo "SELINUX=permissive" > /etc/selinux/config
echo "SELINUXTYPE=targeted" >> /etc/selinux/config
if command -v setenforce >/dev/null 2>&1; then
setenforce 0 || true
fi
echo -e "${GREEN}[5/12] Installing web server...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL apache2
else
$PKG_INSTALL httpd
fi
systemctl enable --now $WEB_SERVICE
echo -e "${GREEN}[6/12] Configuring SELinux contexts for web directories...${NC}"
if command -v semanage >/dev/null 2>&1; then
semanage fcontext -a -t httpd_exec_t "/var/www/html(/.*)?" || true
semanage fcontext -a -t httpd_config_t "$WEB_CONFIG_DIR(/.*)?" || true
restorecon -Rv /var/www/html || true
restorecon -Rv $WEB_CONFIG_DIR || true
# Create custom web application directory
mkdir -p "$APP_DIR"
semanage fcontext -a -t httpd_exec_t "$APP_DIR(/.*)?" || true
restorecon -Rv "$APP_DIR" || true
chown -R $WEB_USER:$WEB_USER "$APP_DIR"
chmod -R 755 "$APP_DIR"
fi
echo -e "${GREEN}[7/12] Configuring SELinux boolean settings for web services...${NC}"
if command -v setsebool >/dev/null 2>&1; then
setsebool -P httpd_can_network_connect on || true
setsebool -P httpd_can_network_connect_db on || true
setsebool -P httpd_can_sendmail on || true
setsebool -P httpd_enable_cgi on || true
fi
echo -e "${GREEN}[8/12] Installing database server...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL postgresql postgresql-contrib
else
$PKG_INSTALL postgresql-server postgresql-contrib
if [[ ! -d /var/lib/pgsql/data/base ]]; then
postgresql-setup --initdb || true
fi
fi
systemctl enable --now $DB_SERVICE
echo -e "${GREEN}[9/12] Configuring SELinux contexts for database directories...${NC}"
if command -v semanage >/dev/null 2>&1; then
semanage fcontext -a -t postgresql_db_t "/var/lib/postgresql(/.*)?" || true
semanage fcontext -a -t postgresql_db_t "/var/lib/pgsql(/.*)?" || true
semanage fcontext -a -t postgresql_etc_t "$DB_CONFIG_DIR(/.*)?" || true
restorecon -Rv /var/lib/postgresql || true
restorecon -Rv /var/lib/pgsql || true
restorecon -Rv $DB_CONFIG_DIR || true
# Create custom database backup directory
mkdir -p /opt/database/backups
semanage fcontext -a -t postgresql_db_t "/opt/database(/.*)?" || true
restorecon -Rv /opt/database || true
chown -R postgres:postgres /opt/database
chmod -R 700 /opt/database
fi
echo -e "${GREEN}[10/12] Creating custom SELinux policy module...${NC}"
cat > /tmp/webapp_custom.te << 'EOF'
policy_module(webapp_custom, 1.0);
require {
type httpd_t;
type httpd_exec_t;
type postgresql_t;
class file { read write execute getattr };
class dir { search };
}
# Allow httpd to execute custom application files
allow httpd_t httpd_exec_t:file { read write execute getattr };
# Allow httpd to connect to postgresql
allow httpd_t postgresql_t:dir search;
EOF
if command -v checkmodule >/dev/null 2>&1; then
cd /tmp
checkmodule -M -m -o webapp_custom.mod webapp_custom.te || true
if [[ -f webapp_custom.mod ]]; then
semodule_package -o webapp_custom.pp -m webapp_custom.mod || true
if [[ -f webapp_custom.pp ]]; then
semodule -i webapp_custom.pp || true
fi
fi
fi
echo -e "${GREEN}[11/12] Configuring firewall rules...${NC}"
if command -v ufw >/dev/null 2>&1 && systemctl is-active --quiet ufw; then
ufw allow 80/tcp
ufw allow 443/tcp
elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
fi
echo -e "${GREEN}[12/12] Running verification checks...${NC}"
# Check SELinux status
if command -v sestatus >/dev/null 2>&1; then
echo -e "${BLUE}SELinux Status:${NC}"
sestatus || echo -e "${YELLOW}SELinux not fully active yet (reboot required)${NC}"
fi
# Check web server status
if systemctl is-active --quiet $WEB_SERVICE; then
echo -e "${GREEN}✓ Web server ($WEB_SERVICE) is running${NC}"
else
echo -e "${RED}✗ Web server ($WEB_SERVICE) is not running${NC}"
fi
# Check database status
if systemctl is-active --quiet $DB_SERVICE; then
echo -e "${GREEN}✓ Database server ($DB_SERVICE) is running${NC}"
else
echo -e "${RED}✗ Database server ($DB_SERVICE) is not running${NC}"
fi
# Check application directory
if [[ -d "$APP_DIR" ]]; then
echo -e "${GREEN}✓ Application directory created: $APP_DIR${NC}"
ls -laZ "$APP_DIR" 2>/dev/null || ls -la "$APP_DIR"
else
echo -e "${RED}✗ Application directory not found: $APP_DIR${NC}"
fi
echo -e "${GREEN}SELinux configuration completed successfully!${NC}"
echo -e "${YELLOW}IMPORTANT: Reboot the system to fully activate SELinux enforcement${NC}"
echo -e "${YELLOW}After reboot, run 'sudo setenforce 1' to enable enforcing mode${NC}"
echo -e "${BLUE}Web content directory: /var/www/html${NC}"
echo -e "${BLUE}Custom app directory: $APP_DIR${NC}"
echo -e "${BLUE}Database backup directory: /opt/database/backups${NC}"
Review the script before running. Execute with: bash install.sh