Cron & uptime
Run a command on a schedule with a cron service, and find out when a service or site stops answering with an uptime check. Both replace things you'd otherwise keep on a VPS: a crontab, and a script that curls your site.
Which one to use
| What it does | Use it for | |
|---|---|---|
| Cron service | Runs one command from your repo’s built image on a cron schedule, to completion. No automatic retry. | Cleanups, digests, syncs — work that belongs to the app. |
| Workflow | Runs any Docker image on a schedule or on demand, with up to 10 retries and a run history. | Jobs that need retries or a longer timeout, or that don’t belong to one service. See Workflows. |
| Uptime check | HostStack requests your public URL on an interval and alerts when it stops answering. | Knowing your site is down before your users tell you. Nothing to write or run. |
Uptime checks
An uptime check requests a service’s public URL on an interval you choose. After a number of consecutive failures, also yours to choose, it raises an alert. The alert goes to the dashboard, to your alert channels (Slack, Discord or email) and to team owners by email. It closes itself when the service answers again.
An uptime check is not the same as the health check, and the difference matters. The health check runs on the server, against the container, while a deploy goes out. The uptime check runs from HostStack’s control plane, against the URL your users open, for as long as it is on. So it also catches DNS, TLS and routing problems, and a server that accepts the connection and then never replies.
Only services with a public URL can be checked: web services and static sites. Workers, private services and cron jobs have nothing to request.
Turn on an uptime check
Give the service a custom domain
The check requests an active custom domain on the service — the one marked primary, or the oldest if none is. Until the service has one, the check reports
unresolvable. See Custom domains.Open the uptime settings
On the service, go to Settings → Health Check and scroll to Uptime monitoring. Or open Monitoring in the sidebar: it lists every service and site your team owns, including those nothing is watching, and lets you arm a check from there.
Set the path and save
The defaults —
GET /every 60 seconds, expecting a 200, alerting after 3 failures in a row — suit most apps. Point the path at a health endpoint if you have one.
hoststack uptime set <service-id> --path /healthz \
--status 200 --every 60 --timeout 10000 --after 3
hoststack uptime get <service-id> # config and the last result
hoststack uptime rm <service-id> # stop checkingimport { HostStack } from '@hoststack.dev/sdk';
const hs = new HostStack({ apiKey: process.env.HOSTSTACK_API_KEY! });
const { check } = await hs.uptime.upsert(teamId, 'svc_...', {
path: '/healthz',
expectedStatus: 200,
intervalSeconds: 60,
failureThreshold: 3,
});Turn on an uptime check for my "web" service that requests
/healthz every minute and alerts after 3 failures in a row.Uptime check settings
pathstringA path, not a URL — it must start with
/. The host is picked at check time, so moving the service to a new domain moves the check with it.Default:
/method"GET" | "HEAD"Only safe methods: the check repeats forever and must never write anything. Set through the API, SDK or MCP.
Default:
GETexpectedStatus 100–599The exact status code that counts as up. CLI flag
--status; dashboard label Expected status.Default:
200timeoutMs 1000–60000How long to wait for an answer before the check counts as failed. This is what catches a server that hangs. CLI flag
--timeout.Default:
10000intervalSeconds 30–3600How often to check. CLI flag
--every; dashboard label Check every (seconds).Default:
60failureThreshold 1–10Consecutive failures before an alert fires. One failed request is usually a restart, not an outage. CLI flag
--after; dashboard label Alert after N failures.Default:
3enabledbooleanOff keeps the check but stops requesting.
hoststack uptime disable <service-id>sets it.Default:
true
How a check decides
- Up means the response status equals the expected status. Any other status, a connection error, or no answer within the timeout is a failure.
- Redirects are not followed. A redirect is an answer, and following it could land on a different site that is up. If a
301is the right answer, set that as the expected status. - Services are requested over
httpswhen the domain has TLS enabled, otherwisehttp. Sites are always requested overhttps. - Requests carry the user agent
HostStack-Uptime/1.0 (+https://hoststack.dev), if you need to allow or filter them.
| Status | Meaning |
|---|---|
| unknown | Not checked yet, or the check was just changed. |
| up | Answering as expected. |
| down | Failed as many times in a row as the threshold — an alert is open. |
| unresolvable | Nothing to request: the service has no active custom domain, or the site’s domain is not verified. |
| paused | The service is suspended, deploying, not deployed yet, or sleeps when idle. |
Paused is deliberate. A suspended or deploying service is not an outage. A service that sleeps when idle is left alone because every check would wake it, and it would never sleep again.
Check a site HostStack doesn’t host
An uptime check can point at a site hosted somewhere else. Of the three monitoring tools, this is the one an outside site can’t provide for itself: analytics is a script tag and error reporting is an HTTP POST, but an uptime check has to come from somewhere else to mean anything.
Register the site
Add it under Site analytics.
Prove you own the domain
Publish the
TXTrecord HostStack shows you, then verify it. This is required because the check makes HostStack request that hostname on a schedule, from its own IP — it will only do that for a domain you have shown is yours.Turn on the check against the site
Use the site’s id in place of a service id.
hoststack analytics verify example.com # prints the TXT record
hoststack analytics verify example.com --check # after you publish it
hoststack uptime set --site <site-id> --path /Ownership is checked again before every request. If the proof is withdrawn, the check goes to unresolvable on its next run.
The Monitoring page
Monitoring in the dashboard sidebar answers “is anything broken?” in one view: uptime and outage history for every service and site, open alerts, unresolved errors, backups, certificates, and services running close to their CPU or memory limit. Things nothing is watching are listed too, so a missing check is visible rather than absent.
An uptime check tells you whether the site is answering. To see what threw, where, and how often, use Error tracking.
Cron services
A cron service is a service of type cron_job. It is built from your repository like any other service, but nothing stays running. On each tick of its schedule, HostStack starts a fresh container from the service’s latest live image, runs the command with /bin/sh -c, records the exit code and output, and removes the container. An image without a shell — Go’s distroless runtime image, or your own FROM scratch — runs the command directly instead, so there it must be a program and its arguments, such as ./server cleanup; shell syntax fails the run with a message that says why.
- The schedule is a standard cron expression, in UTC —
*/5 * * * *is every five minutes,0 3 * * *is daily at 03:00. It is validated when you save it. - The container gets the service’s runtime environment variables and joins the project’s private network, so it can reach sibling services and databases.
- A tick only runs while the service is active. Suspended or failed services skip their ticks; a tick that lands mid-deploy waits up to a minute for the deploy to finish.
- The service needs one successful deploy before the first run.
Create a cron service
Create the service
In the project, click New Service, pick the repository, and choose Cron Job as the type.
Set the schedule and command
Fill in Schedule and Command — for example
node cleanup.js. Both can be changed later under Settings → General.Watch it run
The Executions tab lists every run, scheduled or manual, with its status, exit code, duration and output. Run Now starts one straight away.
const { service } = await hs.services.create(teamId, {
projectId: 'prj_...',
name: 'nightly-cleanup',
type: 'cron_job',
githubRepo: 'acme/my-app',
cronSchedule: '0 3 * * *',
startCommand: 'node cleanup.js',
});
await hs.cron.trigger(teamId, service.publicId); // run now
const { executions } = await hs.cron.list(teamId, service.publicId);# Create the service in the dashboard, SDK or MCP; then:
hoststack cron trigger <service-id> # run now
hoststack cron list <service-id> [--limit 20] # recent executions
hoststack cron get <service-id> <execution-id>Create a cron job service called "nightly-cleanup" in my project
from acme/my-app that runs "node cleanup.js" every day at 03:00 UTC.Cron run limits
- Time limit
10 minutes. A run still going is stopped and recorded as failed. Use a workflow for anything longer — its timeout goes up to 24 hours.
Resources512 MB of memory, 1 CPU and 256 processes per run, whatever the service’s plan.
OutputThe last 10,000 lines of stdout and stderr are kept with the execution.
RetriesNone. A run that exits non-zero is recorded as failed and the next one runs on schedule.
Example: check a URL every five minutes
A cron service whose command curls your API and posts to Slack when it fails. Set the schedule to */5 * * * * and the command to:
curl -fsS https://api.example.com/health \
|| curl -fsS -X POST -H 'content-type: application/json' \
-d '{"text":"prod /health is down"}' \
"$SLACK_INCOMING_WEBHOOK"Add SLACK_INCOMING_WEBHOOK under the service’s Settings → Variables and tick Secret, so the value is masked in the dashboard and the API. The command runs in your app’s image, so curl must be installed in it.
Move a host crontab
A crontab on a VPS like this one:
*/15 * * * * root /usr/local/bin/snapshot-database.sh
0 4 * * * root /usr/local/bin/rotate-logs.sh
*/5 * * * * monitoring /usr/local/bin/check-prod.shbecomes one cron service per line. Commit each script to your app’s repository (or a small repository of its own), create a cron service that runs it, and copy the schedule across — converted to UTC if the server ran in another time zone. A job that stops running then raises an alert, instead of being found a week later.
Alerts
Uptime checks, cron services, workflows and Search Console links raise these events. Route them to Slack, Discord or email under Settings → Alert channels.
| Event | When | Default |
|---|---|---|
| service.uptime_down | A check failed as many times in a row as its threshold. | On |
| service.uptime_recovered | A check that was down answers again. | Off |
| cron.execution_failed | A run exited non-zero, timed out, or never finished. | On |
| cron.schedule_missed | A tick produced no run at all. | On |
| workflow.failed | A workflow run failed after its retries. | On |
| analytics.search_cliff | A page on a site linked to Google Search Console lost 80%+ of its daily search impressions over two weeks. | On |
| analytics.search_cliff_recovered | That page is being shown in search again. | Off |
| analytics.search_indexing | A sitemap page Google had indexed no longer is, or Google cannot read a sitemap. | Off |
| analytics.search_health | Bing lost half or more of a site’s indexed pages, a sitemap Bing could read fails, or a site’s Google or Bing link stopped syncing. | Off |
“On” events are pre-selected when you create a channel, and are emailed to team owners unless they opt out.
Failed run or missed run
cron.execution_failed means your command ran and failed: there is an exit code and output to read. It is sent when a service starts failing, not on every failed run — a job that keeps failing sends one alert, and alerts again only after six hours without a failure.
cron.schedule_missed means the tick never happened: no run was created, so there is no exit code, no output and nothing in your code to fix. That one is a HostStack fault. Every run is stamped with the schedule slot it was for, and a sweep compares those against the slots your schedule says should exist — the only way to notice a run that is missing rather than failing.
Push notifications
Alerts can also arrive as browser push notifications, so you hear about an outage with the dashboard closed. Turn them on per device under Settings → Alert channels → Push notifications. On iPhone and iPad, first add HostStack to the Home Screen (Share → Add to Home Screen) and open it from there — iOS only allows push for installed web apps. Push is best-effort and goes through your browser vendor, so keep email on for anything you can’t afford to miss.
Next steps
Was this page helpful?