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:
| Condition | Source behavior |
|---|---|
| Service type | Web service (web_service / empty type). Private services, workers, cron, and static sites have no public activator wake path. |
| Plan | Free tier (empty or free). Paid tiers are always-on and never auto-hibernate. |
| Not manually suspended | Suspend parks the App until you resume it; an incoming request does not wake a suspended service. |
| Activator routing | The 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
| Value | Meaning |
|---|---|
Unset / 0 | Platform default: 15 minutes. Stored as 0; the operator resolves the default and does not rewrite the field. |
| Positive seconds | Override window for that App. |
| Accepted range | 0 … 604800 (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
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
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: 5Cache-Control: no-store- JSON body
{"error":"service hibernated","retryAfter":5}when the client does not explicitly prefertext/html - An HTML interstitial when
Acceptincludes a nonzerotext/htmlmedia 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
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-clientThe client (README):
- Issues
GETwithAccept: application/jsonto your application URL - Recognizes the source-backed hibernated JSON (and HTML) wake response
- Waits using
Retry-After, capped inside an overall deadline - Succeeds only when your success predicate matches
- 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.
# 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.mjsWake 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 hibernation | Manual suspend | |
|---|---|---|
| Trigger | Idle window elapsed on an eligible free web service | Explicit suspend API/dashboard action |
| Wake | Next matching public request via activator | Explicit resume |
| Typical status | Hibernated with idle reason | Hibernated with suspended reason |
See App lifecycle for restart, suspend, and resume.