Someone asked on r/Domains in March 2025 how to identify all the domains currently redirecting to their main website. The thread has no answers. A company monitoring hundreds of domains asked something similar on r/selfhosted in 2024, and the best reply admitted the gap rather than closing it: for monitoring WHOIS changes, it said, “the situation is even worse”. The closest thing to a canonical answer is a Server Fault question from 2013 whose accepted reply is to “double check your current DNS and your future DNS, just to be sure”.

Meanwhile people keep rebuilding the tool. Uptime Kuma has had requests for domain expiry checks open since November 2021, and GitHub carries a steady supply of one-off clones.

The question deserves a better answer than a spreadsheet. Here is a script that gives one, and a second column that decides what you actually do with the output.

Why the dashboard cannot tell you

Cloudflare’s interface is organised by zone. Everything worth knowing here is a property of the set of zones, and there is no screen for that.

The raw logs that would answer it are Enterprise only: Logpush lists Free, Pro and Business as No. Per-rule statistics never arrived either — a feature request for Page Rule statistics was opened in May 2017, raised again by its author twice, and closed in June 2025 without being implemented.

So the cross-domain view is something you assemble yourself. The good news is that the API gives you the list for free.

Start from the account, not from your spreadsheet

The list of domains you think you own and the list Cloudflare is actually serving are different lists, and the difference is the first finding of any audit.

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones?per_page=50&page=1" \
| jq -r '.result[] | "\(.name)\t\(.status)"'

A zone’s status is one of initializing, pending, active or moved. Anything that is not active is a finding on its own: a pending zone is a domain whose nameservers never switched at the registrar, which means every rule you wrote for it has never once run. On a portfolio built in batches, that is the long tail the runbook warns about, showing up months later.

Mind the pagination. The endpoint pages, and a portfolio of three hundred will not arrive in one response — loop page until you get fewer rows than you asked for.

What to check, and why each check is a different failure

Three questions, three different ways a domain can be quietly broken.

Does it answer at all? A dead domain and a domain that answers wrongly look identical in a spreadsheet and nothing alike in a browser.

Does it go where you think? A redirect that works and points at last year’s landing page is worse than a broken one, because nothing will ever alert you.

Is the certificate current? Cloudflare’s Universal SSL certificates “have a 90-day validity period” and “the auto renewal period starts 30 days before expiration”. Read that carefully before you set a threshold: on a healthy proxied domain, fewer than 30 days remaining is normal, not an alarm. What matters is a certificate that is already expired, or one on a domain that left Cloudflare and has nobody renewing it.

And the failure that no HTTP check catches: the domain simply stopped existing. The owner of Pinboard watched it go NXDOMAIN in June 2026 with auto-renew switched on — “something went wrong, and I didn’t get an email from the registrar about it”. Auto-renew is the advice everyone gives and the thing that had already failed by the time anyone noticed.

The second column: what is worth fixing

Three hundred rows of findings is not an answer, it is a new problem. Nobody fixes three hundred domains. You fix the ones that carry weight, and until recently working out which those were meant a subscription.

That changed. Ahrefs’ Domain Rating endpoint is now open, and its documentation is explicit: “Requests to this endpoint are free and do not consume any API units.”

Checked today, it answers without a subscription:

$ curl -s "https://api.ahrefs.com/v3/public/domain-rating-free?target=cloudflare.com&output=json"
{"domain_rating": {"domain_rating": 94.0, "license": "...", "warning": "..."}}

Three conditions come with it, and all three belong in this article rather than in a footnote.

A free key is required from 10 August 2026. The endpoint currently answers unauthenticated and tells you so itself, in a warning field in its own response: “Unauthenticated access to this endpoint will be removed on 2026-08-10.” The script below sends a key already. Generating one is free, and doing it now costs less than debugging the script in three weeks.

Attribution, if you display the number. The licence grants the right to “use, display, publish or integrate DR Data” and requires the credit “Domain Rating by Ahrefs” with a working hyperlink that “shall not be hidden, obscured or removed”. A private terminal report is one thing; a dashboard your team reads is a place where that line has to appear.

