WHMCS powers millions in revenue and stores sensitive customer data. A security breach can destroy your business overnight. This comprehensive checklist covers 40+ essential security measures that protect 99% of production WHMCS installations from common attacks.
Why WHMCS Security Matters
$50K+
Average Breach Cost
72%
Businesses Close After Breach
15 min
Average Attack Detection
Server Level Security
Operating System Hardening
# Update system packages
sudo apt update && sudo apt upgrade -y
# Remove unnecessary packages
sudo apt autoremove -y
sudo apt autoclean
# Disable unused services
sudo systemctl disable cups
sudo systemctl disable bluetooth
sudo systemctl disable avahi-daemon
sudo systemctl disable whoopsie
sudo systemctl disable snapd.apparmor
# Configure automatic security updates
sudo apt install unattended-upgrades -y
echo 'Unattended-Upgrade::Automatic-Reboot "true";' >> /etc/apt/apt.conf.d/50unattended-upgrades
echo 'Unattended-Upgrade::Automatic-Reboot-Time "02:00";' >> /etc/apt/apt.conf.d/50unattended-upgrades SSH Security Configuration
# Edit SSH configuration
sudo nano /etc/ssh/sshd_config
# Recommended SSH settings:
Port 2222 # Change from default 22
Protocol 2 # Use SSH protocol 2
PermitRootLogin no # Disable root login
PasswordAuthentication no # Use key-based auth only
PubkeyAuthentication yes # Enable public key auth
MaxAuthTries 3 # Limit login attempts
ClientAliveInterval 300 # 5 minute timeout
ClientAliveCountMax 2 # Max client alive messages
MaxStartups 2 # Limit concurrent connections
LoginGraceTime 60 # 60 second login grace
AllowUsers yourusername # Only allow specific users
# Restart SSH service
sudo systemctl restart sshd
# Set up key-based authentication
ssh-keygen -t ed25519 -b 4096
ssh-copy-id -p 2222 user@your-server Firewall Configuration
# Install and configure UFW firewall
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow essential services
sudo ufw allow 2222/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
# Allow specific IPs for admin access (replace with your IP)
sudo ufw allow from YOUR.IP.ADDRESS.HERE to any port 2222
# Enable firewall
sudo ufw --force enable
sudo ufw status verbose
# Configure fail2ban for intrusion prevention
sudo apt install fail2ban -y
# Create WHMCS-specific fail2ban jail
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[whmcs-admin]
enabled = true
port = http,https
filter = whmcs-admin
logpath = /var/log/nginx/access.log
maxretry = 3
bantime = 3600
[whmcs-client]
enabled = true
port = http,https
filter = whmcs-client
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 1800 Web Server Security
Nginx Security Configuration
# /etc/nginx/sites-available/whmcs-secure
server {
listen 443 ssl http2;
server_name your-domain.com;
root /var/www/whmcs;
index index.php;
# SSL Configuration
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
# Hide Nginx version
server_tokens off;
# Protect sensitive directories
location ~ ^/(admin|includes|templates_c|lang|attachments|downloads|vendor)/ {
location ~ ^/admin/ {
allow YOUR.IP.ADDRESS.HERE; # Your IP
deny all;
try_files $uri $uri/ /admin/index.php?$query_string;
}
location ~ ^/(includes|templates_c|lang|attachments|downloads|vendor)/ {
deny all;
return 404;
}
}
# Block access to sensitive files
location ~ /\.(ht|env|git) {
deny all;
return 404;
}
# Block access to backup and log files
location ~* \.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)$ {
deny all;
return 404;
}
# Rate limiting
limit_req_zone $binary_remote_addr zone=admin:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=general:10m rate=60r/m;
location /admin {
limit_req zone=admin burst=3 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
location /includes/api.php {
limit_req zone=api burst=10 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
location / {
limit_req zone=general burst=20 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
# PHP processing
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Security parameters
fastcgi_param HTTP_PROXY "";
fastcgi_param SERVER_NAME $host;
fastcgi_intercept_errors on;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
} PHP Security Hardening
# Edit PHP configuration
sudo nano /etc/php/8.1/fpm/php.ini
# Security settings
expose_php = Off
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/error.log
# Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_get_contents,file_put_contents,fopen,fwrite
# File upload restrictions
file_uploads = On
upload_max_filesize = 10M
max_file_uploads = 5
upload_tmp_dir = /var/lib/php/uploads
# Session security
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1
session.cookie_samesite = "Strict"
# Memory and execution limits
memory_limit = 256M
max_execution_time = 300
max_input_time = 60
post_max_size = 50M
# Hide PHP information
expose_php = Off
allow_url_fopen = Off
allow_url_include = Off Database Security
MySQL Security Configuration
# Run MySQL security script
sudo mysql_secure_installation
# MySQL configuration hardening
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
# Bind to localhost only
bind-address = 127.0.0.1
# Disable remote root access
skip-networking = false
local-infile = 0
# Enable SSL
ssl-ca = /var/lib/mysql/ca.pem
ssl-cert = /var/lib/mysql/server-cert.pem
ssl-key = /var/lib/mysql/server-key.pem
# Logging
general_log = 1
general_log_file = /var/log/mysql/general.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# Security settings
validate_password.policy = MEDIUM
validate_password.length = 12
validate_password.mixed_case_count = 1
validate_password.number_count = 1
validate_password.special_char_count = 1
# Create dedicated WHMCS database user
mysql -u root -p
CREATE DATABASE whmcs_production;
CREATE USER 'whmcs_user'@'localhost' IDENTIFIED BY 'complex_password_here!123';
GRANT SELECT, INSERT, UPDATE, DELETE ON whmcs_production.* TO 'whmcs_user'@'localhost';
FLUSH PRIVILEGES;
# Remove test database and anonymous users
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
FLUSH PRIVILEGES; WHMCS Application Security
File Permissions & Ownership
# Set correct ownership
sudo chown -R www-data:www-data /var/www/whmcs/
# Set secure file permissions
find /var/www/whmcs/ -type f -exec chmod 644 {} \;
find /var/www/whmcs/ -type d -exec chmod 755 {} \;
# Secure sensitive directories
chmod 750 /var/www/whmcs/admin/
chmod 750 /var/www/whmcs/includes/
chmod 750 /var/www/whmcs/vendor/
chmod 777 /var/www/whmcs/templates_c/
chmod 777 /var/www/whmcs/attachments/
chmod 777 /var/www/whmcs/downloads/
# Protect configuration file
chmod 600 /var/www/whmcs/configuration.php
chown www-data:www-data /var/www/whmcs/configuration.php
# Create .htaccess files for protection
echo "deny from all" > /var/www/whmcs/includes/.htaccess
echo "deny from all" > /var/www/whmcs/vendor/.htaccess
echo "deny from all" > /var/www/whmcs/templates_c/.htaccess WHMCS Configuration Security
# Edit WHMCS configuration.php
nano /var/www/whmcs/configuration.php
# Essential security configurations:
# Strong database password
$db_password = "complex_secure_password_here!123@456";
# Secure admin directory (rename from default)
$customadminpath = "secure_admin_panel_name";
# Enable security features
$disable_webupdater = true;
$maintenance_mode = false;
# Template security
$compiledir = "/var/www/whmcs/templates_c/";
$customclientsdir = "";
# API security
$api_access_key = "your_strong_api_key_here";
$api_secret_key = "your_secret_api_key_here";
# SSL and cookie security
$force_ssl = true;
$cookie_domain = "your-domain.com";
$cookie_path = "/";
# Additional security settings in Admin Area:
# Security Tab Settings:
- Enable "Block Tor Access"
- Enable "Two Factor Authentication"
- Set "Admin Session Timeout" to 30 minutes
- Enable "Admin IP Access Restriction"
- Set strong "Admin Security Questions"
# System Settings:
- Disable "Allow customers to manage products"
- Enable "Log All Activity"
- Set "Inactive Client After" to 90 days
- Enable "Delete Inactive Clients" Access Control & Authentication
Two-Factor Authentication Setup
Admin 2FA Configuration
- 1. Navigate to Setup → Staff Management → Administrator Roles
- 2. Edit each admin role and enable "Require 2FA"
- 3. Go to Setup → General Settings → Security
- 4. Enable "Two Factor Authentication"
- 5. Set "2FA Enforcement" to "Required for Admin Users"
- 6. Each admin must set up 2FA using Google Authenticator or similar app
Admin IP Restrictions
# Method 1: WHMCS Admin IP Restriction
# In WHMCS Admin Area:
# Setup → General Settings → Security → Admin IP Access Restriction
# Add your static IPs (comma-separated):
203.0.113.1,203.0.113.2,203.0.113.0/24
# Method 2: Nginx IP restriction
# Add to your admin location block:
location /secure_admin_panel_name/ {
allow 203.0.113.1; # Your office IP
allow 203.0.113.2; # Your home IP
allow 203.0.113.0/24; # Your IP range
deny all;
limit_req zone=admin burst=3 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
# Method 3: Firewall-level restriction
# Allow admin access only from specific IPs
sudo ufw allow from 203.0.113.1 to any port 443 comment 'Admin access'
sudo ufw allow from 203.0.113.2 to any port 443 comment 'Admin access' Monitoring & Logging
Comprehensive Logging Setup
# Install log monitoring tools
sudo apt install logwatch rsyslog-gnutls -y
# Configure centralized logging
sudo nano /etc/rsyslog.conf
# Add these lines:
$ModLoad imfile
$InputFilePollInterval 10
# WHMCS access logs
$InputFileName /var/log/nginx/whmcs-access.log
$InputFileTag whmcs-access:
$InputFileStateFile whmcs-access
$InputFileSeverity info
$InputFileFacility local0
$InputRunFileMonitor
# WHMCS error logs
$InputFileName /var/log/nginx/whmcs-error.log
$InputFileTag whmcs-error:
$InputFileStateFile whmcs-error
$InputFileSeverity error
$InputFileFacility local1
$InputRunFileMonitor
# Set up log rotation
sudo nano /etc/logrotate.d/whmcs
/var/log/nginx/whmcs-*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 www-data www-data
postrotate
systemctl reload nginx
endscript
}
# Install and configure log monitoring
sudo apt install fail2ban-server -y
# Create WHMCS-specific fail2ban filters
sudo nano /etc/fail2ban/filter.d/whmcs-admin.conf
[Definition]
failregex = ^<HOST> - .* "POST /admin/login.php.*" (4|5)\d\d
^<HOST> - .* "POST /admin/.*" 401
ignoreregex =
sudo nano /etc/fail2ban/filter.d/whmcs-client.conf
[Definition]
failregex = ^<HOST> - .* "POST /dologin.php.*" (4|5)\d\d
^<HOST> - .* "POST /password.php.*" 429
ignoreregex = Security Monitoring Scripts
#!/bin/bash
# WHMCS Security Monitor Script
# Save as /opt/whmcs-monitor.sh
WHMCS_PATH="/var/www/whmcs"
LOG_FILE="/var/log/whmcs-security.log"
EMAIL="[email protected]"
# Check file permissions
echo "$(date): Checking file permissions..." >> $LOG_FILE
# Critical files should not be world-readable
find $WHMCS_PATH -name "configuration.php" -perm /004 -exec echo "ALERT: configuration.php is world-readable!" >> $LOG_FILE \;
# Check for suspicious files
find $WHMCS_PATH -name "*.php" -type f -mmin -60 | while read file; do
if grep -q "eval\|base64_decode\|shell_exec" "$file"; then
echo "ALERT: Suspicious code found in $file" >> $LOG_FILE
mail -s "WHMCS Security Alert" $EMAIL < $LOG_FILE
fi
done
# Check database connection logs
MYSQL_LOG="/var/log/mysql/general.log"
if [ -f $MYSQL_LOG ]; then
# Look for multiple failed connection attempts
FAILED_LOGINS=$(tail -1000 $MYSQL_LOG | grep -c "Access denied")
if [ $FAILED_LOGINS -gt 10 ]; then
echo "ALERT: $FAILED_LOGINS failed database login attempts detected!" >> $LOG_FILE
mail -s "Database Security Alert" $EMAIL < $LOG_FILE
fi
fi
# Check for admin login attempts from unusual IPs
ACCESS_LOG="/var/log/nginx/whmcs-access.log"
if [ -f $ACCESS_LOG ]; then
# Extract IPs accessing admin panel in last hour
ADMIN_IPS=$(tail -1000 $ACCESS_LOG | grep "/admin/" | grep "$(date '+%d/%b/%Y:%H')" | awk '{print $1}' | sort | uniq)
# Check against whitelist (replace with your IPs)
WHITELIST=("203.0.113.1" "203.0.113.2")
for ip in $ADMIN_IPS; do
if [[ ! " ${WHITELIST[@]} " =~ " ${ip} " ]]; then
echo "ALERT: Admin access from unauthorized IP: $ip" >> $LOG_FILE
mail -s "Unauthorized Admin Access" $EMAIL < $LOG_FILE
fi
done
fi
# Make script executable and add to cron
chmod +x /opt/whmcs-monitor.sh
# Add to crontab (run every 30 minutes)
(crontab -l 2>/dev/null; echo "*/30 * * * * /opt/whmcs-monitor.sh") | crontab - Backup & Recovery Security
Secure Backup Implementation
#!/bin/bash
# Secure WHMCS Backup Script
# Save as /opt/whmcs-backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/secure/backups/whmcs"
WHMCS_PATH="/var/www/whmcs"
DB_NAME="whmcs_production"
DB_USER="backup_user"
DB_PASS="secure_backup_password"
ENCRYPTION_KEY="your_strong_encryption_key_here"
# Create backup directory
mkdir -p $BACKUP_DIR/$DATE
# Database backup with encryption
mysqldump -u $DB_USER -p$DB_PASS $DB_NAME | \
gzip | \
openssl enc -aes-256-cbc -salt -k $ENCRYPTION_KEY > \
$BACKUP_DIR/$DATE/database.sql.gz.enc
# File backup with encryption (exclude sensitive files)
tar --exclude='templates_c' \
--exclude='downloads' \
--exclude='attachments' \
-czf - $WHMCS_PATH | \
openssl enc -aes-256-cbc -salt -k $ENCRYPTION_KEY > \
$BACKUP_DIR/$DATE/files.tar.gz.enc
# Create checksums
cd $BACKUP_DIR/$DATE
sha256sum *.enc > checksums.txt
# Upload to remote storage (AWS S3 example)
aws s3 sync $BACKUP_DIR/$DATE s3://your-backup-bucket/whmcs/$DATE/ \
--storage-class GLACIER \
--server-side-encryption AES256
# Clean up old local backups (keep 7 days)
find $BACKUP_DIR -type d -mtime +7 -exec rm -rf {} \;
# Log backup completion
echo "$(date): Backup completed successfully - $DATE" >> /var/log/whmcs-backup.log
# Set permissions
chmod 600 /opt/whmcs-backup.sh
chown root:root /opt/whmcs-backup.sh
# Schedule daily backups at 2 AM
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/whmcs-backup.sh") | crontab - Security Checklist Summary
Server Security
- OS updated and hardened
- SSH secured with keys only
- Firewall configured (UFW/iptables)
- Fail2ban installed and configured
- Unnecessary services disabled
- Automatic security updates enabled
- Log monitoring setup
Web Server Security
- SSL/TLS properly configured
- Security headers implemented
- Rate limiting configured
- Server signature hidden
- Directory restrictions in place
- File access controls configured
- PHP security hardened
Database Security
- MySQL secured and updated
- Dedicated database user created
- Strong passwords enforced
- Remote access disabled
- SSL connections enabled
- Query logging enabled
- Regular backups scheduled
WHMCS Security
- Admin directory renamed
- Two-factor authentication enabled
- IP access restrictions configured
- File permissions properly set
- Configuration file secured
- Activity logging enabled
- Security questions configured
Critical Security Reminders
- • Change all default passwords immediately
- • Keep WHMCS updated to latest version
- • Monitor security logs daily
- • Test backups monthly
- • Review user permissions quarterly
- • Conduct security audits annually
- • Have incident response plan ready
- • Train staff on security practices
- • Subscribe to WHMCS security notifications
- • Consider professional security assessment
Implementation Priority
Critical (Day 1)
- • SSL certificate installation
- • Admin directory rename
- • Strong passwords
- • Basic firewall
- • File permissions
Important (Week 1)
- • Two-factor authentication
- • IP restrictions
- • Log monitoring
- • Backup system
- • Fail2ban setup
Enhanced (Month 1)
- • Advanced monitoring
- • Security automation
- • Incident response plan
- • Security training
- • Regular audits
Secure Your WHMCS Installation Today
Don't wait for a security breach to happen. I provide complete WHMCS security audits and hardening services that implement all these measures and more. Protect your business, customers, and revenue with enterprise-grade security.
About Shahid Malla
ExpertFull Stack Developer with 10+ years of experience in WHMCS development, WordPress, and server management. Trusted by 600+ clients worldwide for hosting automation and custom solutions.