Broken Link Checker: Find 404s and Redirects on Any Website
Pricing
from $0.60 / 1,000 link checkeds
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Security
Limited permissionsShare
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
checkExternalLinksoff 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:andjavascript: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
maxDepthto 0.
Input
| Field | Description | Default |
|---|---|---|
startUrls | Pages where the crawl starts. Pages of the same domain (with or without www) are followed | |
maxPages | Pages of the site opened and read for links (see Run time above 700 pages) | 100 (up to 5000) |
maxDepth | Clicks away from a start URL. 0 checks the links of the start URLs only | 10 |
checkExternalLinks | Also check links to other domains | true |
includeOkLinks | Also list working links | false |
maxConcurrency | Requests sent at once to the crawled site | 4 (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:
| Result | Meaning | Charged |
|---|---|---|
ok | 2xx answer (listed only with includeOkLinks) | yes |
redirect | works after one or more redirects: redirectTo is the final address | yes |
broken | 4xx or 5xx answer, unknown domain, redirect loop | yes |
blocked | the site refused the check (401, 403, 429, 503, 999 or a bot challenge): the link may work in a browser | no |
error | no usable answer: timeout, connection or certificate failure | no |
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.
| Field | Description | Default |
|---|---|---|
urls | Addresses to check, one row each. Duplicates are checked once | |
maxRedirects | Redirects followed for each address before it counts as a redirect loop | 10 (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 theCrawl-delayof 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-Afterheader is honored up to 30 seconds) and the link is tried again, 3 times at most; then it is listed asblocked. 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 asblockedand 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.
srcsetimage candidates, CSSurl()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
brokenrows and map each one to a new page. - Redirect checker: keep
resultequal toredirectand replacelinkUrlbyredirectToin your pages. Shortened social links are not complete: opensourcePageUrlto see them. - Weekly dead link check: schedule the run and send new
brokenrows 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.
Related Actors
- PageSpeed Insights Bulk Audit: Lighthouse scores and Core Web Vitals for every page of a sitemap.
- Tech Stack Detector: the technologies behind a list of websites.