Shahid Malla

WHMCS Security Checklist for Production Servers

Shahid Malla Shahid MallaFebruary 3, 202618 min read
WHMCS Security Checklist for Production Servers

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. 1. Navigate to Setup → Staff Management → Administrator Roles
  2. 2. Edit each admin role and enable "Require 2FA"
  3. 3. Go to Setup → General Settings → Security
  4. 4. Enable "Two Factor Authentication"
  5. 5. Set "2FA Enforcement" to "Required for Admin Users"
  6. 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.

Share this article:
Shahid Malla

About Shahid Malla

Expert

Full 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.