---
id: platform/idle-services
title: Recover when an idle web service wakes
description: Configure free-tier idle sleep, recognize the activator wake response, and wait for a real application readiness signal.
keywords: [bex, idle, hibernate, sleep, wake, free tier, Retry-After]
last_updated: 2026-09-23
---

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](./app-lifecycle.md) 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

```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](/examples/idle-service-client/README.md)):

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 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](./app-lifecycle.md) for restart, suspend, and resume.

## Related

- [Web services](./web-services.md)
- [App lifecycle](./app-lifecycle.md)
- [Scaling](./scaling.md)
- [Health checks](./health-checks.md)
- [Maintenance mode](./maintenance-mode.md)
