← Blog

Cómo correr una tarea cron sin un servidor

Railyard

4 de agosto de 2026 · 7 min de lectura

Sigue esto en tu propio servidor. Empieza gratis, sin tarjeta

La tarea en sí es pequeña: respaldar una tabla, enviar un resumen semanal, vencer algunas filas. Levantar todo un servidor, o toda una app, para correr un solo comando en un horario se siente desproporcionado para la tarea, y es un instinto razonable. Esta guía cubre lo que realmente necesitas para un script que tiene que correr en un horario en vez de responder a solicitudes: primero la versión manual simple (una VM barata, crontab, y la parte que la gente se salta, confirmar que realmente corrió), y luego el verdadero Cron de Railyard, descrito con precisión en vez de aspiracionalmente.

Lo que necesitas

  • El script en sí, ejecutable como un solo comando (node send-digest.js, python cleanup.py, una línea de shell), con las variables de entorno que necesita ya identificadas.
  • Para la ruta manual: acceso SSH a cualquier VM Linux pequeña. Un Hetzner CX23 (2 vCPU, 4 GB de RAM, 5.99 € sin IVA al mes) es más que suficiente para un script que corre segundos o minutos en un horario, no de forma continua.

La versión manual: una VM, crontab, y prueba de que corrió

Escribe la tarea como un solo comando

Envuelve cualquier cosa de más de una línea en un script, para que cron tenga una sola cosa que llamar:

#!/usr/bin/env bash
set -euo pipefail

cd /opt/myjob
set -a
source /opt/myjob/.env
set +a

node send-digest.js >> /var/log/myjob/digest.log 2>&1

set -euo pipefail importa particularmente aquí porque cron no reintenta una corrida fallida por ti; sin esto, un paso que falla a mitad de camino puede dejar el resto del script corriendo igual y reportando éxito. set -a antes de source exporta cada variable que define el archivo .env, para que el comando de la última línea realmente las herede; cron mismo arranca las tareas con un entorno casi vacío, no el de tu shell.

Hazlo ejecutable y córrelo una vez a mano antes de programarlo:

chmod +x /opt/myjob/run.sh
/opt/myjob/run.sh
echo $?

Deberías ver 0.

Agrega la entrada de crontab, con bloqueo

crontab -e
0 3 * * * /usr/bin/flock -n /tmp/myjob.lock /opt/myjob/run.sh

flock -n toma un bloqueo exclusivo antes de correr el comando y se rinde de inmediato, en vez de encolar una segunda corrida, si la corrida anterior todavía lo tiene. Sin esto, una tarea que a veces tarda más se superpone con su propia siguiente corrida programada, a menudo contra las mismas filas.

Confirma que realmente corrió, no solo que estaba programada

Que exista una entrada de crontab no dice nada sobre si la tarea tuvo éxito anoche. La versión honesta de “monitoreo” aquí es un interruptor de hombre muerto: la propia tarea hace ping a una URL cuando tiene éxito, y algo te alerta si ese ping no llega a tiempo. Auto-alojar esa verificación es en sí un pequeño servicio siempre activo; consulta auto-alojar Uptime Kuma o auto-alojar Healthchecks para esa parte específicamente. La entrada de crontab se convierte en:

0 3 * * * /usr/bin/flock -n /tmp/myjob.lock /opt/myjob/run.sh && curl -fsS https://hc-ping.com/your-check-id

El && significa que el ping solo se dispara si run.sh terminó con éxito; un fallo silencioso ahora aparece como un check-in perdido en vez de como nada en absoluto.

Para el detalle de zonas horarias y logging completo que esta versión condensada se salta, reemplazar Heroku Scheduler con cron cubre el mismo terreno con más profundidad, incluyendo por qué el comportamiento por defecto de UTC contra hora local de cron del sistema atrapa a casi todos al migrar desde una plataforma con su propio planificador.

Qué es realmente el Cron de Railyard

Railyard tiene una función real de tarea programada, etiquetada Cron en el panel, y vale la pena describirla con precisión en vez de vagamente. Agrega una entrada con un nombre, un horario, y un comando:

  • El horario es una expresión cron simple de cinco campos (0 3 * * *), analizada y evaluada de la misma forma en que cron del sistema lee una, en UTC.
  • El comando corre dentro de una instancia nueva de la propia imagen de tu app, exactamente el mismo build que ya corren tus procesos web y worker, no un contenedor separado que tienes que mantener sincronizado a mano.
  • Cada corrida queda registrada: hora de inicio, código de salida, y salida, guardadas como historial por tarea, así que “¿realmente corrió el trabajo de anoche?” es una página que revisas en vez de un archivo de log que tienes que acordarte que existe.

Ese último punto reemplaza el truco del interruptor de hombre muerto de la versión manual de arriba con algo integrado: el propio historial de ejecución es el registro de si la tarea corrió y qué pasó, sin que un segundo servicio le haga ping a un tercero para avisarte.

Una limitación honesta, la misma que encontrarías corriéndolo tú mismo: el Cron de Railyard no se salta una corrida mientras una anterior de la misma tarea sigue en curso. La protección flock -n de la versión manual es algo que igual tienes que incorporar a la tarea misma si puede realísticamente durar más que su propio intervalo, ya sea haciendo que el comando sea seguro de correr dos veces a la vez, o haciendo que verifique y salga temprano si detecta que una corrida anterior sigue en progreso.

