Cutover Joomla->WP: antiguo.feadulta.com da HTTP 500 en frontend (pedir opinion a Opus) #162
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Cutover Joomla→WP: antiguo.feadulta.com da HTTP 500 en frontend — pido opinión a Opus
Contexto
Cutover ejecutado 2026-07-07/08: WordPress pasa a servir
www.feadulta.com(antes Joomla).Para no perder las URLs viejas de Joomla, el sitio Joomla se mantiene vivo bajo un subdominio
nuevo,
antiguo.feadulta.com, con banner +noindex(issue de referencia: checklist maestrodel cutover). Mecanismo:
mvfísico de directorios en el mismo filesystem del hosting (cPanel,sin acceso root, shell restringido — ver feadulta-server-glibc-rota en memoria).
/web/*→/web/antiguo/*(mv, no copia)./web/wp-nuevo/*→/web/*(mv).antiguocreado en cPanel apuntando a/web/antiguo.www.feadulta.com (WordPress) quedó correcto y verificado (contenido, dominio en enlaces,
GA4, Search Console, redirección 301 de URLs viejas de Joomla → antiguo, permalinks). Ese lado
está cerrado.
El problema: antiguo.feadulta.com
Tras el
mv, el frontend de Joomla enantiguo.feadulta.comdevuelve HTTP 500 paracualquier ruta de contenido real (
/es/,/item/8856-venid-a-mi.html, etc.), con cerooutput de error capturable por PHP (ni excepción, ni fatal reportado por
register_shutdown_function).En cambio:
/administrator/(panel admin de Joomla) → 200 OK, carga perfectamente./a secas → 200/redirect JS a /es/ (el bootstrap básico de Joomla funciona).Es decir: el framework Joomla arranca bien (DB, sesión, bootstrap), pero algo específico del
pipeline de renderizado del frontend (plantilla + módulos + vista del componente) revienta
el proceso sin dejar rastro.
Cambios hechos en el
mv(candidatos a causa)configuration.php:$log_pathy$tmp_pathtenían rutas absolutas hardcodeadas(
/usr/home/feadulta.com/web/...) — corregidas a incluir/antiguo/.$live_site=''(relativo, no tocado, no debería importar). Confirmado que no quedan más rutas
/usr/home/feadulta.com/websin/antiguo/en ese fichero.fe_adulta_1/index.php: yo mismo añadí código condicional porHTTP_HOST(banner "hemos migrado" +
noindex+ headerX-Robots-Tag) ANTES delmv, y se probó conéxito vía HTTP real contra el subdominio
wp-nuevocon Host header simulado (200 OK, bannervisible) — es decir, el mismo fichero, con los mismos bytes, SÍ renderizó una página completa
correctamente antes de mover los ficheros. Esto hace menos probable (pero no descarta del
todo) que mi edición sea la causa directa.
Diagnóstico hecho (todo descartado como causa, salvo lo no resuelto)
com_admintools, extension_id 10207): desactivado yreactivado, sin cambio de comportamiento.
session_handler: probado'database'(original) y'none'— el 500 persiste en amboscasos. Sesión nativa PHP (
session_start()files-based) funciona bien de forma aislada.sapi:fpm-fcgi,sessionypdo_mysqlcargadas — normal.memory_limit=768M,max_execution_time=240,open_basedirincluye la ruta nueva/usr/home/feadulta.com/— todo cubre el path/web/antiguosin problema.ew4r_session, 2605 filas) intacta.desde la ruta vieja.
(
_diag.php, ruta/): ejecuta OK, solo warnings de deprecación normales de PHP 8.3 sobrecódigo Joomla 3.x viejo.
$_SERVER['REQUEST_URI']a una ruta de artículo real antes decargar el framework (
_diag2.php): llega al log hasta"app-created-ok"y ahí se corta sinmás output — ni excepción, ni fatal, ni nada en el shutdown handler.
Hipótesis principal, sin confirmar
El servidor tiene un problema de compatibilidad glibc/libpthread conocido y documentado
(feadulta-server-glibc-rota): binarios que cargan libpthread (grep, sort, scp,
php -l)segfaultan (exit 139) de forma reproducible y no relacionada con nuestro código. La ausencia
total de output de error incluso con try/catch + shutdown handler alrededor de
$app->execute()es consistente con un crash de proceso a bajo nivel (segfault) durante elrenderizado de plantilla/módulo/componente del frontend — un crash así no pasa por el manejo de
errores de PHP en absoluto.
Pero esto es una hipótesis, no una confirmación: no he conseguido localizar el log de error real
del servidor (busqué en rutas típicas de cPanel tipo
domlogs, sin éxito con el shellrestringido de esta cuenta) para confirmar un segfault a nivel de sistema.
Estado actual / decisión de prioridad
Rafa decidió (2026-07-08) que antiguo.feadulta.com es de prioridad baja — es un subdominio
legado, no el sitio principal. No se sigue invirtiendo tiempo en depurarlo de forma urgente. Se
documenta aquí para retomarlo con calma, y para pedir opinión a Opus sobre la causa más probable
y el siguiente paso de diagnóstico más eficiente, dado el shell muy restringido del hosting
(cPanel jail, sin
uname/which/hostname, sin acceso root, sin log de errores localizadoaún).
Pendiente de limpieza (no relacionado con el bug, pero anotado aquí)
/web/antiguo/:_phpcheck.php,_phpcheck2.php,_diag.php,_diag2.php— exponen configuración interna de PHP, borrar cuando se cierre esteissue.
configuration.php:$error_reportingquedó en'maximum'(debería volver a'none'enproducción) una vez se concluya el diagnóstico.
wp-nuevoen cPanel: ya no se usa, pendiente de eliminar.Opinión (Opus) — causa probable y próximos pasos
(a) Causa más probable
Coincido con la hipótesis de crash nativo del proceso (SIGSEGV), no un fatal PHP — confianza media-alta en el tipo de fallo, media en el trigger exacto.
El razonamiento decisivo es el que ya intuisteis pero conviene explicitar:
register_shutdown_functionsí dispara anteE_ERROR,memory_limitagotado y excepciones no capturadas (que PHP convierte en fatal). Que no dispare nada — ni con try/catch + shutdown alrededor de$app->execute()— descarta prácticamente todo el universo de errores gestionados por PHP y deja el proceso muriendo por debajo del runtime: segfault o kill por señal. Es exactamente el patrón de la glibc/libpthread rota ya documentada.Matices importantes sobre lo que NO prueba lo que funciona:
/a secas NO valida el render: solo emite un redirect JS a/es/. Es un render mínimo, no ejercita plantilla + módulos + vista de componente./administrator/no ejercita el pipeline que revienta: no corre los content plugins, ni el router SEF, ni el Language Filter del frontend.wp-nuevocon Host simulado tampoco es equivalente: mismo fichero, sí, pero distinto docroot, distinto pool FPM y, sobre todo, distinta ruta de ejecución (probablemente no llegó a renderizar un artículo real con todos los content-plugins). No descarta tu edición, pero tampoco la incrimina.Sospechosos concretos del trigger, por probabilidad:
preg_replacesobre el cuerpo del artículo y en el enrutado. Un fallo de PCRE JIT contra glibc rota da exactamente esto: proceso muerto, cero output, HTTP 500. Y el admin//casi no tocan ese camino. Es mi favorito porque además es trivial de probar y revertir.intl(ICU/collator del Language Filter multilingüe),mbstring,imagick/gd(render de módulos con imágenes). Cualquiera que enlace libpthread encaja con el patrón documentado.mvpodría haber metido un bucle de enrutado. También produce muerte silenciosa sin traza PHP.(b) Próximos pasos, priorizados (ejecutables con el shell jaulado)
1. Confirmar el segfault en el log correcto (lo más rentable, no depende del shell roto).
El error real NO estará en el output de PHP; estará como señal en el log de Apache/FPM:
exit signal Segmentation fault (11)ochild pid ... signal. Ese único renglón confirma o tumba toda la hipótesis.error_logdentro de/web/antiguo/(PHP lo suele volcar en el CWD del script) y en~/public_html/error_log,~/logs/,~/access-logs/.~/logs/.2. SEF vs. RAW (el discriminador más barato y potente).
Compara vía curl la URL SEF contra la cruda sin router:
index.php?option=com_content&view=article&id=8856&lang=esvs./item/8856-venid-a-mi.html3.
pcre.jit=0(confirma/descarta el sospechoso #1 en un minuto).Crea
/web/antiguo/.user.ini(FPM lo lee) con:pcre.jit=0y para descartar el otro JIT:
opcache.jit=0. Espera el TTL del.user.ini(~300s) o toca un.php. Si el 500 desaparece → PCRE2 JIT confirmado, y tienes workaround permanente sin tocar el binario roto.4. Bisección por BD — SÍ, en bloque, no uno a uno.
El bootstrap ya funciona, así que puedes cortar por lo sano (haz backup de cada tabla antes):
UPDATE ew4r_modules SET published=0;en bloque → si el 500 se va, era un módulo; reactiva en binario (mitad y mitad).contentyeditors(UPDATE ew4r_extensions SET enabled=0 WHERE type='plugin' AND folder IN ('content','editors','editors-xtd');). Estos corren en el render de artículo y NO tumban el arranque. Si desaparece → content-plugin.systemve con más tiento, pero como el bootstrap arranca bien, puedes probar desactivar en bloque los system no esenciales (dejalanguagefilteryredirectpara el final, y NUNCA toques el que necesite el arranque). Merece la pena el "todos de golpe" también aquí para acotar rápido.5. Aislar plantilla vs. componente sin
php -l.offline=1en config: la página offline renderiza plantilla + módulo offline pero no el componente ni los content-plugins. Si la offline sale 200 → plantilla y base OK, el crash está en el pipeline de componente/content-plugin. Discriminador de 30 segundos.protostar/beez3) enew4r_template_styles(home=1). Si con el core renderiza → el culpable esfe_adulta_1(tuindex.phpo algo que carga). Si sigue 500 → no es la plantilla.6. Instrumentar
_diag2.phppor fases (localiza la fase exacta del segfault).En vez de
$app->execute()de una pieza, llama y loguea (conflush/escritura a fichero entre cada una):$app->route()→$app->dispatch()→$app->render(). La última línea escrita antes del corte te dice si muere en enrutado, despacho o render — el equivalente a unphp -lfuncional para este caso, y sin depender del binario que segfaultea.Orden sugerido
1 (log cPanel) y 2 (SEF vs raw) primero: entre ambos, ~10 min, y probablemente ya sabes si es router/SEF y si hay firma de segfault. Luego 3 (pcre.jit) para confirmar el mecanismo. 4–6 solo si 1–3 no cierran el caso. Dado que Rafa lo marcó prioridad baja, con 1+2+3 tienes el 80% del diagnóstico por muy poco esfuerzo.
RESUELTO. No era un segfault — era un bucle de
mod_rewritede Apache.Causa real
En
/errors.log(Apache), cada ruta de contenido real de antiguo.feadulta.com daba:El
.htaccessde Joomla tenía# RewriteBase /comentado (valor por defecto de Joomla, que asume que el sitio vive en la raíz del vhost). El subdominioantiguoen este cPanel no es un vhost separado con DocumentRoot propio en/web/antiguo— es un alias de subdirectorio sobre el mismo vhost. Con la base sin ajustar, el router SEF de Joomla generaba URLs internas mal resueltas y Apache entraba en bucle intentando reescribir la ruta, abortando a los 10 saltos. Encaja con la evidencia:/y/administrator/funcionaban (rutas físicas reales, no dependen del RewriteBase), pero cualquier ruta SEF virtual (/es/item/...,/es/temas/...) entraba en bucle.Fix aplicado
sed -i 's|# RewriteBase /|RewriteBase /|' /web/antiguo/.htaccess(backup guardado como.htaccess.bak-pre-rewritebase).Verificado tras el cambio:
/es/,/item/8856-venid-a-mi.html(redirige 301 a la URL SEF canónica, 200),/es/temas/678-...— todas devuelven 200 ahora.Nota sobre el diagnóstico anterior
La hipótesis del segfault (PCRE2 JIT, glibc/libpthread) que barajamos antes —incluida la de Opus— quedó descartada por esta evidencia más directa. Lección: el log de Apache (
/errors.log, en la raíz accesible desde el shell restringido, no endomlogscomo busqué antes) tenía la respuesta desde el principio; debí buscarlo con más prioridad antes de especular con hipótesis de bajo nivel.Pendiente
/web/antiguo/: ficheros_phpcheck*.php,_diag*.php, y ahora también.htaccess.bak-pre-rewritebasecuando se confirme estable.$error_reportingdeconfiguration.phpa'none'.