Incompatibilidad de versión PHP en WordPress: comprobar y actualizar sin interrupciones

¿Te fue útil?

La caída más aterradora de WordPress es la silenciosa: una actualización de PHP “por seguridad” que pasa la CI, se ve bien en un navegador,
y luego convierte tu flujo de pago de mayor ingresos en una pantalla en blanco a las 2 a. m. mientras el responsable en guardia intenta recordar
qué pool está en qué host. Todo el mundo tiene opiniones. Los logs tienen hechos.

La incompatibilidad de versión de PHP no es misteriosa. Es simplemente una discrepancia entre el comportamiento en tiempo de ejecución y las asunciones del código:
en el core de WordPress, en plugins, en temas y en la pegatina de tu stack (opcache, object cache, proxies). Puedes actualizar
sin tiempo de inactividad si lo tratas como un cambio en producción: inventario, prueba con la forma real del tráfico, cambia con seguridad,
y mantiene el rollback aburrido.

Qué se rompe realmente cuando cambian las versiones de PHP (y por qué WordPress lo nota)

Una actualización de PHP no es “solo un nuevo intérprete”. Es un cambio en las reglas del lenguaje, el comportamiento de la biblioteca estándar, la configuración por defecto
y las extensiones de las que WordPress depende. WordPress se sitúa en la encrucijada entre código legado y hosting moderno. Soporta sitios antiguos, plugins de 2012
y desarrolladores que tratan functions.php como un confesionario. Por eso es sensible.

Las fallas de compatibilidad vienen en cuatro sabores

  1. Fallas graves: errores fatales, errores de parseo, funciones/clases faltantes, extensiones ausentes.
    Estos son los momentos de “el sitio está caído”.
  2. Fallas suaves: warnings/notices que se vuelven fatales bajo configuraciones más estrictas, o cambios lógicos que
    invierten el comportamiento (cadenas vacías vs nulls, comparaciones, manejo de arrays).
  3. Regresiones de rendimiento: diferencias de opcache, efectos secundarios del JIT, o rutas de código que se vuelven más lentas
    debido a cambios del motor. Normalmente se muestra como más CPU y latencias cola, no errores inmediatos.
  4. Desajustes operativos: distintos valores por defecto en php.ini, diferentes configs de pools FPM,
    paquetes del sistema faltantes, cambios en permisos de archivos, o un servidor actualizado y otro no.

Por qué las actualizaciones de WordPress “funcionan en staging” y aún fallan en producción

Staging a menudo tiene un conjunto de plugins distinto, caching diferente y tráfico educado. El tráfico de producción es rudo.
Golpea endpoints extraños, envía cookies grandes, dispara cron en momentos incómodos y ejerce páginas de administración poco usadas.
Y la mayoría de entornos de staging no ejecutan el mismo object cache, la misma semántica de sistema de archivos o el mismo estado de calentamiento del opcode cache.

Una cita operativa que vale la pena dejar en una nota adhesiva: “La esperanza no es una estrategia.” — idea parafraseada comúnmente
en círculos de confiabilidad. En este contexto, esperanza significa “actualizar y ver qué pasa”.
No lo hagas. Mide y cambia con un plan.

Además, WordPress es un ecosistema de plugins. Un solo plugin puede traer una librería de un tercero que asume el comportamiento de una versión de PHP.
Actualizas PHP y de repente una dependencia empieza a lanzar TypeError donde antes hacía conversiones silenciosas. PHP no se porta mal; PHP es más explícito.
Tu presupuesto de errores puede no estar de acuerdo.

Broma #1: Actualizar PHP sin comprobar los plugins es como cambiar la cerradura de la puerta y sorprenderse cuando tus llaves dejan de funcionar.

Hechos interesantes y contexto histórico (lo que explica el dolor)

  • PHP 7.0 (2015) fue el gran punto de inflexión en rendimiento tras el trabajo PHPNG; muchos sitios WordPress vieron grandes mejoras sin cambiar código.
  • PHP 5.6 llegó al end of life en 2018, pero el hosting compartido lo mantuvo vivo durante años porque “todavía no se rompió nada”.
  • PHP 8.0 (2020) introdujo el JIT, pero las cargas típicas de WordPress rara vez se benefician mucho; lo que importa son las mejoras del motor y la compatibilidad.
  • PHP 8 endureció el tipado y hizo que más operaciones lancen TypeError en lugar de seguir con conversiones; muchos plugins legados confiaban en esas conversiones.
  • La historia de la extensión MySQL importa: hace años WordPress usaba funciones mysql_*; los stacks modernos usan mysqli o PDO. Algunos plugins antiguos no se enteraron.
  • La “pantalla blanca de la muerte” de WordPress se volvió meme porque los fatales a menudo estaban ocultos por display_errors=Off; WordPress moderno tiene modo de recuperación, pero no es una protección mágica.
  • OPcache se hizo mainstream en PHP 5.5; hoy la estabilidad de rendimiento depende de sintonizar opcache y no provocarle thrashing con despliegues constantes.
  • Composer no siempre fue común en el ecosistema WordPress; muchos plugins todavía empaquetan código de terceros manualmente, lo que hace que el parcheo de dependencias y la compatibilidad PHP sea más caótica.
  • FPM reemplazó a mod_php como estándar para despliegues serios porque la gestión de procesos y el aislamiento son más sensatos—además de que ejecutar todo en Apache es una elección de estilo de vida.

