
Cuando administras varios sitios en un mismo servidor, revisar uno por uno no escala. chequeo-completo.sh hace una pasada por el servidor y por cada cuenta, y marca con <<< PELIGRO o <<< REVISAR solo lo que necesita atención.
Antes de empezar
- Necesitas un VPS o servidor dedicado con cPanel/WHM y acceso root (desde WHM → Terminal basta).
- Los scripts se probaron en AlmaLinux 10 con cPanel 138, PHP 8.3 de EasyApache (
/opt/cpanel/ea-php83) y WP-CLI instalado en/usr/local/bin/wp. Si tu servidor usa otra versión de PHP, cambia la rutaea-php83en el script. - Pruébalos primero en un horario tranquilo y lee lo que hace cada uno antes de ejecutarlo. Se comparten tal cual, sin garantía: adáptalos a tu servidor.
Además usa ModSecurity, CSF/LFD e ImunifyAV si los tienes; si falta alguno, esa línea simplemente sale vacía.
Qué revisa
- Servidor: carga, memoria, disco, servicios con falla, actualizaciones pendientes y si falta reiniciar por un kernel nuevo.
- Seguridad: estado de ModSecurity, cuántos bloqueos hizo hoy y a quién; IPs bloqueadas por LFD y por qué; detecciones de ImunifyAV; listas negras de correo de la IP del servidor.
- Respaldos y correo: últimos tres respaldos con su tamaño, cola de correo y correos congelados.
- Cada cuenta: si el sitio responde y en cuánto tiempo, vencimiento del certificado SSL y espacio usado.
- Cada WordPress: núcleo y plugins comparados con los originales, actualizaciones pendientes, PHP dentro de
uploads, código malicioso conocido, archivos raros, administradores (y si hay alguno nuevo en los últimos 7 días), registro abierto, editor de archivos, permisos y errores de PHP del día.
Instalación
Entra a WHM → Terminal (como root) y pega este bloque completo de una vez. Crea el archivo /root/chequeo-completo.sh, le da permisos y comprueba que quedó bien escrito:
cat > /root/chequeo-completo.sh <<'EOF'
#!/bin/bash
# Chequeo completo del servidor y de las cuentas. Uso: /root/chequeo-completo.sh
PHPCLI=/opt/cpanel/ea-php83/root/usr/bin/php
HOY_LOG=$(date '+%a %b %d') # formato del error_log de Apache
HOY_LFD=$(date '+%b %e') # formato de lfd.log
HOY_PHP=$(date '+%d-%b-%Y') # formato de error_log de PHP
P_GRAVE='eval\s*\(\s*(base64_decode|gzinflate|gzuncompress|str_rot13|\$_(POST|GET|REQUEST|COOKIE))|(assert|system|passthru|shell_exec|exec|popen|proc_open)\s*\(\s*\$_(POST|GET|REQUEST|COOKIE|SERVER)|base64_decode\s*\(\s*\$_(POST|GET|REQUEST|COOKIE)|\\x65\\x76\\x61\\x6c'
echo "################ SERVIDOR - $(date '+%d-%m-%Y %H:%M')"
echo "--- Carga (bajo 2 es normal)"; uptime | sed 's/.*load average/load average/'
echo "--- Memoria"; free -h | awk 'NR==2{print "RAM usada "$3" de "$2", disponible "$7} NR==3{print "Swap usada "$3" de "$2}'
echo "--- Disco"; df -h / /home /backup 2>/dev/null | awk 'NR>1 && !s[$6]++ {print $6": "$5" usado ("$4" libres)"}'
echo "--- Servicios con falla"; F=$(systemctl list-units --type=service --state=failed --no-legend 2>/dev/null | awk '{print $1}'); echo "${F:-ninguno}"
echo "--- Servicios clave"; for s in httpd mariadb exim dovecot cpanel lfd crond; do printf "%s=%s " $s "$(systemctl is-active $s 2>/dev/null)"; done; echo
echo "--- Actualizaciones del sistema pendientes"; N=$(dnf -q check-update 2>/dev/null | grep -cE '^[a-zA-Z0-9]'); echo "$N paquetes"
echo "--- Reinicio pendiente por kernel"; needs-restarting -r >/dev/null 2>&1 && echo "no" || echo "SI (reiniciar en horario tranquilo)"
echo "--- cPanel"; /usr/local/cpanel/cpanel -V 2>/dev/null
echo "--- ModSecurity"; grep -rhE '^\s*SecRuleEngine' /etc/apache2/conf.d/modsec/ 2>/dev/null | sort -u | tr -s ' '
echo "--- Bloqueos de ModSecurity hoy"; grep "$HOY_LOG" /etc/apache2/logs/error_log 2>/dev/null | grep -c "ModSecurity: Access denied"
echo "--- A quien bloqueo ModSecurity hoy (IP, ruta). Una IP chilena en una ruta normal = posible falso positivo"
grep "$HOY_LOG" /etc/apache2/logs/error_log 2>/dev/null | grep "ModSecurity: Access denied" | sed -E 's/.*\[client ([0-9.]+)[^]]*\].*\[hostname "([^"]+)"\].*\[uri "([^"]+)"\].*/\1 \2\3/' | cut -c1-90 | sort | uniq -c | sort -rn | head -8
echo "--- CSF"; csf -v 2>/dev/null | head -1; grep -E '^TESTING ' /etc/csf/csf.conf
echo "--- IPs bloqueadas por LFD hoy"; grep "$HOY_LFD" /var/log/lfd.log 2>/dev/null | grep -c "Blocked in csf"
echo "--- Motivos de bloqueo hoy"; grep "$HOY_LFD" /var/log/lfd.log 2>/dev/null | grep "Blocked in csf" | grep -oE '\((sshd|smtpauth|pop3d|imapd|cpanel|whm|webmail|ftpd|mod_security|[a-z_]+)\)' | sort | uniq -c | sort -rn | head -5
echo "--- Ultimos respaldos"; ls -1d /backup/20* 2>/dev/null | tail -3 | while read d; do echo "$d $(du -sh $d 2>/dev/null | cut -f1)"; done
echo "--- Cola de correo"; echo "$(exim -bpc 2>/dev/null) en cola, $(exim -bp 2>/dev/null | grep -c frozen) congelados"
IP=$(hostname -I | awk '{print $1}'); RIP=$(echo "$IP" | awk -F. '{print $4"."$3"."$2"."$1}')
echo "--- Listas negras ($IP)"; for bl in zen.spamhaus.org bl.spamcop.net b.barracudacentral.org dnsbl-1.uceprotect.net; do r=$(dig +short $RIP.$bl A | head -1); printf "%s=%s " $bl "${r:-limpia}"; done; echo
echo "--- ImunifyAV: archivos maliciosos registrados"; imunify-antivirus malware malicious list 2>/dev/null | grep -c "/home/"
echo; echo "################ CUENTAS"
for U in $(ls /var/cpanel/users/ 2>/dev/null | grep -vE '^(system|root|nobody)$'); do
DOM=$(grep -m1 '^DNS=' "/var/cpanel/users/$U" | cut -d= -f2); W="/home/$U/public_html"
echo; echo "======== $DOM (cuenta $U)"
echo "Responde: $(curl -s -o /dev/null -k -m 20 --resolve $DOM:443:127.0.0.1 -w '%{http_code} en %{time_total}s' https://$DOM/)"
echo "SSL vence: $(echo | openssl s_client -connect 127.0.0.1:443 -servername $DOM 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)"
echo "Espacio: $(du -sh /home/$U 2>/dev/null | cut -f1)"
[ -f "$W/wp-config.php" ] || { echo "(no es WordPress)"; continue; }
WP=(sudo -u "$U" -- $PHPCLI /usr/local/bin/wp --path="$W" --skip-plugins --skip-themes)
echo "WordPress: $("${WP[@]}" core version 2>/dev/null) $("${WP[@]}" core check-update --format=count 2>/dev/null | sed 's/^/actualizaciones del nucleo: /')"
C=$("${WP[@]}" core verify-checksums 2>&1 | grep -c '^Warning: File'); echo "Nucleo alterado: $C archivos $( [ $C -gt 0 ] && echo '<<< REVISAR')"
C=$("${WP[@]}" plugin verify-checksums --all --format=csv 2>/dev/null | tail -n +2 | grep -c .); echo "Plugins de wordpress.org alterados: $C archivos $( [ $C -gt 0 ] && echo '<<< REVISAR')"
echo "Plugins con actualizacion: $("${WP[@]}" plugin list --update=available --field=name 2>/dev/null | tr '\n' ' ')"
C=$(find "$W/wp-content/uploads" -type f \( -iname '*.php*' -o -iname '*.phtml' \) -size +200c 2>/dev/null | wc -l); echo "PHP en uploads: $C $( [ $C -gt 0 ] && echo '<<< PELIGRO')"
C=$(grep -rnE "$P_GRAVE" "$W" --include=*.php 2>/dev/null | grep -v json_decode | cut -d: -f1 | sort -u | wc -l); echo "Archivos con codigo malicioso conocido: $C $( [ $C -gt 0 ] && echo '<<< PELIGRO')"
C=$(find "$W" -type f \( -name '*.suspected' -o -name '*.phtml' -o -name '*.phar' -o -name '.*.php' \) 2>/dev/null | wc -l); echo "Archivos raros: $C $( [ $C -gt 0 ] && echo '<<< REVISAR')"
C=$(imunify-antivirus malware malicious list 2>/dev/null | grep -c "$W/"); echo "Detecciones ImunifyAV: $C $( [ $C -gt 0 ] && echo '<<< REVISAR')"
echo "Administradores: $("${WP[@]}" user list --role=administrator --field=user_login 2>/dev/null | tr '\n' ' ')"
N=$("${WP[@]}" user list --role=administrator --fields=user_login,user_registered --format=csv 2>/dev/null | awk -F, -v d="$(date -d '-7 days' +%F)" 'NR>1 && $2>=d {print $1}'); [ -n "$N" ] && echo "Administradores NUEVOS (7 dias): $N <<< PELIGRO"
echo "Registro abierto: $("${WP[@]}" option get users_can_register 2>/dev/null | sed 's/1/si/;s/0/no/') (perfil por defecto: $("${WP[@]}" option get default_role 2>/dev/null))"
echo "Editor desactivado: $(grep -q DISALLOW_FILE_EDIT $W/wp-config.php && echo si || echo 'NO <<< correr /root/endurecer-wordpress.sh') wp-config: $(stat -c %a $W/wp-config.php) public_html: $(stat -c '%a %G' $W)"
E=$(grep -c "$HOY_PHP" $W/error_log 2>/dev/null); echo "Errores de PHP hoy: ${E:-0}"
done
echo; echo "Chequeo terminado."
EOF
chmod 700 /root/chequeo-completo.sh && bash -n /root/chequeo-completo.sh && echo SCRIPT_OK
Si al final aparece SCRIPT_OK, el script está listo. Si no aparece, el bloque se pegó incompleto o con comillas cambiadas: vuelve a pegarlo.
Cómo usarlo
/root/chequeo-completo.sh
Tarda uno o dos minutos. Si la salida es muy larga para leerla en la terminal, guárdala en un archivo: /root/chequeo-completo.sh > /root/chequeo-$(date +%F).txt
Cómo leer el resultado
- Sin marcas
<<<: todo en orden. <<< PELIGROen «PHP en uploads», «código malicioso» o «Administradores NUEVOS»: revisa el sitio el mismo día.- «Núcleo alterado» suele ser un falso aviso: archivos
error_logsueltos dentro dewp-includes, o unreadme.htmlborrado a propósito. Confírmalo conwp core verify-checksums: «should not exist» son archivos que sobran y «doesn’t verify» son archivos modificados (esos sí importan). - En «A quién bloqueó ModSecurity hoy» lo normal es ver robots buscando
.env,.gitowp-config. Una IP de tu país en una ruta normal del sitio puede ser un falso positivo. - Carga bajo 2, disco bajo 80% y «Servicios con falla: ninguno» son valores sanos.
Cuándo usarlo
- Una vez a la semana.
- Después de instalar o actualizar varias cosas.
- Cuando algo se ve raro: un sitio lento o un correo de aviso del servidor.
Si un sitio se siente lento pero el chequeo dice que responde en milisegundos, prueba con otro navegador. Las protecciones de algunos navegadores, como los Shields de Brave, pueden frenar tiendas con muchos scripts.