Consulta Workers y tareas programadas para ver cómo Cron se ubica junto a la pestaña separada Workers, que maneja procesos de fondo de larga duración (un consumidor de cola, por ejemplo) en vez de tareas programadas; ambos corren la misma imagen, así que un despliegue que arregla la app arregla sus tareas programadas al mismo tiempo, por construcción.

Configurándolo

Agrega la tarea desde la pestaña Cron de la app: un nombre, la expresión de horario, y el comando, exactamente como lo escribirías si entraras por SSH al contenedor de la app y lo corrieras directamente. Las variables de entorno que la tarea necesita son las mismas que ya están definidas en la pestaña Variables de la app, ya que el comando corre en esa misma imagen y entorno; no hay un archivo .env separado que sourcear ni un crontab que editar en un servidor, porque no hay ningún servidor separado corriendo la tarea en absoluto. Consulta variables y secretos.

Solución de problemas

La tarea corre a la hora equivocada. El Cron de Railyard evalúa los horarios en UTC; si escribiste un horario asumiendo hora local, conviértelo a UTC en vez de asumir que la plataforma lo lee de otra forma. Esta es la misma conversión que atrapa a la gente al migrar de un planificador de plataforma que también usaba UTC, como el de Heroku.

La tarea parece superponerse consigo misma. Como se mencionó arriba, esto no se previene automáticamente; agrega la misma protección que agregarías a una entrada de crontab normal, ya sea idempotencia en el comando mismo, o una verificación explícita de bloqueo al inicio del script, si la tarea puede realísticamente tardar más que su propio intervalo.

Nada confirma que la tarea corrió. Revisa primero el historial de ejecución directamente en la pestaña Cron en vez de los logs de la app; una corrida que terminó con un código de salida distinto de cero igual aparece ahí, con su código de salida y su salida, lo cual suele ser más rápido de leer que reconstruir la misma información desde la pestaña Logs.

Preguntas frecuentes

¿Necesito una app completa solo para correr un script programado? No más de lo que necesitarías un servidor completo para la versión manual. Si el script es realmente todo lo que estás desplegando, sin ningún proceso web, esa es una forma normal y soportada; una tarea programada y una app solo-worker simplemente significan que no hay un tipo de proceso web, no que falte algo.

¿Cuál es el intervalo mínimo en que puede correr un horario? La sintaxis cron estándar de cinco campos baja hasta una vez por minuto (* * * * *), el mismo piso que cron del sistema; nada por debajo del minuto es expresable en ese formato, sin importar dónde se evalúe.

¿En qué se diferencia esto de un worker en segundo plano? Un worker es un proceso que arranca una vez y sigue corriendo indefinidamente, consumiendo una cola o manteniendo una conexión abierta. Una tarea cron arranca, hace una cosa, y termina, según un horario. Son formas de proceso distintas para tareas distintas, cubiertas por dos funciones separadas y relacionadas en vez de una sola que intente hacer ambas cosas.

¿Qué pasa si el servidor subyacente se reinicia a mitad de una tarea? Lo mismo que le pasa a cualquier proceso matado de otra forma: se detiene, sin terminar. Una tarea segura de correr dos veces (idempotente) tolera esto sin manejo especial; una que no lo es necesita hacerse idempotente sin importar qué la esté programando, ya que el mismo modo de fallo existe también bajo un cron simple en una VM.

¿Puedo disparar la tarea manualmente, no solo según su horario? Sí, una tarea programada se puede correr bajo demanda desde el mismo lugar donde revisas su historial de ejecución, independientemente de esperar su próximo horario programado, lo cual es útil para probar un cambio antes de confiarle el horario real de las 3 a.m.

Ya sea una línea de crontab en una VM de cinco dólares o una entrada en un panel, una tarea cron sigue siendo las mismas tres cosas por debajo: un comando, un horario, y una forma de saber que realmente corrió. Acertar en la tercera suele ser lo que faltaba, no las dos primeras.

Por qué eligen Railyard para esto

  • Tu propia cuenta en la nube. Despliega en DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, o cualquier servidor Linux por SSH, facturado directamente por tu proveedor, sin márgenes adicionales.
  • Un precio fijo por equipo. El plan Free es gratis para siempre: un servidor más un servidor de base de datos, tres apps, sin tarjeta. Pro cuesta $19 al mes por hasta cinco servidores y apps ilimitadas. Team cuesta $49 al mes con servidores ilimitados. Sin cargos por app, por despliegue, por asiento ni por gigabyte.
  • Plantillas de un clic. Despliega apps de código abierto, como bases de datos, analítica y herramientas de desarrollo, en tu propio servidor con un clic.
  • Bases de datos administradas. Postgres, MySQL y Redis, con copias de seguridad programadas y verificadas por restauración, y recuperación a un punto en el tiempo.
  • Despliegue con git push. Despliega desde GitHub, GitLab o la CLI, con HTTPS automático y lanzamientos sin tiempo de inactividad.

Prográmala desde la página de la app

El Cron de Railyard corre un comando dentro de la imagen de tu app según un horario, con historial de cada ejecución, sin crontab ni VM propia.

Plan gratis, sin tarjeta, y 14 días de Pro para cada equipo nuevo. Un precio fijo por equipo.