SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-23

Desactivar wp-cron y usar system cron en WordPress

WP-Cron sólo se ejecuta con visitas: desactívelo, prográmelo con system cron y WP-CLI, y verifique que las tareas se ejecutaron realmente.

Qué es wp-cron y por qué system cron lo sustituye

WP-Cron es el programador de tareas integrado en WordPress y sólo se ejecuta cuando alguien solicita una página. WordPress no inicia ninguna tarea por sí solo. En cada solicitud que no se sirve desde la caché, WordPress lee una lista de trabajos programados y, si alguno está pendiente, envía una segunda solicitud HTTP a /wp-cron.php para ejecutarlo. Si mueve ese trabajo a system cron, obtiene una ejecución predecible en un horario fijo, tanto si el sitio tuvo mil visitantes durante ese minuto como si no tuvo ninguno.

Dos líneas realizan el trabajo: una constante en wp-config.php y una entrada de crontab. Todo lo demás de esta guía cubre lo que esas dos líneas no indican. Debe determinar con qué usuario se ejecuta el trabajo, comprobar que los eventos programados se ejecutaron realmente y conocer las tres formas en que la configuración puede fallar sin mostrar ningún mensaje en el sitio.

Los ejemplos usan /srv/www/example.com como directorio de WordPress y www-data como usuario del servidor web. Sustituya estas rutas y este usuario por los valores de su entorno en todos los casos.

Qué visitante provocó el coste de cron en un sitio con mucha carga

Cada petición que no se sirve desde la caché paga el coste de la comprobación. WordPress carga la opción cron, compara las marcas de tiempo y, cuando hay una tarea pendiente, llama a spawn_cron(), que envía una petición de loopback no bloqueante a /wp-cron.php. El visitante no espera el resultado. Un proceso de PHP sí lo hace. En un VPS pequeño que ejecuta PHP-FPM con pm.max_children = 5, una tarea programada lenta ocupa una quinta parte de la capacidad de PHP durante todo el tiempo que tarda. Además, lo más probable es que se active durante el minuto de mayor carga, porque es cuando se cargan más páginas.

WordPress limita los duplicados. Adquiere un bloqueo cuya duración es WP_CRON_LOCK_TIMEOUT, 60 segundos de forma predeterminada, para que varios visitantes simultáneos no inicien ejecuciones separadas. El bloqueo limita la duplicación. No saca el trabajo de la ruta de la petición.

Cuente con qué frecuencia se ejecuta en su propio servidor antes de decidir si es importante. Cada loopback aparece en el registro de acceso del servidor web:

sudo grep -c 'wp-cron.php' /var/log/nginx/access.log

Apache escribe en /var/log/apache2/access.log en su lugar. Varios miles al día representan un coste real. Es el tipo de cifra que debe medir en su propio servidor, en lugar de leerla en un artículo, igual que compararía el rendimiento de un VPS antes y después de cualquier otro cambio.

La caché cambia el resultado. Si una caché de páginas sirve la mayoría de las peticiones como HTML estático, PHP no se ejecuta para esas peticiones y la comprobación de cron nunca se realiza. Un sitio con mucha carga y una caché eficaz empieza a comportarse como el sitio tranquilo de abajo.

Qué visitante activó cron y provocó fallos en un sitio con poco tráfico

Sin visitantes, no hay cron. Un sitio que recibe unas pocas visitas al día ejecuta sus tareas programadas unas pocas veces al día, en momentos aleatorios determinados por la llegada de esas visitas.

Los síntomas siguen siempre el mismo patrón. Una entrada programada para las 09:00 permanece en la lista de entradas con la marca Missed schedule hasta que alguien carga una página. Los plugins de copia de seguridad omiten la ejecución nocturna. Las comprobaciones de actualizaciones se retrasan, por lo que el panel no muestra ninguna actualización aunque ya esté disponible una versión de seguridad. Los correos de pedidos, los avisos de renovación y las advertencias de caducidad se envían tarde.

Nada de esto registra un error. Desde el punto de vista de WordPress, la tarea nunca llegó tarde porque nunca se inició.

Paso 1: desactive el activador de visitantes en wp-config.php

Abra /srv/www/example.com/wp-config.php y añada la constante:

define( 'DISABLE_WP_CRON', true );

