Cronhq was pretty straightforward to test. The retry timing and timeouts behaved as configured, and my five-minute schedule ran consistently without duplicates or missed runs.
I also liked how simple the heartbeat setup was — just adding a curl call to an existing job was enough. Setup was quick, the API errors were clear, and I didn’t have to add any extra infrastructure.
A few things could make the experience even clearer. It would be helpful if retries appeared separately in the execution history, with duration and HTTP status reflecting each attempt more accurately. I also didn’t receive a recovery alert during my test, so that may be worth checking.
It’d also be nice to see the linked repository and CLI package available, and for the retry and retention docs to stay closely aligned with the current behavior.
I also looked at self-managed cron, cloud schedulers, and tools like Cronitor, Cronhub, and Healthchecks.io. What I liked about Cronhq is that it combines scheduling and heartbeat monitoring without needing an agent.
I only tested the managed Free tier, so I didn’t compare every alternative directly or try the self-hosted/CLI options.
@michael_sunmisola To your second question: before trusting it with a job that moves money, I'd look for a run ID in the webhook headers.
Exactly-once covers your side. If two workers never fire the same slot, fine. Instead retries are a different story though: if my endpoint does the work and the response gets lost or times out, you call me again and the charge happens twice on my side. In the docs I only see X-Cronhq-Timestamp and X-Cronhq-Signature, and I guess the timestamp changes on every attempt, so I have nothing to dedupe on.
I hit this with Celery and Redis: long tasks got redelivered, ran twice, and I had to build the guard myself.
Is there an execution ID that stays the same across the retries of one run? Or is it planned?
the "job runs twice" framing is exactly the failure mode that's easy to ignore until it hits a billing job specifically. Curious how the Postgres lock holds up under a worker crash mid-job - does the lock get released automatically so the next scheduled run can pick it up, or does a dead worker leave a job stuck until someone notices via the heartbeat alert?
@michael_sunmisola that split makes sense, the lock question was really "does it double run" and the honest answer is that's the smaller risk, the ambiguous-completion case is the scary one because nothing looks broken. is the run ID something CronHQ generates and passes to the endpoint so the endpoint itself can dedupe on it, or is it purely internal bookkeeping on your side right now? if it's the former that would let people build idempotency into the receiving job rather than trusting CronHQ's own retry logic alone.
@michael_sunmisola that's the right fix, and exposing it in the headers rather than keeping it internal is what actually lets people opt into real idempotency instead of just trusting the retry logic. one thing worth thinking through before it ships: will the header be additive (new header, old ones untouched) or will you be changing what X-Cronhq-Timestamp means, since anyone who already built a workaround off the existing headers would need a heads up either way. appreciate you thinking out loud about this in public, it's a good sign for how the product gets built.
@michael_sunmisola additive plus a separate attempt number is the right shape, that gives people the stable key for dedup and the attempt count for observability without overloading one field with two meanings. good luck with the ship - this has been one of the more useful threads I've followed on here, mostly because you kept answering the harder follow-up instead of stopping at the first good-enough answer.









Thank you for testing CronHQ this thoroughly, this is genuinely valuable feedback.
I’m glad the five-minute schedule, retries and heartbeat setup worked smoothly. The missing recovery alert isn’t expected, so I’m investigating that. If you’re comfortable sharing the approximate test time or job ID through hello@cronhq.xyz, it would help me trace the exact transition.
You’re also right that each retry attempt should be clearer in the execution history, with its own duration, HTTP status and metadata. I’m reviewing that alongside the documentation and the current visibility of the repository and CLI.
Really appreciate the detailed and balanced review.