How do I monitor a website for regressions automatically?
Catching the silent breakages — an expired certificate, a stripped header, a robots.txt that started blocking Google — before anyone reports them.
Uptime monitoring tells you the site is up. It will not tell you that your certificate expires in nine days, that a CDN change dropped your security headers, or that a deploy pushed a staging robots.txt blocking every crawler. Those need a periodic full audit compared against the previous one — which is what a changes endpoint does.
The failures uptime monitoring never sees
A site returning 200 can be badly broken in ways that cost you for months before anyone notices.
- A staging
robots.txtdeployed to production.Disallow: /everywhere. The site is up, fast, and being progressively removed from every search index. Typically noticed weeks later via a traffic chart. - A certificate approaching expiry. Fine until the exact moment it is catastrophic, and then every visitor gets an interstitial.
- Security headers dropped by an edge change. A CDN configuration update, a new WAF rule, a rewritten origin config. Your CSP is in your repository and not in the response.
- A canonical tag pointing at staging. Every page telling search engines the real version lives on a host nobody can reach.
- A
noindexleft in from a launch. The single most expensive one-line mistake in web publishing. - DMARC or SPF broken by a DNS edit. Silent until deliverability drops, and hard to attribute afterwards.
None of these change the status code. All of them are visible in a full audit.
The shape of the check
Run a complete audit on a schedule, compare it with the previous run, and alert on the difference rather than on the absolute state. Alerting on state means alerting constantly about things you have decided not to fix; alerting on change means you hear about exactly what moved.
curl -s "https://outrings.com/api/v1/changes?url=example.com" | jq '{
comparable,
scoreDelta,
direction,
newProblems: [.newProblems[]? | "\(.id) (\(.status))"],
resolved: [.resolved[]? | "\(.id) (was \(.was))"]
}'The endpoint re-audits, compares against the last stored snapshot, and reports what appeared, what cleared and how each category moved. On the first run it returns comparable: false — that is history starting, not an error.
A daily job
#!/usr/bin/env bash
set -euo pipefail
site="example.com"
result=$(curl -sf "https://outrings.com/api/v1/changes?url=$site")
comparable=$(jq -r '.comparable' <<<"$result")
[ "$comparable" = "true" ] || { echo "first snapshot stored for $site"; exit 0; }
new=$(jq -r '[.newProblems[]?.id] | join("; ")' <<<"$result")
delta=$(jq -r '.scoreDelta // 0' <<<"$result")
if [ -n "$new" ]; then
echo "REGRESSION on $site (score $delta): $new" >&2
exit 1
fi
echo "$site unchanged (score $delta)"Run it from cron, a scheduled CI job, or any scheduler you already have. Exit code 1 on regression is what makes it composable with everything else.
What to alert on, and what to ignore
| Signal | Alert? | Why |
|---|---|---|
| A new critical or high finding | Yes | Something broke since the last run |
| Score dropped more than a few points | Yes | Usually several small regressions at once |
| Certificate expiring within 21 days | Yes | The one deadline that is absolute |
| Indexability changed at all | Yes | noindex or a blocking robots rule is high-cost and easy to miss |
| Score moved by under a point | No | Content churn, not a regression |
| A new low-severity finding | No | Batch these into a weekly review |
Attributing a change correctly
When a score moves, there are three possible causes and only one is worth waking up for: the site changed, the ruleset changed, or a check that previously succeeded is now undetermined because the auditor was blocked. Every response carries versions.ruleset — if that differs between two runs, compare like for like before concluding anything. And a drop accompanied by new undetermined entries usually means bot protection started intercepting the audit rather than that anything regressed.
What our audit reports about this
Every item below is measured directly, not inferred. Run it against your own site and the result names the exact rule or header responsible.
- A changes endpoint that re-audits and reports new problems, resolved findings and per-category deltas against the previous snapshot.
- Stored snapshot history per site, readable without re-fetching, so it still works while the site is down.
- Certificate expiry as a dated field rather than a pass/fail, so you can alert on a horizon rather than on failure.
- Ruleset and schema version stamps, so a score change can be attributed to the site rather than to the rules.
For agents and scripts, the same measurement is at
/api/v1/summary?url=yoursite.com —
see the API documentation.
Related questions
How is this different from uptime monitoring?
Uptime answers whether the server responded. This answers whether it responded correctly — headers, certificate, crawler rules, indexability, email authentication. A site can be perfectly available and quietly deindexing itself.
How often should I run it?
Daily suits most sites, or on every production deploy if you ship frequently. More often than that mostly adds load on your own origin without surfacing anything new.
What does comparable: false mean?
There is no earlier snapshot to compare against, so this run became the baseline. It is the expected result on a first run and not an error.
Will it catch a change my CDN made?
Yes, and that is one of the main reasons to run it. The audit reads the response as served to the public, so a header your origin sets and your edge strips shows up as missing — which is the truth that matters.
Read next
How do I add a website check to my CI pipeline?
Blocking a deploy when it would break your headers, certificate, indexability or accessibility — with copy-paste config for GitHub Actions and GitLab.
ReadHow do I track website changes over time?
Building a history of what your site actually served, so you can answer "when did this break" instead of guessing.
ReadWhy did my website score change when I did not change anything?
The four causes of an unexplained score movement, and how to tell which one you are looking at in under a minute.
ReadHow do I audit a website from the command line?
Getting a complete, machine-readable audit with one curl command — and pulling out just the parts you need with jq.
Read