Skip to content

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 doesUse it for
Cron serviceRuns 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.
WorkflowRuns 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 checkHostStack 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

  1. 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.

  2. 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.

  3. 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 checking

Uptime check settings

path
string

A 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: GET

expectedStatus
100–599

The exact status code that counts as up. CLI flag --status; dashboard label Expected status.

Default: 200

timeoutMs
1000–60000

How long to wait for an answer before the check counts as failed. This is what catches a server that hangs. CLI flag --timeout.

Default: 10000

intervalSeconds
30–3600

How often to check. CLI flag --every; dashboard label Check every (seconds).

Default: 60

failureThreshold
1–10

Consecutive 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: 3

enabled
boolean

Off 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 301 is the right answer, set that as the expected status.
  • Services are requested over https when the domain has TLS enabled, otherwise http. Sites are always requested over https.
  • Requests carry the user agent HostStack-Uptime/1.0 (+https://hoststack.dev), if you need to allow or filter them.
StatusMeaning
unknownNot checked yet, or the check was just changed.
upAnswering as expected.
downFailed as many times in a row as the threshold — an alert is open.
unresolvableNothing to request: the service has no active custom domain, or the site’s domain is not verified.
pausedThe 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.

  1. Register the site

    Add it under Site analytics.

  2. Prove you own the domain

    Publish the TXT record 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.

  3. Turn on the check against the site

    Use the site’s id in place of a service id.

bash
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

  1. Create the service

    In the project, click New Service, pick the repository, and choose Cron Job as the type.

  2. Set the schedule and command

    Fill in Schedule and Command — for example node cleanup.js. Both can be changed later under Settings → General.

  3. 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);

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.

Resources

512 MB of memory, 1 CPU and 256 processes per run, whatever the service’s plan.

Output

The last 10,000 lines of stdout and stderr are kept with the execution.

Retries

None. 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:

bash
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:

bash
*/15 * * * * root /usr/local/bin/snapshot-database.sh
0 4 * * * root /usr/local/bin/rotate-logs.sh
*/5 * * * * monitoring /usr/local/bin/check-prod.sh

becomes 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.

EventWhenDefault
service.uptime_downA check failed as many times in a row as its threshold.On
service.uptime_recoveredA check that was down answers again.Off
cron.execution_failedA run exited non-zero, timed out, or never finished.On
cron.schedule_missedA tick produced no run at all.On
workflow.failedA workflow run failed after its retries.On
analytics.search_cliffA page on a site linked to Google Search Console lost 80%+ of its daily search impressions over two weeks.On
analytics.search_cliff_recoveredThat page is being shown in search again.Off
analytics.search_indexingA sitemap page Google had indexed no longer is, or Google cannot read a sitemap.Off
analytics.search_healthBing 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?

Essential cookies only — for login sessions. No tracking. Details