Your own domains, at a civil pace. The same licence prohibits scraping or harvesting DR data “in bulk or systematically”. Walking the portfolio you own, once, with a pause between requests, is the use the endpoint exists for. Sweeping other people’s domains to build a DR dataset is the thing it forbids. Ten sequential requests spaced by 0.3 seconds went through without a complaint; the limits are not documented and Ahrefs reserves the right to change them without notice, so pace it and do not assume the ceiling is high.

One caveat on the metric itself: DR is Ahrefs’ number, not Google’s. It is a good relative scale for “which of these domains carries more weight than the others”, and it says nothing about rankings.

The script

Needs curl, openssl and jq. The date arithmetic is GNU; on macOS use date -j -f '%b %d %T %Y %Z'.

#!/usr/bin/env bash
set -uo pipefail
: "${AHREFS_KEY:?export AHREFS_KEYfree key from ahrefs.com}"

printf '%-34s %-5s %-32s %-6s %s\n' DOMAIN CODE DESTINATION CERT DR

while read -r domain; do
  [ -z "$domain" ] && continue

  head=$(curl -sI --max-time 10 "https://$domain/" 2>/dev/null)
  code=$(printf '%s' "$head" | awk 'NR==1{print $2}')
  dest=$(printf '%s' "$head" | awk 'tolower($1)=="location:"{print $2}' | tr -d '\r')

  until=$(echo | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null \
          | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
  days=""
  [ -n "$until" ] && days=$(( ( $(date -d "$until" +%s) - $(date +%s) ) / 86400 ))

  dr=$(curl -s --max-time 10 -H "Authorization: Bearer $AHREFS_KEY" \
       "https://api.ahrefs.com/v3/public/domain-rating-free?target=$domain&output=json" \
       | jq -r '.domain_rating.domain_rating // "-"')

  printf '%-34s %-5s %-32s %-6s %s\n' \
    "$domain" "${code:-DEAD}" "${dest:--}" "${days:+${days}d}" "$dr"
  sleep 0.3
done < domains.txt

A real run, against this site and two others, on 22 July 2026:

DOMAIN                             CODE  DESTINATION                      CERT   DR
www.301.sh                         301   https://301.sh/                  89d    8
301.sh                             200   -                                89d    8
spintax.net                        200   -                                85d    56
this-domain-does-not-exist-9f3.com DEAD  -                                       0

Four rows and four different states. The www form redirects to the apex, which is what it is supposed to do. Two domains answer directly. The last one does not resolve at all, and the script says DEAD rather than pretending to have an opinion about it.

Note the certificate column: 89 and 85 days. That is Universal SSL at the start of its 90-day life, which is what healthy looks like. A row reading 4d on a domain nobody is renewing is a different matter, and a row with no number at all means the handshake never happened.

Two shapes worth recognising that are not in that sample. A 522 means Cloudflare answered but your origin did not, so visitors are getting an error page where you think a redirect lives. And a 200 on a domain that is supposed to forward means the redirect is simply gone: a rule deleted, or one that never applied because the zone is still pending.

The rule that turns the table into decisions

no

yes

no

yes

no

yes

Answers?

DR above zero?

Goes where
you expect?

Let it lapse

Fix first:
links are pointing at nothing

Leave it alone

Deciding what each domain is for

Sort by broken first, then by DR descending, and the top of that list is your afternoon. A dead domain with real authority is the most expensive row in the table: every link pointing at it is value you already paid for and are currently throwing away. A dead domain with a DR of zero is a renewal invoice you can stop paying.

That second conclusion is worth as much as the first, and no health check alone can reach it.

What one pass does not give you

It gives you a snapshot, and portfolios do not break on the day you look at them.

A certificate expires in ninety days. A registrar’s auto-renew fails silently one Tuesday. A rule gets edited on a zone nobody has opened in a year, and the redirect starts landing on the wrong page while returning a perfectly healthy 301. The script above finds all of these — the next time somebody remembers to run it.

That is the difference between an audit and a system, and it is the honest place to say what 301.st is for: it runs this continuously across the portfolio and tells you when a row changes. For twenty domains, a cron entry and the script above are genuinely enough, and you should start there.

Domain Rating by Ahrefs.