Guion de diagnóstico rápido: confirma que es PHP, luego encuentra el borde

Cuando un sitio WordPress empieza a devolver 502s, páginas en blanco o fallos raros de administración después de “una ventana de mantenimiento rutinaria”,
necesitas un bucle cerrado: confirma dónde está la falla, localiza la ruta de código y decide si hacer rollback o parchear hacia adelante.
No te dediques a explorar a ciegas.

Primero: confirma la capa del síntoma

  1. ¿Es a nivel HTTP (502/504) o a nivel de aplicación (200 con HTML roto)?
    502 normalmente significa que PHP-FPM murió, se colgó o el upstream está mal cableado. 200 con página en blanco suele significar un fatal de PHP con salida suprimida.
  2. ¿La falla está en todos los nodos o solo en algunos?
    La mezcla de versiones de PHP en un pool balanceado es un modo clásico de fallo “a veces funciona”.
  3. ¿Está limitado al admin, cron o a una ruta de plugin específica?
    Los flujos de checkout y los endpoints AJAX suelen tocar código distinto al de la página de inicio.

Segundo: revisa los dos logs que realmente importan

  1. Log de errores de PHP-FPM (o journal de systemd): muestra fatales, segfaults y problemas de configuración de pool.
  2. Log de la aplicación WordPress/PHP: wp-content/debug.log si está habilitado, o tu logging centralizado.

Tercero: aisla si es incompatibilidad o capacidad

  • Si los errores mencionan Call to undefined function, Class not found, Parse error, o TypeError después de una actualización: trátalo como incompatibilidad hasta demostrar lo contrario.
  • Si los logs muestran timeouts, server reached pm.max_children, o duraciones largas de requests: tu actualización cambió características de rendimiento y ahora estás limitado por capacidad.
  • Si las fallas son intermitentes y se correlacionan con un backend específico: probablemente tienes una desviación de versión o de configuración.

Cuarto: decide rollback vs arreglo hacia adelante

Mi regla: si el checkout/admin está caído o las tasas de error se disparan y no tienes una solución confirmada, haz rollback primero.
Un rollback te da tiempo para pensar sin que tus clientes paguen por tu curiosidad.

Tareas prácticas (comandos, salidas y decisiones)

Estas son las tareas que realmente ejecuto. No porque sean sofisticadas, sino porque eliminan la ambigüedad.
Cada tarea incluye: comando, qué significa la salida y la decisión que tomas.

Tarea 1: Identificar la versión de PHP que sirve el sitio (CLI vs FPM pueden diferir)

cr0x@server:~$ php -v
PHP 8.1.2 (cli) (built: Feb 15 2025 10:41:12) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.2, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.2, Copyright (c), by Zend Technologies

Significado: Este es el binario CLI. Puede no coincidir con lo que Nginx/Apache usa vía FPM.
Decisión: Si estás depurando comportamiento web, no asumas que este es el runtime. Comprueba FPM a continuación.

Tarea 2: Confirmar la versión y el estado de PHP-FPM vía systemd

cr0x@server:~$ systemctl status php8.1-fpm --no-pager
● php8.1-fpm.service - The PHP 8.1 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.1-fpm.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2025-12-27 10:12:33 UTC; 2h 4min ago
       Docs: man:php-fpm8.1(8)
   Main PID: 1234 (php-fpm8.1)
     Status: "Processes active: 6, idle: 10, Requests: 24912, slow: 3, Traffic: 1.2req/sec"

Significado: FPM está en ejecución y reporta requests lentos.
Decisión: Si ves “failed” o bucles de reinicio, estás en territorio de outage. Si los requests lentos se disparan tras la actualización, investiga rendimiento y timeouts.

Tarea 3: Ver qué socket upstream usa Nginx (captura desviación de versión)

cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '/fastcgi_pass/p' | head
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;

Significado: Nginx está conectado al socket de PHP 8.1.
Decisión: Si esto apunta a php7.4-fpm.sock en algunos nodos y a php8.1-fpm.sock en otros, arregla tu gestión de configuración antes de tocar código.

