Broken Link Checker: Find 404s and Redirects on Any Website avatar

Broken Link Checker: Find 404s and Redirects on Any Website

Pricing

from $0.60 / 1,000 link checkeds

Go to Apify Store
Broken Link Checker: Find 404s and Redirects on Any Website

Broken Link Checker: Find 404s and Redirects on Any Website

Crawl a website and check every link, image, script and stylesheet: 404s, server errors, dead domains and redirect chains. One row per problem link with the page it was found on. Links that cannot be checked are free.

Pricing

from $0.60 / 1,000 link checkeds

Rating

0.0

(0)

Developer

Baptiste Bouton

Baptiste Bouton

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Security

Limited permissions

Share

Give it the address of a website. It reads the pages of that site, collects every link, image, script and stylesheet they contain, and checks each address once. You get one row per problem link with the page it was found on: 404 and other error pages, dead domains, redirect chains, and links the target site refused to check. It works as a dead link checker and a redirect checker, needs no API key and charges per link checked. Links that could not be checked (timeout, refusal) are free.

What it checks

  • Links on your site: <a href> hyperlinks, <img src> images, <script src> scripts and <link href> stylesheets, icons, canonical and alternate URLs.
  • Links to other sites: checked with one request per address, never crawled. Turn checkExternalLinks off to check your own site only.
  • Redirects: followed one by one (up to 10) and counted, so you can replace old addresses with the final one.
  • mailto:, tel: and javascript: links are ignored and never written. Link texts are not collected.

Use cases

  • Find the 404s on a site before a launch, a migration or an SEO audit.
  • Clean up old http:// and moved addresses that cost a redirect on every click.
  • Check a documentation or blog site every week with the Apify scheduler and get the new broken links only.
  • Check just a list of pages (a landing page set, a newsletter archive): set maxDepth to 0.

Input

FieldDescriptionDefault
startUrlsPages where the crawl starts. Pages of the same domain (with or without www) are followed
maxPagesPages of the site opened and read for links (see Run time above 700 pages)100 (up to 5000)
maxDepthClicks away from a start URL. 0 checks the links of the start URLs only10
checkExternalLinksAlso check links to other domainstrue
includeOkLinksAlso list working linksfalse
maxConcurrencyRequests sent at once to the crawled site4 (up to 10)

Example:

{ "startUrls": [{ "url": "https://crawlee.dev" }], "maxPages": 200 }

Output

One row per problem link and per page where it appears. Real rows from a run on https://crawlee.dev on 26 October 2026 (10 pages, 1,036 links checked, 4 broken, 205 redirected):

[
{
"linkUrl": "https://github.com//github.com/apify/crawlee/pull/1890/issues/issuecomment-1533271694",
"sourcePageUrl": "https://crawlee.dev/js/api/core/changelog",
"element": "a",
"result": "broken",
"statusCode": 404,
"redirectTo": null,
"redirectCount": 0,
"isExternal": true,
"error": "HTTP 404",
"checkedAt": "2026-10-26T08:06:43.140Z"
},
{
"linkUrl": "https://www.crawlee.dev/blog/launching-crawlee-python",
"sourcePageUrl": "https://crawlee.dev/blog",
"element": "a",
"result": "redirect",
"statusCode": 200,
"redirectTo": "https://crawlee.dev/blog/launching-crawlee-python",
"redirectCount": 1,
"isExternal": false,
"error": null,
"checkedAt": "2026-10-26T08:06:03.517Z"
},
{
"linkUrl": "https://stackoverflow.com/questions/tagged/crawlee",
"sourcePageUrl": "https://crawlee.dev/",
"element": "a",
"result": "blocked",
"statusCode": 403,
"redirectTo": null,
"redirectCount": 0,
"isExternal": true,
"error": "stackoverflow.com refused the request (HTTP 403)",
"checkedAt": "2026-10-26T08:05:49.287Z"
}
]

result is one of:

ResultMeaningCharged
ok2xx answer (listed only with includeOkLinks)yes
redirectworks after one or more redirects: redirectTo is the final addressyes
broken4xx or 5xx answer, unknown domain, redirect loopyes
blockedthe site refused the check (401, 403, 429, 503, 999 or a bot challenge): the link may work in a browserno
errorno usable answer: timeout, connection or certificate failureno

A link found on 20 pages gives 20 rows but is checked and charged once per run. Addresses that may name a person are shortened in every field: links to X, Twitter, Facebook, Instagram and GitHub keep only the domain, the account part of LinkedIn, Medium, YouTube and TikTok profile links becomes (hidden), and so does any part that looks like an e-mail address. Use sourcePageUrl to find a shortened link on your page.

Check a list of URLs

Already have the addresses, for example the old URLs of a site you moved, a backlink list or the links of a newsletter? Put them in urls (up to 10,000) and nothing is crawled: each address is checked once, with the same limits as the crawl (4 requests at once, 2 per host, a host is left alone after 5 refusals in a row). It works as a URL status checker and an HTTP status checker. startUrls is ignored when urls is filled.

