be4314dde9
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.
774 lines
37 KiB
Bash
Executable File
774 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
|
||
|
||
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
|