#!/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 # Scan all domain access logs for 500 errors for log in "$DOMLOGS_DIR"/*; do [ -f "$log" ] || continue [[ "$log" =~ (bytes_log|offset|error_log|ftpxferlog|-ssl_log)$ ]] && continue 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 read -r ip _ _ timestamp _ request _ _ <<< "$line" # 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 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" 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 "" # For each affected domain, check the actual PHP error logs declare -A diagnosed_causes declare -A cause_examples while IFS='|' read -r domain user status url timestamp ip; do [ -z "$domain" ] && continue # Find PHP error log - check multiple locations error_log="" if [ "$user" != "unknown" ]; then # Try multiple possible locations 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 around the same time as the 500 recent_error=$(tail -500 "$error_log" | grep -E "Fatal error|Parse error|syntax error|memory.*exhausted|database|MySQL|Permission denied" | tail -1) if [ -n "$recent_error" ]; then # Determine cause cause="UNKNOWN" if [[ "$recent_error" =~ (memory.*exhausted|Allowed memory size) ]]; then cause="PHP_MEMORY_EXHAUSTED" cause_examples["$cause"]="$domain: $recent_error" elif [[ "$recent_error" =~ (Fatal error|PHP Fatal error) ]]; then if [[ "$recent_error" =~ (undefined function|Call to undefined function) ]]; then cause="MISSING_PHP_FUNCTION" cause_examples["$cause"]="$domain: $recent_error" else cause="PHP_FATAL_ERROR" cause_examples["$cause"]="$domain: $recent_error" fi elif [[ "$recent_error" =~ (Parse error|syntax error) ]]; then cause="PHP_SYNTAX_ERROR" cause_examples["$cause"]="$domain: $recent_error" elif [[ "$recent_error" =~ (database|MySQL|Can\'t connect) ]]; then cause="DATABASE_CONNECTION" cause_examples["$cause"]="$domain: $recent_error" fi ((diagnosed_causes["$cause"]++)) else ((diagnosed_causes["NO_PHP_ERROR_LOGGED"]++)) fi else # No error log found - likely .htaccess issue # Check if .htaccess exists and might have issues if [ "$user" != "unknown" ] && [ -f "/home/$user/public_html/.htaccess" ]; then # Check for common .htaccess syntax errors htaccess_check=$(grep -E "RewriteEngine|RewriteRule|RewriteCond|php_value|php_flag" "/home/$user/public_html/.htaccess" 2>/dev/null) if [ -n "$htaccess_check" ]; then ((diagnosed_causes["HTACCESS_LIKELY"]++)) cause_examples["HTACCESS_LIKELY"]="$domain (user: $user) - has .htaccess with rewrite rules, check syntax" else ((diagnosed_causes["NO_ERROR_LOG_FILE"]++)) cause_examples["NO_ERROR_LOG_FILE"]="$domain (user: $user) - checked multiple locations, no error_log found" fi else ((diagnosed_causes["NO_ERROR_LOG_FILE"]++)) cause_examples["NO_ERROR_LOG_FILE"]="$domain (user: $user) - checked multiple locations, no error_log found" 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 " ${YELLOW}Fix:${NC} Increase memory_limit in /home/USER/public_html/.user.ini" echo " ${YELLOW}Set:${NC} memory_limit = 512M" ;; PHP_FATAL_ERROR) echo -e "${RED}⚠️ $clean_cause${NC} - $count occurrences" echo " ${YELLOW}Fix:${NC} Review recent code/plugin changes" ;; PHP_SYNTAX_ERROR) echo -e "${RED}📝 $clean_cause${NC} - $count occurrences" echo " ${YELLOW}Fix:${NC} Check for PHP syntax errors in recent file edits" ;; MISSING_PHP_FUNCTION) echo -e "${RED}📦 $clean_cause${NC} - $count occurrences" echo " ${YELLOW}Fix:${NC} Install missing PHP extension (check error for which one)" ;; DATABASE_CONNECTION) echo -e "${RED}🗄️ $clean_cause${NC} - $count occurrences" echo " ${YELLOW}Fix:${NC} Check database credentials or MySQL service" ;; HTACCESS_LIKELY) echo -e "${RED}⚙️ .HTACCESS SYNTAX ERROR (LIKELY)${NC} - $count occurrences" echo " ${YELLOW}Fix:${NC} Check .htaccess file for syntax errors" echo " ${YELLOW}Test:${NC} Temporarily rename .htaccess to .htaccess.bak and test" echo " ${YELLOW}Common issues:${NC} Invalid RewriteRule, bad php_value directives" ;; NO_ERROR_LOG_FILE) echo -e "${YELLOW}📄 $clean_cause${NC} - $count occurrences" echo " ${YELLOW}Note:${NC} Checked multiple error_log locations - none found" echo " ${YELLOW}Check:${NC} May need to enable PHP error logging" ;; NO_PHP_ERROR_LOGGED) echo -e "${YELLOW}❓ $clean_cause${NC} - $count occurrences" echo " ${YELLOW}Note:${NC} 500 error but no PHP error in log - likely .htaccess or Apache config" ;; *) 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 "Full error list saved to: $ERRORS_500" echo "" press_enter