Time to First Byte: the 600ms and 1000ms thresholds
A server that takes over 600ms to send the first byte, or over 1000ms. squirrel measures TTFB on every fresh fetch.
What TTFB measures
Time to First Byte is the time from the start of the request to the first byte of the response arriving. It covers DNS, the connection, and everything the server did before it started writing: routing, database queries, template rendering, and any upstream call in the way.
It is not a Core Web Vital itself, but every one of them starts after it. A page cannot paint before the HTML arrives, so a slow TTFB moves the floor under Largest Contentful Paint by the same amount. The usual causes are an uncached page rendered per request, a slow query, and a long distance between the visitor and the origin.
What squirrel checks
The rule runs on every crawled page and emits one or two checks:
ttfb, skipped with the messageTTFB not measuredand the reasonNo TTFB timing data (only available for fresh fetches)when the crawl recorded no TTFB for the page, which is the case for a response served from the audit’s own cache rather than fetched.ttfb, passing with the messageFast server response (Nms)under 600ms, warning withSlow server response (Nms)from 600 to 999ms, and failing withVery slow server response (Nms)at 1000ms or above. The expected value shown is< 600mson a pass or a warning and< 1000mson a failure.download-time, at info status with the messageContent download: Nms, emitted only when the crawl recorded a download time and a TTFB, since the skip above returns before this check.
The rule uses the measured TTFB alone and never falls back to total load time, which includes the download and would overstate the server’s share. Values are rounded to whole milliseconds.
How to fix it
Serve the page from cache when the content allows it, so the origin is not rendering per request:
Cache-Control: public, max-age=0, s-maxage=600, stale-while-revalidate=86400
That lets a CDN answer from its edge and revalidate in the background. When the page genuinely has to be rendered per request, the work is in the request path: the query that runs without an index, the upstream API called serially, the template compiled on every hit.
The common false positive is a cold start. The first request to a serverless origin after an idle period pays for boot, and a single crawl can catch exactly that request. Re-run the audit before acting on one slow page.
| Rule ID | perf/ttfb |
| Category | Performance |
| Scope | Per-page |
| Severity | warning |
| Weight | 7/10 |
Options
| Option | Type | Default | Description |
|---|---|---|---|
good_threshold | number | 600 | Good TTFB in ms |
poor_threshold | number | 1000 | Poor TTFB in ms |
Configuration example
[rules."perf/ttfb"]
good_threshold = 600
poor_threshold = 1000Enable / disable
Disable this rule
[rules]
disable = ["perf/ttfb"]Disable all Performance rules
[rules]
disable = ["perf/*"]Enable only this rule
[rules]
enable = ["perf/ttfb"]enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.
Related rules
- perf/cache-headers: the per-page caching directives that decide whether the origin is hit at all.
- perf/bad-caching: the same question asked across the whole crawl.
- perf/http2: the protocol the request was made over.
- perf/lcp-hints: the paint that cannot start until this number is spent.
Performance findings ship in every audit next to the SEO, security and agent experience rules. See Core Web Vitals for how an agent works through a report.
References
Check your site
Run squirrel audit https://example.com and open the Performance section of the report. Each page reports its measured server response time. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.