Securing CloudPanel on Ubuntu 24.04 Part 3
Part 3: Web Security and Application-Level Protection #
I’ll explain how to implement comprehensive web security measures specifically tailored for CloudPanel. Let’s break this down into manageable components that work together to create a robust security layer.
1. Enhanced Nginx Security Configuration
First, let’s create a security-focused Nginx configuration that aligns with CloudPanel. CloudPanel uses a dual Nginx configuration approach, with core settings in /home/clp/services/nginx/
and site-specific configurations in /etc/nginx/
:
# Create a security-focused Nginx configurationsudo nano /etc/nginx/conf.d/security-headers.conf
Let’s implement strong security headers while maintaining CloudPanel’s functionality:
# Enhanced Security Headers Configuration# These headers protect against common web vulnerabilities while ensuring CloudPanel operates correctly
# Basic security headersadd_header X-Content-Type-Options "nosniff" always;add_header X-Frame-Options "SAMEORIGIN" always;add_header X-XSS-Protection "1; mode=block" always;add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Content Security Policy tailored for CloudPaneladd_header Content-Security-Policy " default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-ancestors 'self'; form-action 'self';" always;
# HSTS configuration - Enable only after ensuring SSL is properly configured# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Protection against various attacksclient_body_buffer_size 10K;client_max_body_size 10M;client_body_timeout 12;client_header_timeout 12;
# Rate limiting configurationlimit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;limit_conn_zone $binary_remote_addr zone=addr:10m;
2. PHP Security Hardening
CloudPanel manages multiple PHP versions, so we need to secure each version while maintaining compatibility. Let’s create a comprehensive PHP security configuration:
# Create a function to apply security settings to all PHP versionssudo nano /usr/local/bin/cloudpanel-php-secure.sh
Here’s a script that automatically secures all PHP versions used by CloudPanel:
#!/bin/bash
# CloudPanel Security Manager# This script implements security measures for CloudPanel installations# focusing on PHP hardening and firewall configurations.
set -euo pipefailIFS=$'\n\t'
# Configuration variablesreadonly CP_HOME="/home/clp"readonly SITES_ROOT="/home"readonly DB_PATH="${CP_HOME}/htdocs/app/data/db.sq3"readonly LOG_DIR="/var/log/cloudpanel_monitoring"readonly LOG_FILE="${LOG_DIR}/security_manager.log"readonly ALERT_LOG="${LOG_DIR}/alerts.log"readonly BACKUP_DIR="${LOG_DIR}/backups"
# Color definitionsreadonly RED='\033[0;31m'readonly GREEN='\033[0;32m'readonly YELLOW='\033[1;33m'readonly BLUE='\033[0;34m'readonly NC='\033[0m'
# Security settingsreadonly DEFAULT_MEMORY_LIMIT="256M"readonly DEFAULT_UPLOAD_SIZE="10M"
# Initialize loggingsetup_logging() { mkdir -p "${LOG_DIR}" "${BACKUP_DIR}"
if [[ ! -f "${LOG_FILE}" ]]; then echo "=== CloudPanel Security Manager Log Started $(date '+%Y-%m-%d %H:%M:%S') ===" > "${LOG_FILE}" fi
if [[ ! -f "${ALERT_LOG}" ]]; then echo "=== CloudPanel Security Alerts Started $(date '+%Y-%m-%d %H:%M:%S') ===" > "${ALERT_LOG}" fi
# Set up log rotation cat > "/etc/logrotate.d/cloudpanel-security" << 'LOGROTATE'/var/log/cloudpanel_monitoring/*.log { daily rotate 14 compress delaycompress missingok notifempty create 0640 root root}LOGROTATE
log "INFO" "System" "Logging initialized"}
# Log function with component trackinglog() { local level=$1 local component=$2 shift 2 local message="$*" local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Select color based on level local color="" case "${level}" in "ERROR") color="${RED}" ;; "WARN") color="${YELLOW}" ;; "INFO") color="${GREEN}" ;; "DEBUG") color="${BLUE}" ;; esac
# Log to file echo "${timestamp} | ${level} | ${component} | ${message}" >> "${LOG_FILE}"
# Console output echo -e "${color}[${timestamp}] [${level}] ${component}: ${message}${NC}"
# Log alerts if [[ "${level}" == "ERROR" || "${level}" == "WARN" ]]; then echo "${timestamp} | ${level} | ${component} | ${message}" >> "${ALERT_LOG}" fi}
# Backup functionbackup_file() { local file=$1 local backup="${BACKUP_DIR}/$(basename "${file}").$(date +%Y%m%d_%H%M%S)"
if [[ -f "${file}" ]]; then cp "${file}" "${backup}" log "INFO" "Backup" "Created backup: ${backup}" else log "WARN" "Backup" "File not found: ${file}" return 1 fi}
# Configure firewallconfigure_firewall() { log "INFO" "Firewall" "Configuring firewall rules"
sqlite3 "${DB_PATH}" << 'FWEND'BEGIN TRANSACTION;DELETE FROM firewall_rule;INSERT INTO firewall_rule (created_at, updated_at, port_range, source, description)VALUES (datetime('now'), datetime('now'), '80,443', '0.0.0.0/0', 'Web Traffic'), (datetime('now'), datetime('now'), '22', '0.0.0.0/0', 'SSH Access'), (datetime('now'), datetime('now'), '8443', '127.0.0.1,::1', 'CloudPanel Local Access');COMMIT;FWEND
log "INFO" "Firewall" "Rules updated successfully"}
# Configure PHP securityconfigure_php_security() { local site_id=$1 local domain=$2
log "INFO" "PHP" "Configuring security for ${domain}"
sqlite3 "${DB_PATH}" << PHPENDUPDATE php_settings SET memory_limit = '${DEFAULT_MEMORY_LIMIT}', max_execution_time = 30, max_input_time = 60, max_input_vars = 1000, post_max_size = '${DEFAULT_UPLOAD_SIZE}', upload_max_file_size = '${DEFAULT_UPLOAD_SIZE}', additional_configuration = 'expose_php = Offallow_url_fopen = Offallow_url_include = Offsession.cookie_httponly = 1session.cookie_secure = 1session.cookie_samesite = "Strict"session.use_strict_mode = 1display_errors = Offlog_errors = Onerror_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICTdisable_functions = exec,passthru,shell_exec,system,proc_open,popenmax_execution_time = 30memory_limit = ${DEFAULT_MEMORY_LIMIT}'WHERE site_id = ${site_id};PHPEND
log "INFO" "PHP" "Settings updated for ${domain}"}
# Process siteprocess_site() { local site_id=$1 local domain=$2
log "INFO" "Site" "Processing ${domain}" configure_php_security "${site_id}" "${domain}"}
# Main functionmain() { local start_time=$(date +%s)
setup_logging log "INFO" "System" "Starting security configuration"
backup_file "${DB_PATH}" configure_firewall
while IFS='|' read -r site_id domain; do process_site "${site_id}" "${domain}" done < <(sqlite3 "${DB_PATH}" "SELECT id, domain_name FROM site;")
log "INFO" "System" "Restarting PHP-FPM" systemctl restart "php*-fpm"
local end_time=$(date +%s) log "INFO" "System" "Completed in $((end_time - start_time)) seconds"}
# Run main with error handlingif [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then trap 'log "ERROR" "System" "Failed on line $LINENO"' ERR main "$@"fi
3. Web Application Firewall Integration
Let’s implement a WAF configuration that works with CloudPanel’s structure:
Install Naxsi WAF for Nginx #
Here’s a CloudPanel-specific Naxsi WAF configuration:
# Include in the server block of your WordPress siteserver { # Standard CloudPanel configuration remains unchanged listen 80; listen [::]:80; server_name example.com;
# Enable Naxsi base rules include /etc/nginx/naxsi/naxsi-base.conf;
# WordPress-specific whitelist rules include /etc/nginx/naxsi/wordpress.rules;
location / { # Standard WordPress handling try_files $uri $uri/ /index.php?$args;
# WordPress-specific Naxsi configurations BasicRule wl:1315,1101 "mz:$BODY_VAR:post_title"; BasicRule wl:1315,1101 "mz:$BODY_VAR:content"; BasicRule wl:1315 "mz:$BODY_VAR:excerpt"; BasicRule wl:1101 "mz:$BODY_VAR:cat";
# Allow WordPress admin actions BasicRule wl:1000,1015 "mz:$URL:/wp-admin/|$BODY_VAR:action"; }
# Special handling for wp-admin location /wp-admin { # Allow file uploads in wp-admin BasicRule wl:1500 "mz:$URL:/wp-admin/|$BODY_VAR:attachment"; BasicRule wl:1310,1311 "mz:$URL:/wp-admin/|BODY";
# Standard CloudPanel proxy configuration proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}