Colóquela encima de la línea que contiene /* That's all, stop editing! Happy publishing. */, porque la línea situada justo debajo de ese comentario requiere wp-settings.php, y wp-settings.php es donde WordPress conecta la comprobación de cron con init. Una constante definida después de ese require se establece demasiado tarde y no cambia nada. El archivo parece correcto, pero el activador sigue ejecutándose.

Confirme que la línea está donde espera:

grep -n "DISABLE_WP_CRON\|stop editing" /srv/www/example.com/wp-config.php

La constante no impide que se programen eventos. Los plugins siguen añadiendo trabajos a la cola, exactamente igual que antes. Sólo impide que las cargas de página procesen esa cola. Por tanto, la cola ya no se ejecutará hasta que termine el paso 3.

Tampoco bloquea las solicitudes directas a /wp-cron.php. Cualquiera puede solicitar esa URL. Normalmente no supone un problema, porque el archivo sólo ejecuta lo que está pendiente. Bloquearla en la configuración del servidor web es opcional. Si la bloquea, el recurso alternativo curl situado cerca del final de esta guía también dejará de funcionar.

Paso 2: instalar WP-CLI

WP-CLI es la herramienta oficial de línea de comandos para WordPress. Necesita el binario de PHP para la línea de comandos, que es un paquete independiente del módulo de PHP del servidor web.

php -v
sudo apt install -y php-cli

Instale WP-CLI desde la compilación phar, que es la opción recomendada en la guía oficial de instalación:

cd /tmp
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
wp --info

php wp-cli.phar --info muestra la ruta del binario de PHP, la versión de PHP y la versión de WP-CLI. Si muestra los tres valores, el archivo phar funciona. En agosto de 2026, la guía de instalación establece PHP 7.2.24 como versión mínima, y Ubuntu 24.04 incluye PHP 8.3, por lo que un servidor actualizado supera ampliamente ese requisito. Más adelante, actualícelo con sudo wp cli update.

Ejecute WP-CLI como el usuario del sitio, nunca como root:

sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com core version

Como root, WP-CLI se niega a iniciar:

Error: YIKES! It looks like you're running this as root.

Sugiere --allow-root. No lo use aquí. El motivo se explica en el primer modo de fallo que aparece a continuación.

Tenga en cuenta también que sudo -u www-data -i no funciona, porque el shell de inicio de sesión de esa cuenta es /usr/sbin/nologin y se obtiene This account is currently not available. Al pasar el comando directamente a sudo -u se omite el shell de inicio de sesión, por lo que se ejecuta correctamente.

Ahora confirme que el propio WordPress detecta la constante del paso 1:

sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com eval 'var_dump( DISABLE_WP_CRON );'

Esto muestra bool(true). Un error fatal sobre una constante no definida significa que no se está llegando a la línea define(), lo que normalmente indica que quedó después de require.

Paso 3: añada la entrada de cron con el usuario correcto

El usuario correcto es el propietario de los archivos que escribe PHP. Compruebe ambos extremos:

stat -c '%U %G' /srv/www/example.com/wp-content/uploads
grep -E '^(user|group) = ' /etc/php/8.3/fpm/pool.d/www.conf

En una instalación predeterminada de Ubuntu, ambos comandos devuelven www-data. Si asignó al sitio un grupo PHP-FPM propio con su propio usuario, que es el resultado habitual de una configuración por sitio en una pila LAMP en Ubuntu 24.04, use ese usuario para todo lo siguiente.

Cree un directorio de registros en el que ese usuario pueda escribir:

sudo install -d -o www-data -g www-data -m 750 /var/log/wp-cron

Edite el crontab de ese usuario:

sudo crontab -u www-data -e

Añada una línea:

*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1

Por partes. */5 lo ejecuta cada cinco minutos. flock -n adquiere un archivo de bloqueo y termina de inmediato si una ejecución anterior todavía lo mantiene. /usr/local/bin/wp es la ruta absoluta que necesita cron. --path permite ejecutar el comando desde cualquier directorio de trabajo. --due-now ejecuta sólo los eventos cuya hora ya ha llegado, en lugar de ejecutar todos los eventos de la cola. La redirección envía la salida normal y los errores a un único archivo que puede leer.

En la práctica, esa redirección no es opcional. Cron envía por correo la salida de un trabajo a su usuario, la mayoría de las imágenes VPS no tienen instalado un agente de transferencia de correo y cron registra entonces (CRON) info (No MTA installed, discarding output) y descarta la salida. Un archivo conserva la información necesaria para diagnosticar el problema.

