Broken Link Checker: Find 404s, Dead Links & Broken Images avatar

Broken Link Checker: Find 404s, Dead Links & Broken Images

Pricing

Pay per event

Go to Apify Store
Broken Link Checker: Find 404s, Dead Links & Broken Images

Broken Link Checker: Find 404s, Dead Links & Broken Images

Broken link checker and 404 checker: crawl a website and find broken links and images (404s, 410s, 5xx errors, DNS failures, timeouts) with the page each one is on, the link text and the status code. Internal and external links. Healthy links are free. Respects robots.txt.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Scott Williams

Scott Williams

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

18 hours ago

Last modified

Share

Broken Link Checker: find 404s and dead links on any website

Broken Link Checker crawls a website from a start page, checks every link and image it finds, and gives you a list of the broken ones: the page each one is on, the link text, the URL, and the HTTP status code (404, 410, 500 and so on) or the network error (DNS failure, refused connection, timeout). Use it as a dead link checker, a 404 checker, or a bulk link checker for internal and external links.

No browser, no proxy, no login. It obeys robots.txt and crawl delays.

At a glance: $0.003 per unique broken URL found ($3.00 per 1,000); a dead URL linked from many pages is charged once per run. Healthy links are free in the default mode. Pay per result with no subscription, set a maximum cost per run, export to JSON, CSV, Excel or HTML, or call it from the Apify API, n8n, Make or Zapier.

Who it's for

  • SEO teams and agencies cleaning up 404 errors and dead outbound links during site audits.
  • Site owners and developers checking a site before and after a migration, redesign or URL change.
  • Content teams finding dead links and missing images in old blog posts and documentation.
  • Anyone monitoring a site: schedule a weekly run and get a fresh list of broken links each time.
  • HTTP 4xx and 5xx responses, such as 404 Not Found, 410 Gone, 500 and 502.
  • Network failures: DNS errors, refused connections, timeouts, too many redirects.

Not counted as broken:

  • 401 and 403 (login walls and bot protection usually mean the page exists) and 429 (rate limited).
  • Links disallowed by the target site's robots.txt. They are not requested and are counted as "not checked".

Each link is first checked with a HEAD request. If the server answers HEAD with 403, 404, 405, 501 or any 5xx, the link is checked again with GET before it is reported, because many servers handle HEAD badly.

What it checks

  • Every <a href> link on each crawled page, internal and (by default) external.
  • Every <img src> image on each crawled page (turn off with Check images).
  • Only http and https URLs. mailto:, tel:, javascript:, data:, sms:, ftp: and # links are skipped.

The crawler follows internal links on the same host (www. and the bare domain count as the same host) until it reaches Max pages to crawl per site. Links found on those pages are all checked, including internal pages it did not crawl.

How to use it

  1. Open the Actor in Apify Console and click Try for free.
  2. Add one start URL per site in Website URLs, usually the home page.
  3. Set Max pages to crawl per site. The default is 10; raise it to cover the whole site.
  4. Optional: turn off Check external links or Check images, add Exclude URL patterns, or turn off Only broken links to get every link with its status.
  5. Click Start and export the dataset to CSV or Excel. Each row tells you which page to edit and which link to fix.

Input

FieldTypeDefaultDescription
startUrlsarray of URL strings (required)["https://the-internet.herokuapp.com/broken_images"]Start page of each site. A URL without http gets https:// added.
maxPagesPerSiteinteger, 1 or more10How many pages of each site to crawl for links.
checkExternalLinksbooleantrueAlso check links that point to other websites.
checkImagesbooleantrueAlso check image sources.
onlyBrokenbooleantrueSave only broken links. Turn off to save every checked link with its status.
excludePatternsarray of stringsnoneRegular expressions. Matching links are neither crawled nor checked.
maxResultsinteger0Stop after this many dataset rows. 0 means no limit.

Example input

{
"startUrls": ["https://the-internet.herokuapp.com/broken_images"],
"maxPagesPerSite": 500,
"checkExternalLinks": true,
"checkImages": true,
"onlyBroken": true,
"excludePatterns": ["/tag/", "\\?replytocom="]
}

Output

One row per broken link occurrence. If the same dead URL is linked from 3 pages, you get 3 rows, one per page to fix, and pay for 1. This is a real row from a test run on the default start page:

{
"site": "https://the-internet.herokuapp.com/broken_images",
"pageUrl": "https://the-internet.herokuapp.com/broken_images",
"linkUrl": "https://the-internet.herokuapp.com/asdf.jpg",
"linkText": null,
"type": "image",
"internal": true,
"statusCode": 404,
"isBroken": true,
"error": "HTTP 404",
"finalUrl": "https://the-internet.herokuapp.com/asdf.jpg",
"redirected": false,
"checkedAt": "2026-10-30T01:31:18.990180+00:00"
}
FieldDescription
siteThe start URL of the crawl
pageUrlThe page the link was found on
linkUrlThe link or image URL
linkTextAnchor text for links, alt text for images (up to 120 characters)
typelink or image
internaltrue when the link points to the same site
statusCodeHTTP status, or null for network errors and links not checked
isBrokentrue for broken links
errorFor example HTTP 404, a DNS or connection error, not checked: robots.txt disallows it, or not checked: private, loopback and link-local addresses are not allowed
finalUrl, redirectedWhere the link ended up after redirects, and whether it redirected
checkedAtWhen the link was checked (UTC)