Tarea 4: Confirmar el manejador PHP en Apache (mod_php vs proxy_fcgi)

cr0x@server:~$ apachectl -M 2>/dev/null | egrep 'php|proxy_fcgi|mpm'
 mpm_event_module (shared)
 proxy_fcgi_module (shared)

Significado: Apache probablemente usa FPM vía proxy_fcgi, no mod_php.
Decisión: Si ves un php_module cargado, estás en mod_php y cambiar versiones es diferente y más arriesgado. Prefiere el aislamiento FPM para setups con múltiples versiones.

Tarea 5: Golpear un endpoint tipo phpinfo localmente de forma segura (sin exponerlo públicamente)

cr0x@server:~$ curl -sS -H 'Host: example.com' http://127.0.0.1/wp-admin/admin-ajax.php | head
0

Significado: Esto no muestra la versión de PHP, pero confirma que WordPress se ejecuta vía la ruta web localmente.
Decisión: Si esto devuelve 502/504 localmente, el problema está en el servidor/ruta de la aplicación, no en el CDN o DNS externo.

Tarea 6: Habilitar depuración de WordPress (temporalmente) y confirmar que se escriben logs

cr0x@server:~$ sudo -u www-data php -r 'echo "ok\n";'
ok

Significado: Tu usuario web puede ejecutar PHP y debería poder escribir logs si los permisos lo permiten.
Decisión: Si el usuario web no puede ejecutar o escribir donde se necesita, arregla permisos antes de perseguir fantasmas de “compatibilidad”.

Tarea 7: Seguir los logs de PHP-FPM para fatales y errores de pool durante una petición

cr0x@server:~$ sudo tail -n 30 /var/log/php8.1-fpm.log
[27-Dec-2025 12:14:22] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
[27-Dec-2025 12:14:27] ERROR: WARNING: [pool www] child 1842 said into stderr: "PHP Fatal error:  Uncaught TypeError: strlen(): Argument #1 ($string) must be of type string, null given in /var/www/html/wp-content/plugins/foo/bar.php:91"

Significado: Tienes tanto presión de capacidad (pm.max_children) como una incompatibilidad real (TypeError).
Decisión: Arregla el fatal primero (es de corrección). Luego aborda la capacidad, porque los errores pueden amplificar la carga por reintentos.

Tarea 8: Confirmar módulos PHP instalados (extensiones faltantes causan comportamientos raros)

cr0x@server:~$ php -m | egrep 'curl|gd|imagick|mbstring|mysqli|openssl|zip'
curl
gd
mbstring
mysqli
openssl
zip

Significado: Módulos como imagick podrían faltar tras una actualización, cambiando el comportamiento de procesado de medios.
Decisión: Si falta un módulo requerido, instálalo para la versión de PHP objetivo y reinicia FPM antes de culpar a WordPress.

Tarea 9: Comprobar versiones del core y plugins con WP-CLI

cr0x@server:~$ cd /var/www/html
cr0x@server:/var/www/html$ sudo -u www-data wp core version
6.4.3

Significado: Tienes la versión del core fijada.
Decisión: Si el core es antiguo respecto a tu PHP objetivo, actualiza el core en staging primero. La compatibilidad del core suele ser mejor que la de los plugins, pero no te la juegues.

Tarea 10: Encontrar los plugins más propensos a romperse (y desactivar uno sin tocar la BD manualmente)

cr0x@server:/var/www/html$ sudo -u www-data wp plugin list --status=active
+-----------------------+----------+-----------+---------+
| name                  | status   | update    | version |
+-----------------------+----------+-----------+---------+
| woocommerce           | active   | available | 8.1.1   |
| elementor             | active   | none      | 3.18.0  |
| foo-payments-gateway  | active   | none      | 2.4.7   |
+-----------------------+----------+-----------+---------+

Significado: Ahora tienes una lista corta de código que se ejecuta en la mayoría de las requests.
Decisión: Si un fatal apunta dentro de la ruta de un plugin, desactiva ese plugin en un nodo de prueba o en staging primero. En emergencia, desactívalo en producción solo si entiendes el impacto en el negocio.

Tarea 11: Ejecutar una comprobación de sintaxis PHP sobre un plugin o tema (caza errores de parseo temprano)

cr0x@server:/var/www/html$ find wp-content/plugins/foo-payments-gateway -name '*.php' -print0 | xargs -0 -n1 php -l | head
No syntax errors detected in wp-content/plugins/foo-payments-gateway/includes/api.php
No syntax errors detected in wp-content/plugins/foo-payments-gateway/foo.php

Significado: No hay errores de parseo. Eso es necesario, no suficiente.
Decisión: Si ves errores de parseo, ese plugin está muerto en esa versión de PHP. Actualízalo/reemplázalo, o no actualices PHP todavía.