Compruebe que el archivo se haya guardado:

sudo crontab -u www-data -l

Para varios sitios, use una línea para cada uno y distribuya los minutos para que no se inicien todos a la vez:

*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-one.lock /usr/local/bin/wp --path=/srv/www/one.example.com cron event run --due-now >> /var/log/wp-cron/one.log 2>&1
2-59/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-two.lock /usr/local/bin/wp --path=/srv/www/two.example.com cron event run --due-now >> /var/log/wp-cron/two.log 2>&1

El registro crece indefinidamente si no lo rota. Escriba /etc/logrotate.d/wp-cron:

/var/log/wp-cron/*.log {
    weekly
    rotate 4
    missingok
    notifempty
    compress
    create 640 www-data www-data
}

Compruebe que se analiza correctamente sin ejecutar nada: sudo logrotate --debug /etc/logrotate.d/wp-cron.

Paso 4: confirme que los eventos programados se ejecutaron realmente

Que una línea de crontab se haya guardado correctamente no demuestra nada. Empiece por la comprobación más sencilla y avance hasta la que confirme el resultado.

Primero, ¿cron inició el comando? Cron escribe en el journal mediante su propia unidad:

journalctl -u cron.service --since "15 min ago" | grep wp

Una entrada correcta tiene este aspecto, sin incluir el timestamp ni el nombre del host al principio:

CRON[24913]: (www-data) CMD (/usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1)

Esa línea significa que cron inició el comando como www-data. No indica si el comando se ejecutó correctamente.

Segundo, ¿WordPress ejecutó algo? Lea el archivo de registro:

sudo tail -n 20 /var/log/wp-cron/example.log

WP-CLI muestra una línea por evento y, después, un total:

Executed the cron event 'wp_version_check' in 0.418s.
Success: Executed a total of 2 cron events.

Los errores se escriben en el mismo archivo, que es precisamente el objetivo de 2>&1. En la mayoría de las ejecuciones no habrá eventos pendientes y se escribirá muy poco, por lo que debe leer el archivo después de una ejecución en la que sepa que había trabajo pendiente.

Tercero, compruébelo de principio a fin. Programe un evento marcador y observe cómo desaparece:

sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event schedule wp_cli_cron_check now
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event list --fields=hook,next_run_relative --format=csv | grep wp_cli_cron_check

Espere un intervalo y vuelva a ejecutar el comando de listado. El hook habrá desaparecido porque un evento de una sola ejecución se elimina de la cola cuando se ejecuta. Ningún plugin registra un callback para ese nombre de hook, por lo que ejecutarlo no hace nada más en el sitio. Si el hook sigue apareciendo después de dos intervalos, la cola no se está ejecutando. Las dos primeras comprobaciones le indican si el problema está en cron o en WP-CLI.

No use wp cron test para esto. Ese comando comprueba si funciona la creación de procesos activada por visitantes y genera un error cuando DISABLE_WP_CRON es true. En un servidor configurado correctamente, el error es el resultado esperado, no un fallo.

La alternativa del temporizador de systemd

Si el resto de las tareas programadas del servidor ya se ejecuta mediante servicios y temporizadores de systemd, incluya WordPress ahí también. Cada ejecución aparecerá en systemctl list-timers y la salida se enviará al journal en lugar de a un archivo que tenga que rotar.

Escriba /etc/systemd/system/wp-cron-example.service:

[Unit]
Description=Run due WordPress cron events for example.com

[Service]
Type=oneshot
User=www-data
Group=www-data
ExecStart=/usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now

A continuación, /etc/systemd/system/wp-cron-example.timer:

[Unit]
Description=Run WordPress cron for example.com every 5 minutes

[Timer]
OnCalendar=*:0/5
AccuracySec=30s
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now wp-cron-example.timer
systemctl list-timers wp-cron-example.timer
journalctl -u wp-cron-example.service -n 20

systemd no ejecutará dos instancias del mismo servicio al mismo tiempo, por lo que esta versión no necesita flock. Persistent=true hace que recupere una ejecución omitida mientras el equipo estaba apagado, algo que una entrada de crontab no puede hacer.

Elija el crontab o el temporizador. Ejecutar ambos contra el mismo sitio hace que la cola se procese dos veces, y sus clientes notarán las ejecuciones duplicadas de una tarea de correo electrónico o de pedidos.

Por qué el trabajo de cron no debe ejecutarse como root

Esta es la primera de las tres formas en que falla la configuración. Si coloca el trabajo en el crontab de root, WP-CLI se detiene antes de hacer nada:

Error: YIKES! It looks like you're running this as root.

La cola nunca se ejecuta y, si no redirigió la salida, nunca verá el mensaje. La solución peligrosa consiste en añadir --allow-root, porque entonces todos los archivos que un plugin escriba durante esa ejecución pertenecerán a root. La siguiente petición web se ejecuta como www-data, no puede escribir en esos directorios y el sitio empieza a mostrar mensajes como estos:

Unable to create directory wp-content/uploads/2026/08. Is its parent directory writable by the server?

Corrija la propiedad y, después, mueva el trabajo:

sudo chown -R www-data:www-data /srv/www/example.com/wp-content

El crontab de root y el crontab de www-data son archivos independientes, por lo que eliminar la línea de uno no afecta al otro. Compruebe ambos:

sudo crontab -u root -l
sudo crontab -u www-data -l

Por qué cron informa wp: not found

Este es el segundo fallo. Cron proporciona a los trabajos de usuario un PATH muy reducido: /usr/bin:/bin. WP-CLI se instala en /usr/local/bin, que no está incluido en esa lista. El trabajo se inicia, falla en una fracción de segundo y el registro contiene una sola línea:

/bin/sh: 1: wp: not found

Compruebe directamente el entorno de cron en lugar de hacer suposiciones. Añada una línea temporal:

*/5 * * * * env > /tmp/cron-env.txt 2>&1

