Files
Linux-Server-Management-Too…/modules/website/500-error-tracker.sh
T
cschantz a246b514bc Add comprehensive automatic diagnostics for 500 errors
Added 10+ new automated checks that run when no PHP error is found in error_log:

New checks added:
1. .htaccess issues:
   - Invalid PHP directives (php_value/php_flag with FPM)
   - Malformed RewriteRule syntax
   - Missing RewriteBase with relative paths

2. File validation:
   - File exists check (FILE_NOT_FOUND)
   - File readable check (PERMISSION_ERROR)
   - PHP syntax validation using php -l (PHP_SYNTAX_ERROR)

3. Directory permissions:
   - Document root exists (DOCROOT_MISSING)
   - Document root permissions (755/750/711)

4. PHP handler issues:
   - PHP handler configured for domain
   - .htaccess AddHandler/SetHandler misconfig (PHP_HANDLER_ERROR)

5. WordPress-specific:
   - wp-config.php readable
   - WP_DEBUG_DISPLAY causing 500s (WP_DEBUG_ERROR)

Flow: When error_log has no matching errors, script now runs ALL checks
sequentially until it finds an issue, providing specific diagnosis instead
of generic "NO_PHP_ERROR_LOGGED".

This should catch most common 500 error causes automatically.
2025-11-03 21:36:28 -05:00

774 lines
37 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
while IFS='|' read -r domain user status url timestamp ip; do
[ -z "$domain" ] && continue
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"
else
# Check for PHP syntax errors
syntax_check=$(php -l "$specific_file" 2>&1)
if [[ "$syntax_check" =~ "Parse error" ]] || [[ "$syntax_check" =~ "syntax error" ]]; then
cause="PHP_SYNTAX_ERROR"
syntax_line=$(echo "$syntax_check" | grep -oP "line \K[0-9]+" | head -1)
diagnosis="$domain$url - PHP syntax error in $specific_file${syntax_line:+ at line $syntax_line}"
issue_found="yes"
fi
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"
# 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