Free tool

Dead link checker

Give it a domain. It opens the first ten pages in your sitemap, follows every link on them — internal and external, however many there are — and reports back only the ones that failed to answer.

Reads /sitemap.xml, takes the first 10 pages, and follows every link found on them.

Why a link can look dead when it isn't

This checker requests links from a server, not from a browser, and a fair number of large sites refuse requests that don't look like a person. GitHub answers 406, LinkedIn has its own 999, others use 403. None of those mean the link is broken — they mean the site declined to talk to a checker. So the results are split: statuses that genuinely indicate a missing page, and statuses that indicate a refusal. Only the first group is worth acting on without looking.

The crawl itself is one endpoint: read the sitemap, open ten pages, collect every a element's target, resolve the relative ones, drop the duplicates, and request what's left. Built with the Hyperlambda Generator and callable by your own agents over MCP. Run the platform yourself:

curl -fsSL https://hyperlambda.dev/docker-compose.yaml | docker compose -f - up

The whole backend, printed

This is the longest backend of the nine, and still one file. It reads your sitemap, walks the first ten pages, collects every link, throws away the duplicates, then requests each one and keeps the ones that did not answer 200. This is it in full:

.arguments
   url:string
validators.mandatory:x:@.arguments/*/url
strings.concat
   get-value:x:@.arguments/*/url
   .:/sitemap.xml
http.get:x:-
xml2lambda:x:@http.get/*/content
.candidates
for-each:x:@xml2lambda/*/urlset/*/url/*/loc/*/#text/[0,10]
   http.get:x:@.dp/#
   html2lambda:x:@http.get/*/content
   for-each:x:@html2lambda/*/html/*/body/**/a
      if
         and
            exists:x:"@.dp/#/*/\\@href"
            not
               strings.starts-with:x:"@.dp/#/*/\\@href"
                  .:#
            not
               strings.starts-with:x:"@.dp/#/*/\\@href"
                  .:"mailto:"
            not
               strings.starts-with:x:"@.dp/#/*/\\@href"
                  .:"tel:"
            not
               strings.starts-with:x:"@.dp/#/*/\\@href"
                  .:"javascript:"
         .lambda
            .link
            set-value:x:@.link
               get-value:x:"@.dp/#/*/\\@href"
            if
               strings.starts-with:x:@.link
                  .:/
               .lambda
                  set-value:x:@.link
                     strings.concat
                        get-value:x:@.arguments/*/url
                        get-value:x:@.link
            unwrap:x:+/*/*
            add:x:@.candidates
               .
                  .:x:@.link
.unique-candidates
for-each:x:@.candidates/*
   if
      not-exists:x:"@.unique-candidates/*/\"={@.dp/#}\""
      .lambda
         add:x:@.unique-candidates
            get-nodes:x:@.dp/#
.result
for-each:x:@.unique-candidates/*
   try
      http.get:x:@.dp/#
      if
         not
            eq:x:@http.get
               .:int:200
         .lambda
            unwrap:x:+/*/*/*
            add:x:@.result
               .
                  .
                     link:x:@.dp/#
                     status:x:@http.get
   .catch
      unwrap:x:+/*/*/*
      add:x:@.result
         .
            .
               link:x:@.dp/#
               status:int:0
return-nodes:x:@.result/*

The try near the bottom is why a host that refuses the connection outright comes back as status 0 rather than taking the whole run down with it. Describe an endpoint in a sentence, and the Hyperlambda Generator writes and deploys it — then it is a tool your own agents can call over MCP.

More free tools

Hunting broken links is one of nine free tools here, each with the handful of backend lines that powers it printed on the page. See all of them — no signup on any of it.

Questions

Why only ten pages?

Because every link on those pages gets requested, and the link count grows much faster than the page count. Ten pages of a normal site is already a few hundred links once duplicates are removed — the navigation alone repeats on every page, so it is collected once and checked once.

What does a status of 0 mean?

No answer at all — the request threw rather than returning a status. A dead domain, a DNS failure, a certificate error or a connection that timed out all look like this, and all of them are worth investigating.

Does it check links on pages beyond the sitemap?

No. Pages come from /sitemap.xml only, and only the first ten of them. The links found on those pages are followed wherever they point, including off your own domain, but their destinations are not themselves crawled for further links.

Nothing came back. Is that good?

Yes — it means every link on those ten pages answered successfully. The tool reports failures only, so an empty result is a clean bill of health for the pages it looked at.