Lea /tmp/cron-env.txt después de un intervalo y elimine la línea. El valor de PATH= en ese archivo es exactamente el entorno que recibe el trabajo.

Hay dos soluciones. Use la ruta absoluta /usr/local/bin/wp, como en el paso 3. O establezca PATH una sola vez al principio de crontab, antes de todas las líneas de trabajos:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

El mismo problema aparece un nivel más abajo. El archivo phar de wp comienza con #!/usr/bin/env php, por lo que el shell también debe poder encontrar php. Si PHP está fuera de /usr/bin, algo habitual en compilaciones personalizadas y compilaciones de paneles de control, aparece lo siguiente:

/usr/bin/env: 'php': No such file or directory

En ese caso, invoque explícitamente el intérprete, por ejemplo /usr/local/bin/php /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now.

Por qué un intervalo de un minuto recrea el problema original

Este es el tercer fallo. * * * * * parece más seguro que cinco minutos, pero en un sitio con mucha actividad te devuelve al punto de partida. Si una ejecución tarda más que el intervalo, la siguiente empieza mientras la primera aún está en curso. Diez minutos después hay diez procesos de PHP, cada uno con su propia memoria y su propia conexión a la base de datos.

Comprueba directamente si se están acumulando procesos:

ps -eo etimes,user,args | grep '[c]ron event run'

etimes es la antigüedad del proceso en segundos. Una línea indica un estado normal. Varias líneas con antigüedades muy superiores al intervalo indican que las ejecuciones se están acumulando. En un VPS pequeño, esto termina con un error de Too many connections de MySQL o con el kernel finalizando PHP para recuperar memoria. Puedes confirmarlo con sudo dmesg -T | grep -i 'killed process'.

WP-CLI ejecuta directamente las devoluciones de llamada de los eventos en lugar de solicitar wp-cron.php, por lo que el bloqueo de 60 segundos que usa WordPress para evitar procesos duplicados no se aplica aquí. flock -n en la entrada del paso 3 es lo que impide ahora la ejecución simultánea. Una ejecución omitida termina de inmediato y sin mostrar mensajes, por diseño. La ejecución simultánea es un problema que debes resolver en el crontab, no uno que el kernel del host resuelva por ti: incluso la asignación de tareas teniendo en cuenta la caché añadida en Linux kernel 7.2 sólo decide en qué núcleo se ejecuta un proceso, nunca cuántos procesos has iniciado.

Elige el intervalo según la programación más corta de la que realmente dependas y mide primero una ejecución:

time sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now

Cinco minutos es un valor predeterminado razonable: una entrada programada para las 09:00 se publica antes de las 09:05. Quince minutos es suficiente para un sitio que no tenga nada crítico en cuanto al tiempo. Un minuto sólo es adecuado para tiendas y plugins basados en colas que realmente lo necesiten, y únicamente cuando sepas que una ejecución termina en unos segundos.