FieldDescriptionDefault
urlsAddresses to check, one row each. Duplicates are checked once
maxRedirectsRedirects followed for each address before it counts as a redirect loop10 (1 to 20)

Each address is asked with HEAD, and with GET only when the server does not support HEAD (400, 405 or 501). Addresses that point to a private or local network are not checked. Each ok, redirect or broken address counts as one checked link; blocked and error addresses are free.

{ "urls": ["https://crawlee.dev/docs", "https://crawlee.dev/nope-404", "http://apify.com"] }

One row per address. Real rows from a run on 4 November 2026:

[
{
"inputIndex": 0,
"url": "https://crawlee.dev/docs",
"statusCode": 301,
"finalUrl": "https://apify.github.io/crawlee/js/docs/",
"finalStatusCode": 200,
"redirectChain": [
{ "url": "https://crawlee.dev/docs", "statusCode": 301 },
{ "url": "https://crawlee.dev/js/docs", "statusCode": 301 }
],
"redirectCount": 2,
"result": "redirect",
"contentType": "text/html; charset=utf-8",
"responseTimeMs": 221,
"error": null,
"checkedAt": "2026-11-04T07:54:54.916Z"
},
{
"inputIndex": 2,
"url": "https://crawlee.dev/nope-404",
"statusCode": 404,
"finalUrl": "https://crawlee.dev/nope-404",
"finalStatusCode": 404,
"redirectChain": [],
"redirectCount": 0,
"result": "broken",
"contentType": "text/html; charset=utf-8",
"responseTimeMs": 78,
"error": "HTTP 404",
"checkedAt": "2026-11-04T07:54:54.800Z"
}
]

statusCode is the first answer and finalStatusCode the last one. Profile links and e-mail addresses are masked as in the crawl: any path on these social networks is hidden in full, so use inputIndex (the position of the address in your list, from 0, duplicates removed) to find the matching entry.

Pricing

One charge per unique link or URL checked in a run (current price on the Pricing tab), whatever the result shown, with volume discounts for higher Apify plans. blocked and error links are free, and so are links skipped because of robots.txt. For scale, 10 pages of crawlee.dev hold 1,036 unique links. Set a maximum cost per run in the Apify Console and the Actor stops cleanly when it is reached, without charging links it did not check.

Run time

Most of the time goes into links to other sites, which get 2 requests at once each. On crawlee.dev, whose pages link to hundreds of GitHub addresses, 100 pages took 15 minutes (about 9 seconds per page). The default run timeout of 2 hours covers roughly 700 pages of that kind; raise the timeout in the run options for more. One minute before the timeout the Actor stops and ends normally, with every row found so far.

Polite crawling

  • The crawled site gets at most 4 requests at once by default (maxConcurrency, up to 10), and the Crawl-delay of its robots.txt is honored up to 10 seconds between requests. Pages disallowed by robots.txt are not opened, and a robots.txt answering with a server error (5xx) stops the crawl of that site.
  • Other sites get at most 2 requests at once, one per address.
  • An answer 429 or 503 pauses the site (the Retry-After header is honored up to 30 seconds) and the link is tried again, 3 times at most; then it is listed as blocked. After 5 refusals in a row on a site (401, 403, 429, 503, 999 or a bot challenge) the Actor stops sending requests to that site and says so in the log; its remaining links are listed as blocked and not charged.
  • Requests carry a User-Agent that names the Actor. Bot protections and captchas are never bypassed.
  • HEAD requests are used where possible; a server that refuses HEAD gets a GET.

Limits

  • Pages are read as served, without running JavaScript: links added by scripts after loading are not seen.
  • srcset image candidates, CSS url() and links inside iframes are not checked.
  • Pages larger than 5 MB are checked but not read for links.
  • Files such as PDF, ZIP or images are checked with HEAD and do not count in maxPages.

Recipes

  • 404 checker before a migration: run it on the old site, export the broken rows and map each one to a new page.
  • Redirect checker: keep result equal to redirect and replace linkUrl by redirectTo in your pages. Shortened social links are not complete: open sourcePageUrl to see them.
  • Weekly dead link check: schedule the run and send new broken rows to Slack or e-mail with Make, Zapier or n8n.
  • AI assistants: with the Apify MCP server an assistant can run a link check and list the pages to fix.

FAQ

Do I need an API key or a login? No. Only public pages are read.

Does it collect personal data? No. Link texts are not collected, and profile addresses and e-mail addresses in links are shortened to (hidden).

Will it slow my site down? It sends 4 requests at once by default, follows your robots.txt and its Crawl-delay, and slows down or stops when your server answers 429 or 503. Lower maxConcurrency for a fragile server.

Why are some links blocked? The target site refused an automated check. They are free: test them in a browser.