Tarea 12: Detectar llamadas obsoletas y warnings ejecutando una página con reporte de errores más estricto (staging)

cr0x@server:~$ php -d display_errors=1 -d error_reporting=E_ALL -r 'trigger_error("test", E_USER_DEPRECATED);'
Deprecated: test in Command line code on line 1

Significado: Puedes forzar la visibilidad de deprecaciones en un entorno controlado.
Decisión: En staging, aumenta la visibilidad de errores y ejecuta los flujos críticos. Las deprecaciones pueden ser los fatales de mañana cuando las librerías se actualicen.

Tarea 13: Comprobar errores en tráfico real desde los access logs (ver picos de 502 y endpoints fallidos)

cr0x@server:~$ sudo awk '$9 ~ /^5/ {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
  184 /wp-admin/admin-ajax.php
   77 /?wc-ajax=checkout
   22 /wp-json/wc/v3/orders

Significado: Las fallas se agrupan alrededor de AJAX y checkout—rutas clásicas con mucho código de plugin.
Decisión: Enfoca las pruebas y la isolación en esas rutas. No pierdas tiempo refrescando la página de inicio.

Tarea 14: Confirmar ajustes de opcache (un opcache mal sintonizado puede parecer un “PHP upgrade rompió todo”)

cr0x@server:~$ php -i | egrep 'opcache.enable|opcache.memory_consumption|opcache.max_accelerated_files' | head
opcache.enable => On => On
opcache.memory_consumption => 128 => 128
opcache.max_accelerated_files => 10000 => 10000

Significado: OPcache está activado, pero la memoria puede o no ser suficiente según la proliferación de plugins.
Decisión: Si ves reinicios frecuentes del cache o “out of memory for opcache,” aumenta la memoria y los slots de archivos. Si no, perseguirás latencias fantasma.

Tarea 15: Comparar versiones de PHP en la flota (captura upgrades parciales)

cr0x@server:~$ for h in wp-app-01 wp-app-02 wp-app-03; do ssh $h "php-fpm8.1 -v 2>/dev/null | head -1 || php-fpm8.0 -v 2>/dev/null | head -1"; done
PHP 8.1.2 (fpm-fcgi) (built: Feb 15 2025 10:41:12)
PHP 8.1.2 (fpm-fcgi) (built: Feb 15 2025 10:41:12)
PHP 8.0.30 (fpm-fcgi) (built: Jan 10 2025 08:12:44)

Significado: Un nodo está en PHP 8.0 mientras otros están en 8.1.
Decisión: Para. Arregla la desviación de versiones antes de investigar errores “aleatorios”. Los runtimes mixtos son un impuesto de fiabilidad que pagas para siempre.

Tarea 16: Verificar qué archivo de config usa FPM (porque crees que lo cambiaste…)

cr0x@server:~$ php-fpm8.1 -tt 2>&1 | head -20
[27-Dec-2025 12:33:01] NOTICE: configuration file /etc/php/8.1/fpm/php-fpm.conf test is successful
[27-Dec-2025 12:33:01] NOTICE: using php.ini in /etc/php/8.1/fpm/php.ini

Significado: Estás editando la ruta de archivo correcta para FPM.
Decisión: Si apunta a otro lugar (contenedores, prefijos personalizados), ajusta tu tooling de despliegue. Editar el ini equivocado es un clásico eterno.

Tarea 17: Validar la capacidad del pool PHP-FPM y timeouts (evita mitos de “upgrade causó 502”)

cr0x@server:~$ sudo egrep 'pm\.max_children|pm\.max_requests|request_terminate_timeout' /etc/php/8.1/fpm/pool.d/www.conf | sed 's/;.*$//'
pm.max_children = 20
pm.max_requests = 500
request_terminate_timeout = 60s

Significado: Tu pool está limitado a 20 workers y mata requests a los 60 segundos.
Decisión: Si el tráfico o el código más lento aumentaron el tiempo de request, sube max_children (dentro de CPU/RAM) y ajusta el timeout de terminación. Pero no “resuelvas” errores fatales subiendo timeouts.

Patrones de actualización que evitan interrupciones (Nginx/Apache, PHP-FPM y rollbacks)

“Sin interrupciones” no significa “sin riesgo”. Significa que puedes cambiar versiones de runtime sin cortar conexiones ni dejar el sitio a oscuras. El truco es el paralelismo: ejecutar PHP antiguo y nuevo lado a lado, enrutar tráfico intencionalmente, y
dejar el rollback a un cambio de configuración de distancia.

Patrón A: Sockets PHP-FPM paralelos + cambio de tráfico por fases