The RUN_SUMMARY record in the key-value store has, per site, pages crawled, links checked, broken links, links not checked, and a note when the crawl stopped at Max pages to crawl per site.

Pricing

This Actor uses pay-per-event pricing. You pay for results, not for compute time.

EventPrice
Broken link (each unique broken URL, default mode)$0.003
Link (one row, when Only broken links is off; rows for links not checked because of robots.txt are free)$0.0005
Actor start (charged by Apify, once per GB of run memory; 1 event at the default 1 GB)$0.00005

Examples:

  • A site with 10 unique broken URLs, default mode: 10 × $0.003 + $0.00005 = about $0.03, however many healthy links it has.
  • A full link report with 2,000 link rows (Only broken links off): 2,000 × $0.0005 = about $1.00.
  • A site with no broken links, default mode: $0.00005 (only the start event).

In the default mode each broken URL is charged once per run: if the same dead URL is linked from many pages you get one row per page, but only the first is charged. With Only broken links off, every checked row is charged. Use Max results or Maximum cost per run to cap a run.

Set a maximum cost per run. In Apify Console set Maximum cost per run, or pass maxTotalChargeUsd through the API. The Actor stops cleanly when the limit is reached and the status message says so.

Integrations and API

The Actor ID is fecklesspleb/broken-link-checker (in API URLs: fecklesspleb~broken-link-checker).

  • n8n: use the Apify node, choose Run an Actor with this Actor ID, then read the dataset items and send new broken links to Slack, email or a ticket.
  • Make: use the Apify module Run an Actor, then Get Dataset Items.
  • Zapier: use the Apify app with Run Actor, then a dataset items step.
  • Schedules: create an Apify schedule to check your site weekly, and a webhook to get notified when a run finishes.
  • AI agents: call it through the Apify API, the Apify clients, or Apify's MCP server.

Run it with curl

curl -X POST "https://api.apify.com/v2/acts/fecklesspleb~broken-link-checker/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"startUrls": ["https://the-internet.herokuapp.com/broken_images"], "maxPagesPerSite": 50}'

Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("fecklesspleb/broken-link-checker").call(
run_input={"startUrls": ["https://the-internet.herokuapp.com/broken_images"], "maxPagesPerSite": 50},
max_total_charge_usd=1,
)
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
print(row["pageUrl"], "->", row["linkUrl"], row["error"])

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('fecklesspleb/broken-link-checker').call({
startUrls: ['https://the-internet.herokuapp.com/broken_images'],
maxPagesPerSite: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((r) => `${r.pageUrl} -> ${r.linkUrl} (${r.error})`));

Polite by design

  • Obeys robots.txt (including wildcards) and Crawl-delay for both crawling and checking.
  • At most one request every 0.5 seconds per host.
  • Each URL is checked once per run, even if it appears on many pages.
  • On pages with <meta name="robots" content="nofollow">, links are checked but not crawled further.

Limits

  • It reads the HTML the server returns and does not run JavaScript. Links that only appear after JavaScript runs are not seen.
  • It checks <a href> links and <img src> images. It does not check srcset, CSS background images, scripts or stylesheets.
  • Each run is independent. To track changes over time, compare the datasets of two runs.
  • On the Apify platform, links whose address is private, loopback or link-local (also after a redirect) are not requested. They are free, counted as "not checked", and shown only when Only broken links is off, with not checked: private, loopback and link-local addresses are not allowed. A start URL with such an address is not crawled; the dataset then gets one free row with recordType: "status".

Common searches this Actor answers

  • broken link checker for a whole website
  • find 404 errors on my site
  • dead link checker with export to CSV
  • check external links for dead pages
  • find broken images on a website
  • bulk link checker API
  • check links after a website migration

FAQ

How do I find all broken links on my website? Put your home page in Website URLs and set Max pages to crawl per site high enough to cover the site. Every link and image on the crawled pages is checked, and only broken ones are saved by default.

Does it find broken external links too? Yes, by default. Turn off Check external links to check only links within your own site.

Can it find broken images? Yes. Image sources from <img src> are checked when Check images is on (the default). Image rows have type: "image".

Why are 401, 403 and 429 not counted as broken? They usually mean a login wall, bot protection or a rate limit, not a dead page.

Why is a link marked "not checked"? The target site's robots.txt disallows automated access to it, so the Actor does not request it. These are counted in RUN_SUMMARY and shown in the dataset only when Only broken links is off.

Can I get every link with its status code, not just broken ones? Turn off Only broken links. Every checked link occurrence is saved with its status, at $0.0005 per row.

Can I check several websites in one run? Yes. Add one start URL per site. Each site gets its own entry in RUN_SUMMARY.

Is it legal to check links on a website? The Actor reads public pages the way a browser or search engine does, obeys robots.txt and crawl delays, never logs in, and collects no personal data. Use it on sites you own or are allowed to audit.