Si no puede instalar WP-CLI

Algunos hosts bloquean las herramientas de shell. Una solicitud HTTP simple a wp-cron.php ejecuta la misma cola, pero pasa por toda la pila web:

*/5 * * * * /usr/bin/curl -sS --max-time 120 "https://example.com/wp-cron.php?doing_wp_cron" > /dev/null

Lo que pierde, de forma clara:

  • La ejecución está limitada por los tiempos de espera del servidor web y de PHP-FPM, por lo que un trabajo largo puede interrumpirse a mitad.
  • El certificado debe ser válido o curl se detiene con SSL certificate problem, así que mantenga las renovaciones activas con Certbot en nginx.
  • La caché de páginas no debe almacenar wp-cron.php, porque las solicitudes de cron recibirían una respuesta en caché y no se ejecutaría nada.
  • No obtiene resultados por evento, por lo que la única evidencia de que se ejecutó un trabajo es el efecto que produjo.

-sS mantiene a curl en silencio cuando la ejecución tiene éxito, pero sigue mostrando los errores. Eso es lo que necesita en un trabajo de cron.

Qué más debe incluir la programación del servidor

Una vez que el cron del sistema gestiona la cola de WordPress, mantenga el resto de las tareas rutinarias del servidor en el mismo lugar, donde pueda supervisarlas. Los parches de seguridad del sistema operativo deben gestionarse mediante actualizaciones desatendidas, no mediante una línea de cron que mantenga manualmente. Las actualizaciones de plugins y temas de WordPress requieren otra decisión: wp plugin update --all en un crontab puede dejar fuera de servicio un sitio activo a las 3am sin que nadie lo supervise, así que ejecútelas de forma deliberada o después de una fase de staging y una copia de seguridad.

FAQ

¿Deshabilitar WP-Cron impide que se publiquen las entradas programadas?

No, siempre que otro mecanismo ejecute la cola. DISABLE_WP_CRON sólo impide que las cargas de página activen la cola. Los eventos siguen programados exactamente igual que antes. Una entrada programada para las 09:00 se publica en la primera ejecución de cron posterior a las 09:00, por lo que un intervalo de cinco minutos la publica como máximo a las 09:05. Si establece la constante y nunca añade la entrada de cron, la entrada permanece en la lista con la marca Missed schedule hasta que algo ejecute la cola.

¿Qué usuario debe ejecutar el trabajo de cron de WordPress?

El usuario propietario de los archivos en los que escribe PHP, que es www-data en una instalación predeterminada de Ubuntu. Compruébelo con stat -c '%U %G' /srv/www/example.com/wp-content/uploads y compárelo con la línea user = de la configuración del pool de PHP-FPM. Ejecutar el trabajo como root hace que WP-CLI se detenga con un error YIKES, y forzarlo mediante --allow-root deja archivos propiedad de root dentro de wp-content en los que el servidor web no puede escribir después.

¿Con qué frecuencia debe system cron ejecutar el cron de WordPress?

Cada cinco minutos es adecuado para la mayoría de los sitios. Adapte el intervalo al periodo más corto del que realmente dependa y manténgalo por encima del tiempo que tarda una ejecución individual, que puede medir colocando time delante del comando de WP-CLI. En un sitio con mucha carga, los intervalos de un minuto hacen que las ejecuciones se superpongan, salvo que flock las controle.

¿Por qué wp cron test falla después de deshabilitar WP-Cron?

Porque ese comando comprueba la generación activada por los visitantes y devuelve un error cuando DISABLE_WP_CRON está establecido en true. Ese es el resultado correcto en un servidor configurado de esta forma. Compruebe la ruta de system cron: lea /var/log/wp-cron/example.log o programe un evento marcador con wp cron event schedule y confirme que haya desaparecido de wp cron event list después de la siguiente ejecución.

¿Necesito WP-CLI o basta con usar curl con wp-cron.php?

curl funciona y es la opción adecuada cuando no puede instalar WP-CLI. Es más lento porque carga WordPress a través del servidor web y está limitado por el tiempo de espera de la petición. WP-CLI ejecuta los eventos en un proceso de PHP de línea de comandos sin tiempo de espera web e imprime una línea por evento con su duración, por lo que el registro muestra exactamente qué se ejecutó y cuánto tardó.