Ejecuta dos servicios FPM: el viejo (p. ej., PHP 8.0) y el nuevo (p. ej., PHP 8.1). Conecta Nginx (o Apache proxy_fcgi) a un socket a la vez,
o mejor, divide el tráfico en el load balancer: canary en un nodo con el PHP nuevo, el resto en el viejo.

Este es el enfoque más limpio para tiempo de inactividad cercano a cero porque:

  • Desacopla la instalación de paquetes del cambio de tráfico.
  • Permite pruebas con tráfico real en una pequeña porción.
  • El rollback es rápido: cambia el socket upstream o quita el canario del pool.

Patrón B: Blue/green en la capa de aplicación con imágenes inmutables

Construye una imagen nueva (VM o contenedor) con el runtime PHP nuevo, mismas configuraciones, mismo código y plugins de WordPress, luego levántala
detrás del balanceador junto al pool antiguo. Drena y cambia. Así mantienes los cambios auditables.

Además evita “servidores copo de nieve” donde un nodo tiene una extensión distinta porque alguien depuró una vez y nunca lo documentó.

Patrón C: Mismos servidores, pero rollback rápido mediante toggle de configuración

Si debes cambiar in situ, mantén el servicio FPM antiguo instalado y habilitado, y trata el cambio como un deploy de configuración:
actualiza Nginx fastcgi_pass al socket nuevo y haz reload. Reload, no restart.

Reload significa que las conexiones existentes sobreviven; los nuevos workers toman la configuración. Restart te enseña sobre clientes enfadados.
Elige reload. Siempre.

El plan mínimo seguro de rollback

  • El PHP-FPM antiguo permanece instalado y en ejecución (o arrancable) durante toda la ventana de actualización.
  • Tu servidor web puede cambiar entre sockets o upstreams rápidamente.
  • OPcache se resetea al cambiar (o aceptas el riesgo de bytecode en caché mixto).
  • Sabes qué plugins/temas cambiaron recientemente y puedes desactivarlos con WP-CLI.

Broma #2: Lo único más permanente que una actualización temporal de PHP es el hotfix “temporal” que tendrás miedo de borrar.

Almacenamiento y estado: el multiplicador oculto de tiempo de inactividad

WordPress no es solo PHP. Es PHP más base de datos más uploads más caches. Una actualización de PHP puede desencadenar:

  • Stampedes de caché si se reinicia opcache y los object caches fallan a la vez.
  • Regeneración de medios cambios si difieren las librerías de imágenes (gd vs imagick disponibles).
  • Desplazamientos de permisos en el sistema de archivos si los paquetes cambian defaults de usuario/grupo o configs de pool.

Si usas almacenamiento compartido (NFS, EFS, Gluster, CephFS), ten cuidado: un plugin que haga muchas llamadas stat() puede
volverse más lento con un runtime PHP nuevo (o simplemente con distinto estado de calentamiento del opcache), y la latencia del almacenamiento pasa a importar de repente.
En producción, el almacenamiento nunca es “problema de otro”. Es solo un problema que aún no ha marcado tu número.

Tres mini-historias del mundo corporativo (dolor, arrogancia y competencia aburrida)

Mini-historia #1: El incidente causado por una suposición equivocada

Una compañía mediana ejecutaba WordPress para páginas de marketing y otra app separada para facturación. Marketing no era “crítico”,
frase que los ejecutivos dicen justo antes de pedir un ETA cada siete minutos.

El equipo de infra actualizó PHP de 7.4 a 8.1 en los nodos web durante un ciclo de parches rutinario. Validaron php -v, refrescaron la página de inicio, vieron un 200 y cerraron el ticket. La suposición fue simple: “Si la página de inicio carga, WordPress está bien.”

A la mañana siguiente, el equipo de contenido intentó publicar un post. El editor falló. Luego el login de admin empezó a devolver 500. La página de inicio seguía viéndose bien porque estaba cacheada en el CDN y la cache de borde no se preocupa por fatales de PHP.
Mientras tanto, las publicaciones programadas no se publicaron porque las llamadas WP-Cron fallaban, y un lanzamiento de campaña falló silenciosamente.

La causa raíz fue un plugin usado solo en la ruta del editor admin. Usaba una librería que lanzaba TypeError
en PHP 8.1 cuando se le pasaba null. Nunca se ejecutó en requests anónimos de la página de inicio. Nadie probó la ruta de administración. Nadie revisó logs. Todos probaron la página más probable de estar cacheada.

La solución fue vergonzosamente sencilla: fijar el plugin a una versión actualizada y añadir una prueba de “ruta crítica de admin” al checklist de release.
La lección real fue operativa: valida los flujos que importan, no las páginas que son convenientes.

Mini-historia #2: La optimización que salió mal

