Skip to main content

Recover when an idle web service wakes

Configure free-tier idle sleep, recognize the activator wake response, and wait for a real application readiness signal.

Share
Last updated on September 23, 2026
On this page

Free-tier web services can auto-hibernate after an idle window. The next public request wakes the workload through the platform activator. That first request is not forwarded to your application. Integrations that treat any 200 on /healthz as "ready" will misread the activator's own health probe.

Source pin: Bex 0b2421de955618e334936ede506b502f1413cad1 (activator writeWakeResponse, operator autoSleepWindow / defaultIdleTTL, backend SetIdleTTL / MaxIdleTTLSeconds). An installation may run a different revision; treat observed headers and timing as installation-specific when they differ from this pin.

Who sleeps

Automatic idle hibernation applies only when all of the following hold:

ConditionSource behavior
Service typeWeb service (web_service / empty type). Private services, workers, cron, and static sites have no public activator wake path.
PlanFree tier (empty or free). Paid tiers are always-on and never auto-hibernate.
Not manually suspendedSuspend parks the App until you resume it; an incoming request does not wake a suspended service.
Activator routingThe operator points the public Ingress at the activator while the App is hibernating.

Activity that resets the idle window (while awake) is traffic the platform observes on the App — HTTP requests the service served, or WebSocket frames it sent — plus the activator stamping app.bex.co/last-active on the wake request. Self-hosted installs also need a running operator and activator that implement this path; without them, idle sleep and wake behavior will not match this guide.

Idle window

ValueMeaning
Unset / 0Platform default: 15 minutes. Stored as 0; the operator resolves the default and does not rewrite the field.
Positive secondsOverride window for that App.
Accepted range0604800 (7 days). Outside that range the API returns a bad request.

Free Apps cannot disable auto-sleep by setting 0. Paid plans ignore the stored value for hibernation.

There is no Blueprint / render.yaml idle field in this source revision. Configure idle through the dashboard Settings control, REST, or GraphQL below.

Dashboard

On a free web service, Settings exposes the idle timeout control that writes the same idleTTLSeconds field as the API (setIdleTimeout / PATCH).

REST

bash
curl --fail-with-body -X PATCH "$BEX_API_URL/v1/services/$SERVICE_ID" \
  -H "Authorization: Bearer $BEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"serviceDetails":{"idleTTLSeconds":900}}'

Use 0 to restore the platform default window (still free-tier sleep, not "never sleep").

GraphQL

graphql
mutation {
  setIdleTimeout(id: "SERVICE_ID", idleTTLSeconds: 900) {
    id
    idleTTLSeconds
  }
}

Wake response (not your app)

When the activator matches a hibernating App, it patches last-active, scales the Deployment toward one replica, and returns 503 with:

  • Retry-After: 5
  • Cache-Control: no-store
  • JSON body {"error":"service hibernated","retryAfter":5} when the client does not explicitly prefer text/html
  • An HTML interstitial when Accept includes a nonzero text/html media range

That response is produced by the activator. It is not an application handler result. Do not treat it as proof the process is ready.

Why /healthz on the public host is not enough

The activator answers GET /healthz with 200 before looking up the App. A probe that only checks that path can report healthy while every application route still receives the wake 503. Use an application-owned readiness URL whose success body or header you can identify — for example GET /ready returning {"ok":true,"service":"my-app"}.

The downloadable client refuses a bare /healthz 200 as success for this reason.

Bounded client

bash
curl -fsSLO https://bex.co/examples/idle-service-client-latest.zip
curl -fsSLO https://bex.co/examples/idle-service-client-latest.zip.sha256
shasum -a 256 -c idle-service-client-latest.zip.sha256
unzip idle-service-client-latest.zip
cd idle-service-client

The client (README):

  1. Issues GET with Accept: application/json to your application URL
  2. Recognizes the source-backed hibernated JSON (and HTML) wake response
  3. Waits using Retry-After, capped inside an overall deadline
  4. Succeeds only when your success predicate matches
  5. Exits nonzero on auth refusal, malformed/unexpected responses, persistent unavailability, timeout, or cancellation — without printing credentials

Local protocol fixtures under that tree reproduce wake JSON/HTML, eventual readiness, the activator /healthz false positive, and persistent failure. They are not a live Bex service.

bash
# Offline protocol check
BEX_IDLE_FIXTURE=1 node wake.mjs
 
# Against your service (set an identifiable application response)
BEX_APP_URL='https://YOUR_SERVICE/ready' \
  BEX_EXPECT_JSON='{"ok":true}' \
  node wake.mjs

Wake latency is installation- and capacity-dependent. The client budget is a client-side deadline, not a platform SLA.

Idle sleep vs manual suspend

Auto idle hibernationManual suspend
TriggerIdle window elapsed on an eligible free web serviceExplicit suspend API/dashboard action
WakeNext matching public request via activatorExplicit resume
Typical statusHibernated with idle reasonHibernated with suspended reason

See App lifecycle for restart, suspend, and resume.

Was this page helpful?

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex