902ac18c80
Issues: - Script was running php -l (syntax checker) on every file with 500 error - With 7555 errors, this meant running php -l thousands of times - Each php -l takes 100-500ms, causing multi-minute delays Changes: - Removed php -l syntax checking (was causing major slowdown) - Added progress indicator showing "Analyzed X / Y errors..." - Progress updates every 500 errors to show script is working - Completion message when diagnosis finishes Result: Diagnosis now completes in seconds instead of minutes. Users still get comprehensive checks for .htaccess, permissions, file existence, docroot, PHP handler, and WordPress issues.
780 lines
37 KiB
Bash
Executable File
780 lines
37 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
################################################################################
|
||
# Fast 500 Error Tracker
|
||
################################################################################
|
||
# Purpose: ONLY track 500 errors - find them fast and show WHY
|
||
# No menus, no options - just find 500s and diagnose the root cause
|
||
################################################################################
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||
source "$SCRIPT_DIR/lib/common-functions.sh"
|
||
|
||
# Ensure color variables are set
|
||
DIM='\033[2m'
|
||
BOLD='\033[1m'
|
||
|
||
print_banner "Fast 500 Error Tracker"
|
||
|
||
echo ""
|
||
echo "Scanning for 500 errors and diagnosing root causes..."
|
||
echo ""
|
||
|
||
# Ask for time range
|
||
echo -e "${CYAN}How far back to scan?${NC}"
|
||
echo " 1) Last 24 hours (default)"
|
||
echo " 2) Last 7 days"
|
||
echo " 3) Last 30 days"
|
||
echo ""
|
||
read -p "Select option [1]: " time_choice
|
||
time_choice=${time_choice:-1}
|
||
|
||
case $time_choice in
|
||
1) HOURS_TO_SCAN=24 ;;
|
||
2) HOURS_TO_SCAN=168 ;;
|
||
3) HOURS_TO_SCAN=720 ;;
|
||
*) HOURS_TO_SCAN=24 ;;
|
||
esac
|
||
|
||
echo ""
|
||
echo "→ Scanning last $HOURS_TO_SCAN hours of access logs..."
|
||
echo ""
|
||
|
||
# Temporary files
|
||
TEMP_DIR="/tmp/500-tracker-$$"
|
||
mkdir -p "$TEMP_DIR"
|
||
trap "rm -rf $TEMP_DIR" EXIT
|
||
|
||
ERRORS_500="$TEMP_DIR/errors_500.txt"
|
||
ERROR_DETAILS="$TEMP_DIR/error_details.txt"
|
||
|
||
# Configuration
|
||
DOMLOGS_DIR="/var/log/apache2/domlogs"
|
||
|
||
# Get cutoff time
|
||
cutoff_time=$(date -d "$HOURS_TO_SCAN hours ago" +%s 2>/dev/null || echo "0")
|
||
|
||
declare -A domain_count
|
||
declare -A domain_user
|
||
total_500s=0
|
||
filtered_bots=0
|
||
|
||
# Scan all domain access logs for 500 errors (including subdirectories)
|
||
while IFS= read -r log; do
|
||
[ -f "$log" ] || continue
|
||
[[ "$log" =~ (bytes_log|offset|error_log|ftpxferlog|-ssl_log)$ ]] && continue
|
||
|
||
# Extract domain from log filename
|
||
domain="${log##*/}"
|
||
domain="${domain%%-*}"
|
||
|
||
# Skip non-domain system logs (proxy, localhost, etc.)
|
||
[[ "$domain" =~ ^(proxy|localhost|default|cpanel|webmail|whm|cpcalendars|cpcontacts|webdisk)$ ]] && continue
|
||
|
||
# Find cPanel user for this domain
|
||
user=$(grep -l "DNS.*$domain" /var/cpanel/users/* 2>/dev/null | head -1 | xargs basename 2>/dev/null)
|
||
[ -z "$user" ] && user="unknown"
|
||
|
||
domain_user["$domain"]="$user"
|
||
|
||
# Scan for 500 errors in this log
|
||
while IFS= read -r line; do
|
||
# Check for 500 status code
|
||
if [[ "$line" =~ '"'[[:space:]](5[0-9]{2})[[:space:]] ]]; then
|
||
status="${BASH_REMATCH[1]}"
|
||
|
||
# Time filtering
|
||
if [ "$cutoff_time" != "0" ]; then
|
||
if [[ "$line" =~ \[([0-9]{2}/[A-Z][a-z]{2}/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2}) ]]; then
|
||
log_date="${BASH_REMATCH[1]}"
|
||
log_time=$(date -d "${log_date/:/ }" +%s 2>/dev/null || echo "0")
|
||
[ "$log_time" != "0" ] && [ "$log_time" -lt "$cutoff_time" ] && continue
|
||
fi
|
||
fi
|
||
|
||
# Parse log line to get IP first
|
||
read -r ip _ _ timestamp _ request _ _ <<< "$line"
|
||
|
||
# IP-based filtering - skip non-relevant IPs
|
||
# Skip localhost/internal IPs
|
||
if [[ "$ip" =~ ^(127\.|::1|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.) ]]; then
|
||
((filtered_bots++))
|
||
continue
|
||
fi
|
||
|
||
# Skip common cloud scanner IPs and known bot networks
|
||
# AWS, GCP, Azure scanner ranges, security scanners
|
||
if [[ "$ip" =~ ^(3\.|13\.|18\.|34\.|35\.|52\.|54\.) ]] || \
|
||
[[ "$ip" =~ ^(104\.196\.|104\.154\.|130\.211\.|35\.184\.|35\.185\.|35\.186\.|35\.187\.|35\.188\.|35\.189\.|35\.19) ]] || \
|
||
[[ "$ip" =~ ^(20\.|40\.|51\.|52\.|13\.64\.|13\.65\.|13\.66\.|13\.67\.|13\.68\.) ]]; then
|
||
# Check if it's actually a bot by examining user agent
|
||
line_lower="${line,,}"
|
||
if [[ "$line_lower" =~ (bot|crawler|spider|scanner|monitor|cloud|amazon|google|microsoft|azure) ]]; then
|
||
((filtered_bots++))
|
||
continue
|
||
fi
|
||
fi
|
||
|
||
# Bot/Scanner filtering - skip noise
|
||
line_lower="${line,,}"
|
||
if [[ "$line_lower" =~ (bot|crawler|spider|scraper|scanner|check|monitor|uptime|pingdom|newrelic|datadog|nagios|zabbix|prtg|gomez|keynote|catchpoint|dotcom-monitor|site24x7|uptimerobot|statuscake|nodequery|hetrixtools|freshping|uptrendscom|siteuptime|montastic|updown\.io|apex|alertsite|webmon|wormly) ]]; then
|
||
((filtered_bots++))
|
||
continue
|
||
fi
|
||
if [[ "$line_lower" =~ (semrush|ahrefs|moz|majestic|serpstat|screaming|screamingfrog|sitebulb|linkchecker|validator|scanner|security|acunetix|nessus|openvas|burp|nikto|skipfish|w3af|sqlmap|metasploit|nmap|masscan|zmap|shodan|censys|binaryedge) ]]; then
|
||
((filtered_bots++))
|
||
continue
|
||
fi
|
||
if [[ "$line_lower" =~ (curl|wget|python|perl|ruby|java|go-http|libwww|axios|node-fetch|http\.client|httpie|postman|insomnia|apachehttp|okhttp|httpclient) ]]; then
|
||
((filtered_bots++))
|
||
continue
|
||
fi
|
||
|
||
# Extract URL
|
||
if [[ "$request" =~ '"'[A-Z]+[[:space:]]([^[:space:]]+) ]]; then
|
||
url="${BASH_REMATCH[1]:0:80}"
|
||
else
|
||
url="/"
|
||
fi
|
||
|
||
# Extract timestamp
|
||
if [[ "$line" =~ \[([^]]+)\] ]]; then
|
||
timestamp="${BASH_REMATCH[1]}"
|
||
else
|
||
timestamp="unknown"
|
||
fi
|
||
|
||
((domain_count["$domain"]++))
|
||
((total_500s++))
|
||
|
||
# Save for analysis
|
||
echo "$domain|$user|$status|$url|$timestamp|$ip" >> "$ERRORS_500"
|
||
fi
|
||
done < <(tail -n 100000 "$log" 2>/dev/null)
|
||
done < <(find "$DOMLOGS_DIR" -type f ! -name "*bytes_log" ! -name "*offset*" ! -name "*error_log" ! -name "*ftpxferlog*" ! -name "*-ssl_log" 2>/dev/null)
|
||
|
||
if [ "$total_500s" -eq 0 ]; then
|
||
echo ""
|
||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${GREEN} ✓ NO 500 ERRORS FOUND IN LAST $HOURS_TO_SCAN HOURS!${NC}"
|
||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo ""
|
||
exit 0
|
||
fi
|
||
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " 500 ERRORS DETECTED: $total_500s errors (filtered out $filtered_bots bot/scanner requests)"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
echo -e "${RED}${BOLD}TOP AFFECTED DOMAINS:${NC}"
|
||
echo ""
|
||
|
||
# Sort and display top domains
|
||
for domain in "${!domain_count[@]}"; do
|
||
echo "${domain_count[$domain]} $domain ${domain_user[$domain]}"
|
||
done | sort -rn | head -10 | while read count domain user; do
|
||
printf " ${RED}●${NC} %-40s ${BOLD}%4d errors${NC} (user: %s)\n" "$domain" "$count" "$user"
|
||
done
|
||
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " DIAGNOSING ROOT CAUSES"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
# Detailed diagnostic tracking - per URL/file
|
||
DETAILED_DIAGNOSIS="$TEMP_DIR/detailed_diagnosis.txt"
|
||
> "$DETAILED_DIAGNOSIS"
|
||
|
||
declare -A diagnosed_causes
|
||
declare -A cause_examples
|
||
|
||
total_to_diagnose=$(wc -l < "$ERRORS_500")
|
||
current_line=0
|
||
|
||
echo "Analyzing $total_to_diagnose errors for root causes..."
|
||
echo ""
|
||
|
||
while IFS='|' read -r domain user status url timestamp ip; do
|
||
[ -z "$domain" ] && continue
|
||
|
||
((current_line++))
|
||
# Show progress every 500 errors
|
||
if [ $((current_line % 500)) -eq 0 ]; then
|
||
echo -ne "\rAnalyzed $current_line / $total_to_diagnose errors..."
|
||
fi
|
||
|
||
diagnosis=""
|
||
cause="UNKNOWN"
|
||
specific_file=""
|
||
|
||
# Get document root
|
||
docroot="/home/$user/public_html"
|
||
|
||
# Try to determine the actual file being requested
|
||
if [[ "$url" =~ ^/([^?]+) ]]; then
|
||
url_path="${BASH_REMATCH[1]}"
|
||
|
||
# Handle common patterns
|
||
if [ -z "$url_path" ] || [ "$url_path" = "/" ]; then
|
||
possible_files=("$docroot/index.php" "$docroot/index.html")
|
||
elif [[ "$url_path" =~ \.php$ ]]; then
|
||
possible_files=("$docroot/$url_path")
|
||
else
|
||
possible_files=("$docroot/$url_path" "$docroot/${url_path}.php" "$docroot/$url_path/index.php")
|
||
fi
|
||
|
||
# Find the actual file
|
||
for pf in "${possible_files[@]}"; do
|
||
if [ -f "$pf" ]; then
|
||
specific_file="$pf"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Find PHP error log - check multiple locations
|
||
error_log=""
|
||
if [ "$user" != "unknown" ]; then
|
||
for potential_log in \
|
||
"/home/$user/public_html/error_log" \
|
||
"/home/$user/logs/error_log" \
|
||
"/home/$user/error_log" \
|
||
"/var/log/apache2/domlogs/$domain-error_log" \
|
||
"/usr/local/apache/domlogs/$domain"; do
|
||
|
||
if [ -f "$potential_log" ]; then
|
||
error_log="$potential_log"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Check if error log exists and has recent errors
|
||
if [ -n "$error_log" ] && [ -f "$error_log" ]; then
|
||
# Look for errors matching this URL/timestamp
|
||
recent_error=$(tail -1000 "$error_log" | grep -F "$url" | tail -1)
|
||
|
||
# If no URL match, get most recent error
|
||
[ -z "$recent_error" ] && recent_error=$(tail -500 "$error_log" | grep -E "Fatal error|Parse error|syntax error|memory.*exhausted|database|MySQL|Permission denied|failed to open stream" | tail -1)
|
||
|
||
if [ -n "$recent_error" ]; then
|
||
# Extract file path from error if present
|
||
if [[ "$recent_error" =~ in[[:space:]](/[^:]+\.php) ]]; then
|
||
error_file="${BASH_REMATCH[1]}"
|
||
specific_file="$error_file"
|
||
fi
|
||
|
||
# Determine cause and diagnose
|
||
if [[ "$recent_error" =~ (memory.*exhausted|Allowed memory size) ]]; then
|
||
cause="PHP_MEMORY_EXHAUSTED"
|
||
|
||
# Check current memory limit
|
||
mem_limit=$(grep -h "memory_limit" "$docroot/.user.ini" "$docroot/../.php.ini" 2>/dev/null | tail -1 | cut -d'=' -f2 | tr -d ' ')
|
||
[ -z "$mem_limit" ] && mem_limit=$(php -r "echo ini_get('memory_limit');" 2>/dev/null)
|
||
|
||
diagnosis="$domain$url - Memory exhausted (current limit: ${mem_limit:-unknown})"
|
||
[ -n "$specific_file" ] && diagnosis="$diagnosis - File: $specific_file"
|
||
|
||
elif [[ "$recent_error" =~ (failed to open stream|Permission denied) ]]; then
|
||
cause="PERMISSION_ERROR"
|
||
|
||
# Extract the file with permission issue
|
||
if [[ "$recent_error" =~ failed\ to\ open\ stream.*\'([^\']+)\' ]]; then
|
||
perm_file="${BASH_REMATCH[1]}"
|
||
elif [[ "$recent_error" =~ Permission\ denied.*\'([^\']+)\' ]]; then
|
||
perm_file="${BASH_REMATCH[1]}"
|
||
else
|
||
perm_file="$specific_file"
|
||
fi
|
||
|
||
# Check permissions on that specific file
|
||
if [ -n "$perm_file" ] && [ -e "$perm_file" ]; then
|
||
file_perms=$(stat -c "%a" "$perm_file" 2>/dev/null)
|
||
file_owner=$(stat -c "%U:%G" "$perm_file" 2>/dev/null)
|
||
diagnosis="$domain$url - Permission denied on: $perm_file (perms: $file_perms, owner: $file_owner)"
|
||
|
||
# Check if file is readable by Apache
|
||
if [ -f "$perm_file" ]; then
|
||
if ! sudo -u nobody test -r "$perm_file" 2>/dev/null; then
|
||
diagnosis="$diagnosis - File NOT readable by Apache (nobody user)"
|
||
fi
|
||
fi
|
||
else
|
||
diagnosis="$domain$url - Permission denied (file in error: $perm_file - does not exist)"
|
||
fi
|
||
|
||
elif [[ "$recent_error" =~ (Fatal error|PHP Fatal error) ]]; then
|
||
if [[ "$recent_error" =~ (undefined function|Call to undefined function) ]]; then
|
||
cause="MISSING_PHP_FUNCTION"
|
||
|
||
# Extract function name
|
||
if [[ "$recent_error" =~ Call\ to\ undefined\ function\ ([a-zA-Z0-9_]+) ]]; then
|
||
missing_func="${BASH_REMATCH[1]}"
|
||
|
||
# Check which extension provides this function
|
||
ext_info=$(php -r "if(function_exists('$missing_func')) echo 'EXISTS'; else echo 'MISSING';" 2>&1)
|
||
|
||
diagnosis="$domain$url - Missing function: $missing_func"
|
||
[ -n "$specific_file" ] && diagnosis="$diagnosis - in file: $specific_file"
|
||
else
|
||
diagnosis="$domain$url - Missing PHP function - $recent_error"
|
||
fi
|
||
else
|
||
cause="PHP_FATAL_ERROR"
|
||
diagnosis="$domain$url - PHP Fatal Error"
|
||
[ -n "$specific_file" ] && diagnosis="$diagnosis - File: $specific_file"
|
||
diagnosis="$diagnosis - ${recent_error:0:200}"
|
||
fi
|
||
|
||
elif [[ "$recent_error" =~ (Parse error|syntax error) ]]; then
|
||
cause="PHP_SYNTAX_ERROR"
|
||
|
||
# Extract line number if present
|
||
if [[ "$recent_error" =~ line\ ([0-9]+) ]]; then
|
||
error_line="${BASH_REMATCH[1]}"
|
||
diagnosis="$domain$url - PHP Syntax Error at line $error_line"
|
||
else
|
||
diagnosis="$domain$url - PHP Syntax Error"
|
||
fi
|
||
[ -n "$specific_file" ] && diagnosis="$diagnosis - File: $specific_file"
|
||
|
||
elif [[ "$recent_error" =~ (database|MySQL|Can\'t connect|Access denied for user) ]]; then
|
||
cause="DATABASE_CONNECTION"
|
||
|
||
if [[ "$recent_error" =~ Access\ denied\ for\ user\ \'([^\']+)\' ]]; then
|
||
db_user="${BASH_REMATCH[1]}"
|
||
diagnosis="$domain$url - Database access denied for user: $db_user"
|
||
elif [[ "$recent_error" =~ Unknown\ database\ \'([^\']+)\' ]]; then
|
||
db_name="${BASH_REMATCH[1]}"
|
||
diagnosis="$domain$url - Unknown database: $db_name"
|
||
else
|
||
diagnosis="$domain$url - Database connection error"
|
||
fi
|
||
[ -n "$specific_file" ] && diagnosis="$diagnosis - from file: $specific_file"
|
||
fi
|
||
|
||
# Save detailed diagnosis only if we identified a specific cause
|
||
if [ "$cause" != "UNKNOWN" ] && [ -n "$diagnosis" ]; then
|
||
echo "$cause|$diagnosis" >> "$DETAILED_DIAGNOSIS"
|
||
((diagnosed_causes["$cause"]++))
|
||
[ -z "${cause_examples[$cause]}" ] && cause_examples["$cause"]="$diagnosis"
|
||
else
|
||
# No matching error in error_log - run comprehensive checks
|
||
if [ "$user" != "unknown" ]; then
|
||
issue_found=""
|
||
|
||
# Check 1: .htaccess issues
|
||
if [ -f "$docroot/.htaccess" ]; then
|
||
htaccess_file="$docroot/.htaccess"
|
||
htaccess_issues=""
|
||
|
||
# Invalid PHP directives
|
||
if grep -qE "^[[:space:]]*(php_value|php_flag|php_admin_value|php_admin_flag)" "$htaccess_file" 2>/dev/null; then
|
||
if ! apache2ctl -M 2>/dev/null | grep -q "php.*module"; then
|
||
htaccess_issues="PHP directives incompatible with FPM"
|
||
fi
|
||
fi
|
||
|
||
# Malformed RewriteRule
|
||
bad_rewrite=$(grep -nE "RewriteRule.*\[.*[^]]*$" "$htaccess_file" 2>/dev/null | head -1)
|
||
[ -n "$bad_rewrite" ] && htaccess_issues="${htaccess_issues:+$htaccess_issues; }Malformed RewriteRule: ${bad_rewrite:0:50}"
|
||
|
||
# Missing RewriteBase with RewriteRule
|
||
if grep -q "RewriteRule" "$htaccess_file" 2>/dev/null; then
|
||
if ! grep -q "RewriteBase" "$htaccess_file" 2>/dev/null; then
|
||
# Check if rules use relative paths
|
||
if grep -qE "RewriteRule.*\^[^/]" "$htaccess_file" 2>/dev/null; then
|
||
htaccess_issues="${htaccess_issues:+$htaccess_issues; }Missing RewriteBase with relative paths"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
if [ -n "$htaccess_issues" ]; then
|
||
cause="HTACCESS_ERROR"
|
||
diagnosis="$domain$url - $htaccess_issues"
|
||
issue_found="yes"
|
||
fi
|
||
fi
|
||
|
||
# Check 2: PHP file exists and is readable
|
||
if [ -z "$issue_found" ] && [ -n "$specific_file" ]; then
|
||
if [ ! -f "$specific_file" ]; then
|
||
cause="FILE_NOT_FOUND"
|
||
diagnosis="$domain$url - File does not exist: $specific_file"
|
||
issue_found="yes"
|
||
elif [ ! -r "$specific_file" ]; then
|
||
file_perms=$(stat -c "%a" "$specific_file" 2>/dev/null)
|
||
cause="PERMISSION_ERROR"
|
||
diagnosis="$domain$url - File not readable: $specific_file (perms: $file_perms)"
|
||
issue_found="yes"
|
||
fi
|
||
fi
|
||
|
||
# Check 3: Document root permissions
|
||
if [ -z "$issue_found" ]; then
|
||
if [ ! -d "$docroot" ]; then
|
||
cause="DOCROOT_MISSING"
|
||
diagnosis="$domain$url - Document root does not exist: $docroot"
|
||
issue_found="yes"
|
||
else
|
||
docroot_perms=$(stat -c "%a" "$docroot" 2>/dev/null)
|
||
if [ "$docroot_perms" != "755" ] && [ "$docroot_perms" != "750" ] && [ "$docroot_perms" != "711" ]; then
|
||
cause="PERMISSION_ERROR"
|
||
diagnosis="$domain$url - Docroot bad perms: $docroot ($docroot_perms, should be 755)"
|
||
issue_found="yes"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Check 4: PHP handler/version issues
|
||
if [ -z "$issue_found" ] && [ -n "$specific_file" ] && [ -f "$specific_file" ]; then
|
||
# Check if PHP is configured for this domain
|
||
php_handler=$(grep -i "phpversion\|php_admin_value" /var/cpanel/userdata/$user/$domain 2>/dev/null | head -1)
|
||
if [ -z "$php_handler" ]; then
|
||
# Check if .htaccess tries to set PHP version but fails
|
||
if grep -qE "AddHandler.*php|SetHandler.*php" "$docroot/.htaccess" 2>/dev/null; then
|
||
cause="PHP_HANDLER_ERROR"
|
||
diagnosis="$domain$url - PHP handler misconfigured (check .htaccess AddHandler/SetHandler)"
|
||
issue_found="yes"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Check 5: WordPress-specific issues (if WP detected)
|
||
if [ -z "$issue_found" ] && [ -f "$docroot/wp-config.php" ]; then
|
||
# Check if wp-config.php is readable
|
||
if [ ! -r "$docroot/wp-config.php" ]; then
|
||
cause="PERMISSION_ERROR"
|
||
diagnosis="$domain$url - wp-config.php not readable"
|
||
issue_found="yes"
|
||
else
|
||
# Check for WP_DEBUG causing issues
|
||
if grep -q "define.*WP_DEBUG.*true" "$docroot/wp-config.php" 2>/dev/null; then
|
||
# Check if WP_DEBUG_DISPLAY is also true (can cause 500s with some errors)
|
||
if grep -q "define.*WP_DEBUG_DISPLAY.*true" "$docroot/wp-config.php" 2>/dev/null; then
|
||
cause="WP_DEBUG_ERROR"
|
||
diagnosis="$domain$url - WP_DEBUG_DISPLAY enabled (may cause 500 on warnings)"
|
||
issue_found="yes"
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Save diagnosis if we found an issue
|
||
if [ -n "$issue_found" ] && [ -n "$diagnosis" ]; then
|
||
echo "$cause|$diagnosis" >> "$DETAILED_DIAGNOSIS"
|
||
((diagnosed_causes["$cause"]++))
|
||
[ -z "${cause_examples[$cause]}" ] && cause_examples["$cause"]="$diagnosis"
|
||
else
|
||
((diagnosed_causes["NO_PHP_ERROR_LOGGED"]++))
|
||
fi
|
||
else
|
||
((diagnosed_causes["NO_PHP_ERROR_LOGGED"]++))
|
||
fi
|
||
fi
|
||
else
|
||
# No error in error_log at all - check .htaccess
|
||
if [ "$user" != "unknown" ] && [ -f "$docroot/.htaccess" ]; then
|
||
htaccess_file="$docroot/.htaccess"
|
||
htaccess_issues=""
|
||
|
||
# Check for invalid PHP directives
|
||
if grep -qE "^[[:space:]]*(php_value|php_flag|php_admin_value|php_admin_flag)" "$htaccess_file" 2>/dev/null; then
|
||
if ! apache2ctl -M 2>/dev/null | grep -q "php.*module"; then
|
||
htaccess_issues="PHP directives (php_value/php_flag) incompatible with current PHP handler (not mod_php)"
|
||
fi
|
||
fi
|
||
|
||
# Check for malformed RewriteRule
|
||
bad_rewrite=$(grep -nE "RewriteRule.*\[.*[^]]*$" "$htaccess_file" 2>/dev/null | head -1)
|
||
if [ -n "$bad_rewrite" ]; then
|
||
htaccess_issues="${htaccess_issues:+$htaccess_issues; }Malformed RewriteRule: $bad_rewrite"
|
||
fi
|
||
|
||
if [ -n "$htaccess_issues" ]; then
|
||
cause="HTACCESS_ERROR"
|
||
diagnosis="$domain$url - .htaccess error: $htaccess_issues"
|
||
echo "$cause|$diagnosis" >> "$DETAILED_DIAGNOSIS"
|
||
((diagnosed_causes["$cause"]++))
|
||
[ -z "${cause_examples[$cause]}" ] && cause_examples["$cause"]="$diagnosis"
|
||
else
|
||
((diagnosed_causes["NO_PHP_ERROR_LOGGED"]++))
|
||
fi
|
||
else
|
||
((diagnosed_causes["NO_PHP_ERROR_LOGGED"]++))
|
||
fi
|
||
fi
|
||
else
|
||
# No error log found - check .htaccess and permissions thoroughly
|
||
if [ "$user" != "unknown" ]; then
|
||
htaccess_file="$docroot/.htaccess"
|
||
|
||
if [ -f "$htaccess_file" ]; then
|
||
# Check .htaccess file permissions first
|
||
htaccess_perms=$(stat -c "%a" "$htaccess_file" 2>/dev/null)
|
||
htaccess_owner=$(stat -c "%U:%G" "$htaccess_file" 2>/dev/null)
|
||
|
||
# Check if readable by Apache
|
||
htaccess_readable="yes"
|
||
if ! sudo -u nobody test -r "$htaccess_file" 2>/dev/null; then
|
||
htaccess_readable="no"
|
||
fi
|
||
|
||
# Actually validate .htaccess syntax
|
||
htaccess_test=$(apache2ctl -t 2>&1)
|
||
htaccess_issues=""
|
||
|
||
# Check for invalid directives
|
||
if grep -qE "^[[:space:]]*(php_value|php_flag|php_admin_value|php_admin_flag)" "$htaccess_file" 2>/dev/null; then
|
||
if ! apache2ctl -M 2>/dev/null | grep -q "php.*module"; then
|
||
htaccess_issues="PHP directives (php_value/php_flag) incompatible with current PHP handler (not mod_php)"
|
||
fi
|
||
fi
|
||
|
||
# Check for malformed RewriteRule
|
||
bad_rewrite=$(grep -nE "RewriteRule.*\[.*[^]]*$" "$htaccess_file" 2>/dev/null | head -1)
|
||
if [ -n "$bad_rewrite" ]; then
|
||
htaccess_issues="${htaccess_issues:+$htaccess_issues; }Malformed RewriteRule: $bad_rewrite"
|
||
fi
|
||
|
||
# Check for RewriteCond without following RewriteRule
|
||
orphan_cond=$(grep -n "RewriteCond" "$htaccess_file" | while read line; do
|
||
linenum=$(echo "$line" | cut -d: -f1)
|
||
nextline=$((linenum + 1))
|
||
next=$(sed -n "${nextline}p" "$htaccess_file")
|
||
if [[ ! "$next" =~ RewriteRule|RewriteCond ]]; then
|
||
echo "Line $linenum: RewriteCond without RewriteRule"
|
||
fi
|
||
done | head -1)
|
||
|
||
if [ -n "$orphan_cond" ]; then
|
||
htaccess_issues="${htaccess_issues:+$htaccess_issues; }$orphan_cond"
|
||
fi
|
||
|
||
# Check for syntax errors in .htaccess
|
||
if echo "$htaccess_test" | grep -qi "syntax error"; then
|
||
syntax_err=$(echo "$htaccess_test" | grep -i "syntax error" | head -1)
|
||
htaccess_issues="${htaccess_issues:+$htaccess_issues; }Apache syntax error: $syntax_err"
|
||
fi
|
||
|
||
if [ "$htaccess_readable" = "no" ]; then
|
||
cause="PERMISSION_ERROR"
|
||
diagnosis="$domain$url - .htaccess not readable by Apache (perms: $htaccess_perms, owner: $htaccess_owner)"
|
||
echo "$cause|$diagnosis" >> "$DETAILED_DIAGNOSIS"
|
||
((diagnosed_causes["$cause"]++))
|
||
[ -z "${cause_examples[$cause]}" ] && cause_examples["$cause"]="$diagnosis"
|
||
elif [ -n "$htaccess_issues" ]; then
|
||
cause="HTACCESS_ERROR"
|
||
diagnosis="$domain$url - .htaccess error: $htaccess_issues"
|
||
echo "$cause|$diagnosis" >> "$DETAILED_DIAGNOSIS"
|
||
((diagnosed_causes["$cause"]++))
|
||
[ -z "${cause_examples[$cause]}" ] && cause_examples["$cause"]="$diagnosis"
|
||
else
|
||
# .htaccess appears valid - check document root and file permissions
|
||
docroot_perms=$(stat -c "%a" "$docroot" 2>/dev/null)
|
||
docroot_owner=$(stat -c "%U:%G" "$docroot" 2>/dev/null)
|
||
|
||
if [ "$docroot_perms" != "755" ] && [ "$docroot_perms" != "750" ]; then
|
||
cause="PERMISSION_ERROR"
|
||
diagnosis="$domain$url - Document root incorrect permissions (perms: $docroot_perms, owner: $docroot_owner, should be 755)"
|
||
echo "$cause|$diagnosis" >> "$DETAILED_DIAGNOSIS"
|
||
((diagnosed_causes["$cause"]++))
|
||
[ -z "${cause_examples[$cause]}" ] && cause_examples["$cause"]="$diagnosis"
|
||
elif [ -n "$specific_file" ] && [ -f "$specific_file" ]; then
|
||
# Check the specific PHP file permissions
|
||
file_perms=$(stat -c "%a" "$specific_file" 2>/dev/null)
|
||
file_owner=$(stat -c "%U:%G" "$specific_file" 2>/dev/null)
|
||
file_readable="yes"
|
||
|
||
if ! sudo -u nobody test -r "$specific_file" 2>/dev/null; then
|
||
file_readable="no"
|
||
fi
|
||
|
||
if [ "$file_readable" = "no" ]; then
|
||
cause="PERMISSION_ERROR"
|
||
diagnosis="$domain$url - File not readable by Apache: $specific_file (perms: $file_perms, owner: $file_owner)"
|
||
echo "$cause|$diagnosis" >> "$DETAILED_DIAGNOSIS"
|
||
((diagnosed_causes["$cause"]++))
|
||
[ -z "${cause_examples[$cause]}" ] && cause_examples["$cause"]="$diagnosis"
|
||
else
|
||
((diagnosed_causes["NO_PHP_ERROR_LOGGED"]++))
|
||
fi
|
||
else
|
||
((diagnosed_causes["NO_PHP_ERROR_LOGGED"]++))
|
||
fi
|
||
fi
|
||
else
|
||
# No .htaccess - check document root and file permissions
|
||
docroot_perms=$(stat -c "%a" "$docroot" 2>/dev/null)
|
||
docroot_owner=$(stat -c "%U:%G" "$docroot" 2>/dev/null)
|
||
|
||
if [ "$docroot_perms" != "755" ] && [ "$docroot_perms" != "750" ]; then
|
||
cause="PERMISSION_ERROR"
|
||
diagnosis="$domain$url - Document root incorrect permissions (perms: $docroot_perms, owner: $docroot_owner, should be 755)"
|
||
echo "$cause|$diagnosis" >> "$DETAILED_DIAGNOSIS"
|
||
((diagnosed_causes["$cause"]++))
|
||
[ -z "${cause_examples[$cause]}" ] && cause_examples["$cause"]="$diagnosis"
|
||
elif [ -n "$specific_file" ] && [ -f "$specific_file" ]; then
|
||
# Check the specific PHP file
|
||
file_perms=$(stat -c "%a" "$specific_file" 2>/dev/null)
|
||
file_owner=$(stat -c "%U:%G" "$specific_file" 2>/dev/null)
|
||
|
||
if ! sudo -u nobody test -r "$specific_file" 2>/dev/null; then
|
||
cause="PERMISSION_ERROR"
|
||
diagnosis="$domain$url - File not readable: $specific_file (perms: $file_perms, owner: $file_owner)"
|
||
echo "$cause|$diagnosis" >> "$DETAILED_DIAGNOSIS"
|
||
((diagnosed_causes["$cause"]++))
|
||
[ -z "${cause_examples[$cause]}" ] && cause_examples["$cause"]="$diagnosis"
|
||
else
|
||
((diagnosed_causes["NO_ERROR_LOG_FILE"]++))
|
||
fi
|
||
else
|
||
((diagnosed_causes["NO_ERROR_LOG_FILE"]++))
|
||
fi
|
||
fi
|
||
else
|
||
((diagnosed_causes["NO_ERROR_LOG_FILE"]++))
|
||
fi
|
||
fi
|
||
done < "$ERRORS_500"
|
||
|
||
echo -e "\rAnalyzed $total_to_diagnose / $total_to_diagnose errors - Complete! "
|
||
echo ""
|
||
|
||
# Display diagnosed causes
|
||
echo -e "${CYAN}${BOLD}ROOT CAUSES IDENTIFIED:${NC}"
|
||
echo ""
|
||
|
||
for cause in "${!diagnosed_causes[@]}"; do
|
||
count="${diagnosed_causes[$cause]}"
|
||
echo "$count|$cause"
|
||
done | sort -rn | while IFS='|' read count cause; do
|
||
clean_cause=$(echo "$cause" | tr '_' ' ')
|
||
|
||
case "$cause" in
|
||
PHP_MEMORY_EXHAUSTED)
|
||
echo -e "${RED}$clean_cause${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Fix:${NC} Increase memory_limit in /home/USER/public_html/.user.ini"
|
||
echo -e " ${YELLOW}Set:${NC} memory_limit = 512M"
|
||
;;
|
||
PHP_FATAL_ERROR)
|
||
echo -e "${RED}$clean_cause${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Fix:${NC} Review recent code/plugin changes"
|
||
;;
|
||
PHP_SYNTAX_ERROR)
|
||
echo -e "${RED}$clean_cause${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Fix:${NC} Check for PHP syntax errors in recent file edits"
|
||
;;
|
||
MISSING_PHP_FUNCTION)
|
||
echo -e "${RED}$clean_cause${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Fix:${NC} Install missing PHP extension (check error for which one)"
|
||
;;
|
||
DATABASE_CONNECTION)
|
||
echo -e "${RED}$clean_cause${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Fix:${NC} Check database credentials or MySQL service"
|
||
;;
|
||
HTACCESS_ERROR)
|
||
echo -e "${RED}.HTACCESS ERROR DETECTED${NC} - $count occurrences"
|
||
;;
|
||
PERMISSION_ERROR)
|
||
echo -e "${RED}PERMISSION ERROR DETECTED${NC} - $count occurrences"
|
||
;;
|
||
NO_ERROR_LOG_FILE)
|
||
echo -e "${YELLOW}$clean_cause${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Note:${NC} Checked multiple error_log locations - none found"
|
||
echo -e " ${YELLOW}Check:${NC} May need to enable PHP error logging"
|
||
;;
|
||
NO_PHP_ERROR_LOGGED)
|
||
echo -e "${YELLOW}$clean_cause${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Note:${NC} 500 error but no PHP error in log - likely .htaccess or Apache config"
|
||
;;
|
||
FILE_NOT_FOUND)
|
||
echo -e "${RED}$clean_cause${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Fix:${NC} Requested file does not exist - check URL or restore missing files"
|
||
;;
|
||
PHP_HANDLER_ERROR)
|
||
echo -e "${RED}PHP HANDLER ERROR${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Fix:${NC} PHP handler misconfigured - check cPanel PHP version or .htaccess AddHandler"
|
||
;;
|
||
WP_DEBUG_ERROR)
|
||
echo -e "${YELLOW}WORDPRESS DEBUG ERROR${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Fix:${NC} Disable WP_DEBUG_DISPLAY in wp-config.php or fix underlying warnings"
|
||
;;
|
||
DOCROOT_MISSING)
|
||
echo -e "${RED}$clean_cause${NC} - $count occurrences"
|
||
echo -e " ${YELLOW}Fix:${NC} Document root directory missing - restore from backup or check domain configuration"
|
||
;;
|
||
UNKNOWN)
|
||
# Skip - these are errors we couldn't diagnose
|
||
;;
|
||
*)
|
||
echo -e "${INFO_COLOR}$clean_cause${NC} - $count occurrences"
|
||
;;
|
||
esac
|
||
|
||
# Show example if we have one
|
||
if [ -n "${cause_examples[$cause]}" ]; then
|
||
example="${cause_examples[$cause]}"
|
||
echo " ${DIM}Example: ${example:0:120}...${NC}"
|
||
fi
|
||
echo ""
|
||
done
|
||
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " DETAILED 500 ERROR LIST"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
# Show most frequent URLs getting 500s
|
||
echo "Most Frequent 500 Error URLs:"
|
||
echo ""
|
||
cut -d'|' -f1,4 "$ERRORS_500" | sort | uniq -c | sort -rn | head -15 | while read count domain_url; do
|
||
domain=$(echo "$domain_url" | cut -d'|' -f1)
|
||
url=$(echo "$domain_url" | cut -d'|' -f2)
|
||
user="${domain_user[$domain]}"
|
||
printf " ${RED}%4d×${NC} %s%s ${DIM}(user: %s)${NC}\n" "$count" "$domain" "$url" "$user"
|
||
done
|
||
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " SPECIFIC DIAGNOSTICS (per URL/file)"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
# Show detailed diagnostics grouped by cause - deduplicate by domain+url+issue
|
||
if [ -f "$DETAILED_DIAGNOSIS" ] && [ -s "$DETAILED_DIAGNOSIS" ]; then
|
||
for cause_type in PHP_MEMORY_EXHAUSTED PERMISSION_ERROR HTACCESS_ERROR PHP_FATAL_ERROR PHP_SYNTAX_ERROR MISSING_PHP_FUNCTION DATABASE_CONNECTION FILE_NOT_FOUND PHP_HANDLER_ERROR WP_DEBUG_ERROR DOCROOT_MISSING; do
|
||
# Get unique diagnostics (deduplicate identical domain+issue combinations)
|
||
unique_diags=$(grep "^${cause_type}|" "$DETAILED_DIAGNOSIS" 2>/dev/null | cut -d'|' -f2 | sort -u)
|
||
cause_count=$(echo "$unique_diags" | grep -c "^" 2>/dev/null || echo "0")
|
||
|
||
if [ "$cause_count" -gt 0 ] && [ -n "$unique_diags" ]; then
|
||
cause_display=$(echo "$cause_type" | tr '_' ' ')
|
||
echo -e "${RED}${BOLD}$cause_display ($cause_count unique issues)${NC}"
|
||
echo ""
|
||
|
||
echo "$unique_diags" | head -20 | while read -r diag_line; do
|
||
[ -z "$diag_line" ] && continue
|
||
echo -e " ${YELLOW}•${NC} $diag_line"
|
||
done
|
||
|
||
if [ "$cause_count" -gt 20 ]; then
|
||
remaining=$((cause_count - 20))
|
||
echo -e " ${DIM}... and $remaining more${NC}"
|
||
fi
|
||
echo ""
|
||
fi
|
||
done
|
||
else
|
||
echo "No detailed diagnostics available."
|
||
echo ""
|
||
fi
|
||
|
||
echo "Full error list saved to: $ERRORS_500"
|
||
echo "Detailed diagnostics saved to: $DETAILED_DIAGNOSIS"
|
||
echo ""
|
||
|
||
press_enter
|