Otra organización quiso “acelerar WordPress” y decidió habilitar ajustes agresivos de OPcache y un pm.max_children mayor justo después de actualizar PHP. Dos cambios a la vez. Dos perillas, un tablero. Te puedes imaginar el final.

Inicialmente, las métricas parecían geniales: mayor throughput, menos picos de CPU. Luego la presión de memoria fue creciendo. Los nodos empezaron a hacer swapping. La latencia se volvió no lineal. Aparecieron algunos 502s, luego muchos. El equipo asumió que la nueva versión de PHP era inestable.

No lo era. Habían incrementado la cuenta de workers sin aumentar la memoria, y OPcache estaba configurado tan grande que a cada nodo le quedó menos margen para la carga real de requests. Bajo carga, el kernel empezó a reclamar memoria, el cache de páginas se churneó y los workers de PHP se quedaron atascados. El stack no “colapsó”. Se asfixió.

El rollback a la versión antigua de PHP no ayudó porque el tuning de recursos permaneció. Eso generó confusión: “Hicimos rollback pero sigue roto.” Tras una hora de investigación, revirtieron la configuración del pool FPM y OPcache y el sitio se recuperó.

La lección: ajustar rendimiento tiene radio de blast. Hazlo separado de una actualización de versión. Y si tocas pm.max_children, más vale que conozcas la memoria por worker, no solo el uso de CPU.

Mini-historia #3: La práctica aburrida pero correcta que salvó el día

Una tercera compañía ejecutaba WooCommerce con muchos plugins, algunos antiguos, otros a medida. Tenían una costumbre que parecía burocrática: cada actualización de runtime requería un nodo canario y una prueba scriptada de “top 20 endpoints”.
Sin excepciones.

La actualización a PHP 8.2 se planificó por fases. Construyeron nuevas imágenes, levantaron un único nodo canario y enrutaron el 2% del tráfico hacia él.
Las pruebas sintéticas golpearon checkout, add-to-cart, login, guardado de post en admin y algunos endpoints API. También replicaron patrones de tráfico reales reproduciendo una muestra pequeña de logs de acceso contra el canario (sanitizados y con rate-limit).

En minutos, los logs de error del canario mostraron warnings convirtiéndose en fatales dentro de un plugin de envío. El pool principal se mantuvo sano.
El equipo deshabilitó el plugin en el canario para confirmar el diagnóstico y luego sacó el canario de la rotación.
Los clientes no notaron nada.

La parte “aburrida” fue el runbook: los comandos exactos para diff configs, comparar listas de módulos, validar settings de pool y revertir pesos de tráfico. No fue ingenioso. Fue repetible. Eso les salvó.

Su arreglo posterior también fue aburrido: reemplazar el plugin de envío por una alternativa mantenida y programar la actualización de PHP de nuevo.
Perdieron un día, no un fin de semana.

Errores comunes: síntoma → causa raíz → solución

Si has estado en producción el tiempo suficiente, puedes diagnosticar la mitad de esto con una sola captura de pantalla. La otra mitad necesita logs.
Aquí tienes el mapa.

1) Síntoma: Pantalla blanca (200 OK, página en blanco)

  • Causa raíz: Error fatal de PHP con display_errors=Off, a menudo en la inicialización del tema o plugin.
  • Solución: Revisa el log de PHP-FPM para fatales. Habilita el logging de depuración de WordPress temporalmente. Identifica y actualiza/desactiva el plugin/tema culpable.

2) Síntoma: 502 Bad Gateway justo después de la actualización

  • Causa raíz: Nginx/Apache aún apunta al camino de socket antiguo, o FPM no está en ejecución, o el pool no puede arrancar por sintaxis en la config.
  • Solución: Confirma el objetivo de fastcgi_pass, ejecuta php-fpm -tt, revisa el estado de systemd, corrige permisos de socket, reload del servidor web.

3) Síntoma: Funciona a veces, falla a veces

  • Causa raíz: Desviación de versiones entre nodos; un servidor actualizado, otros no. O archivos de plugin inconsistentes en almacenamiento compartido.
  • Solución: Compara versiones de runtime en todos los nodos. Asegura despliegues atómicos e idénticos; no mezcles ediciones NFS a mano con despliegues CI.

4) Síntoma: Panel de administración roto, sitio público bien

  • Causa raíz: Plugin incompatible usado principalmente en rutas admin/editor. El CDN oculta fallos públicos.
  • Solución: Prueba workflows de admin en staging. Vigila logs del canario. Desactiva/actualiza plugins de uso exclusivo en admin primero.

5) Síntoma: Fallo en el checkout, página del carrito bien

  • Causa raíz: Incompatibilidad de pasarela de pagos o plugin de envío; el tipado más estricto en PHP 8 dispara fatales en casos límite raros.
  • Solución: Reproduce vía /?wc-ajax=checkout, lee errores de PHP, actualiza el plugin o reemplázalo. No “solo aumentes timeouts”.

6) Síntoma: Pico de CPU y requests lentos tras la actualización

  • Causa raíz: Inicio frío de OPcache, mala configuración del JIT, o disminución de efectividad del caching por resets; a veces un plugin activa una ruta más lenta en el nuevo PHP.
  • Solución: Calienta caches, confirma settings de opcache, revisa slowlog y compara perfiles de requests. Ajusta pool FPM solo después de restaurar la corrección.

7) Síntoma: Fallan uploads de imágenes o faltan thumbnails

  • Causa raíz: Falta de gd o imagick en la nueva versión de PHP; o versiones de librerías diferentes cambian comportamiento.
  • Solución: Instala las extensiones coincidentes, reinicia FPM, verifica con php -m, y vuelve a probar uploads.

8) Síntoma: “Allowed memory size exhausted” aparece más

  • Causa raíz: Límites de memoria diferentes por defecto en el ini de FPM; cambio de comportamiento de un plugin; mayor concurrencia incrementa memoria máxima.
  • Solución: Confirma memory_limit para FPM, ajústalo con los recursos reales, y audita el plugin que hace la asignación. No tapes fugas con memoria infinita.

Listas de verificación / plan paso a paso

Este es el plan que ejecutaría para un sitio WordPress en producción donde el tiempo de inactividad es inaceptable y las culpas abundan.
Sigue el orden. El orden es todo.

Fase 0: Decide qué significa “sin interrupciones” para ti

  • Objetivo: No mostrar una página de mantenimiento planificada; las sesiones existentes sobreviven; un breve aumento en la latencia de cola es aceptable.
  • No negociables: rollback en minutos, canary y visibilidad de logs.
  • Chequeo de realidad: Si ejecutas en un solo servidor sin load balancer, “sin interrupciones” se convierte en “tiempo de inactividad muy corto.” Aún puedes ser cuidadoso, pero la física gana.

Fase 1: Inventario del runtime, módulos y superficie de WordPress

  • Registra versión de PHP-FPM, ruta de ini, lista de módulos, ajustes de opcache.
  • Exporta la lista de plugins y el tema.
  • Identifica flujos críticos: login, guardar en admin, checkout, endpoints API, cron.
  • Confirma que puedes desactivar plugins vía WP-CLI si wp-admin queda inaccesible.

Fase 2: Construye un entorno de staging que no sea un cuento de hadas

  • Mismo código de WordPress, mismos plugins, mismo tema.
  • Mismas capas de caching (object cache, comportamiento de page cache), o al menos comprende las diferencias.
  • Copia la base de datos de producción (sanitizada) y un chunk representativo de uploads.
  • Usa la misma clase de config de PHP-FPM (timeouts, settings de pool) para detectar problemas de capacidad temprano.

Fase 3: Pruebas de compatibilidad que realmente encuentran problemas

  • Ejecuta los endpoints principales y flujos críticos bajo el PHP nuevo.
  • Convierte warnings en señal en staging: alto reporte de errores y captura de logs.
  • Vigila TypeErrors, warnings deprecados en rutas clave, extensiones faltantes.
  • Corre un escaneo de sintaxis dirigido y pruebas smoke en plugins conocidos por ser riesgosos (pagos, envíos, builders).

Fase 4: Canary en producción

  • Levanta un nodo con PHP nuevo (o cambia uno existente).
  • Enruta un pequeño porcentaje de tráfico hacia él.
  • Monitorea: tasa de 5xx, latencia, reinicios de PHP-FPM, slowlog, uso de memoria.
  • Si aparecen errores: quita el canario de la rotación, arregla en staging y prueba de nuevo.

Fase 5: Despliegue y estabilización post-cambio

  • Despliega gradualmente en la flota.
  • Calienta caches para evitar stampedes.
  • Mantén el PHP antiguo disponible hasta completar un ciclo de negocio completo (no solo 15 minutos de calma).
  • Tras la estabilidad: elimina el runtime antiguo, pero documenta los artefactos de rollback (cómo reinstalar rápido, qué paquetes).

Preguntas frecuentes

1) ¿Cómo sé si un problema en WordPress es incompatibilidad de PHP o solo un problema de capacidad?

Busca fatales y TypeErrors en los logs de PHP-FPM. Los problemas de capacidad se muestran como timeouts, “reached pm.max_children”, requests lentos y 502s sin un stack trace específico.
La incompatibilidad casi siempre deja una pista: ruta de archivo, número de línea y un tipo de error claro.

2) ¿Puedo actualizar PHP sin actualizar el core de WordPress?

A veces sí, pero es una mala apuesta. El core normalmente sigue las versiones de PHP soportadas, pero core antiguo más PHP moderno aumenta el riesgo de que los plugins entren en rutas no probadas.
Actualiza el core en staging primero, luego PHP. Si no puedes, al menos valida tu versión exacta de core contra el PHP objetivo en staging con flujos reales.

3) ¿Por qué WP-CLI funciona pero el sitio falla?

WP-CLI usa el SAPI CLI y su propia ruta de php.ini. Tu sitio usa PHP-FPM con distintos ini, distintas extensiones y diferentes permisos de usuario.
Siempre comprueba ambos. La diferencia no es académica; es donde viven los outages.

4) ¿Es seguro ejecutar dos versiones de PHP-FPM en el mismo servidor?

Sí, si aíslas sockets/puertos y configs por versión. Es un patrón operativo común para upgrades escalonados.
El riesgo es el error humano: apuntar Nginx al socket equivocado o instalar módulos en una versión y olvidarlo en la otra. Usa gestión de configuración y rutas explícitas.

5) ¿Cuál es el rollback más rápido si PHP 8.x rompe un plugin?

Si mantuviste el servicio FPM antiguo en ejecución: cambia el socket upstream de vuelta y haz reload de Nginx/Apache. Eso son minutos.
Si no lo hiciste: reinstala paquetes, reconfigura, reinicia servicios—ahora es un incidente, no un cambio.

6) ¿Debería habilitar display_errors en producción para ver qué pasa?

No. Registra errores en lugar de mostrarlos. Mostrar errores puede filtrar secretos y romper respuestas de forma fea.
Usa logs de PHP-FPM, el log de depuración de WordPress (temporal) y logging centralizado. Si necesitas visibilidad, añádela de forma segura.

7) ¿Por qué obtengo 502s solo durante picos de tráfico tras la actualización?

Tu runtime nuevo puede tener distintas características de rendimiento, o tus caches se reiniciaron y estás viendo una penalización de cold-start.
Bajo carga, pequeñas lentitudes se vuelven colas y luego timeouts. Revisa settings de pool FPM (pm.max_children), slowlog y timeouts de upstream.

8) ¿Necesito limpiar OPcache al cambiar versiones de PHP?

Si cambias sockets entre dos servicios FPM, cada uno tiene su propio estado de OPcache—no hay contaminación cruzada.
Si reinicias o recargas el mismo servicio tras cambios de código, resetear OPcache puede prevenir comportamientos extraños de “código viejo aún en ejecución”.
Pero no borres caches a ciegas en toda la flota; eso puede causar un stampede.

9) ¿Qué tipos de plugins son más peligrosos durante actualizaciones de PHP?

Pasarelas de pago, plugins de envío/impuestos, page builders, plugins de seguridad/firewall y cualquier cosa que empaquete grandes librerías de terceros.
Funcionan en rutas profundas de request y a menudo asumen tipos y entorno de servidor concretos.

10) Si no puedo hacer un canary, ¿cuál es la siguiente mejor opción?

Como mínimo: ejecuta viejo y nuevo PHP-FPM lado a lado en el mismo host y cambia vía configuración recargable, con un rollback probado.
Si tienes un solo nodo, programa una ventana de bajo tráfico y acepta que “sin interrupciones” pasa a ser “tiempo de inactividad corto”, luego reduce riesgo con pruebas exhaustivas en staging.

Conclusión: pasos prácticos siguientes

La incompatibilidad de PHP no es un fallo moral. Es un resultado predecible de ejecutar un gran ecosistema de plugins sobre un runtime que sigue mejorando su corrección.
Tu trabajo es hacer que la actualización sea aburrida: inventaria, stagea, canaryea, cambia y haz rollback rápido si es necesario.

  1. Hoy: Confirma tu runtime web real (versión FPM, cableado de sockets) y centraliza los logs que necesitarás en un incidente.
  2. Esta semana: Construye un entorno de staging que refleje los plugins, el caching y los workflows de producción. Prueba admin y checkout, no solo la página de inicio.
  3. En la próxima ventana de cambios: Ejecuta versiones PHP-FPM en paralelo y canaryea un nodo. Vigila logs de error y latencia cola. Avanza solo cuando las señales sean limpias.
  4. Tras el despliegue: Elimina desviaciones de versión, documenta el rollback y deja de hacer “dos cambios a la vez” en actualizaciones. Tu yo futuro ya tiene suficientes hobbies.
← Anterior
Sistemas grandes, errores pequeños: por qué «una línea» puede costar una fortuna
Siguiente →
Guerras de copias de seguridad MySQL vs MariaDB en un VPS: mysqldump frente a copias físicas

Deja un comentario