How to Find and Fix Broken Links on a Page
Broken links cost you three different things. Readers hit a dead end and leave. Crawlers spend requests on URLs that return nothing. And internal links to dead pages strand the authority you meant to pass along. The fix is mechanical, but only if you check status codes instead of clicking a sample and hoping.
Read the status code, not the color
| Status | Meaning | What to do |
|---|---|---|
| 200 | the link works | nothing |
| 301 | permanent redirect | works, but update the link to the destination so readers and crawlers skip the hop |
| 302 or 307 | temporary redirect | fine short term. If the move is permanent, the server should say 301 |
| 403 | forbidden | often bot protection rather than breakage. Open it in a browser before deleting the link |
| 404 | not found | fix or remove. This is the one that matters |
| 410 | gone, deliberately | remove the link. The owner is telling you it will not come back |
| 405 or 501 | method not allowed | the server rejected a HEAD request. Retry with GET before calling it broken |
| 429 | rate limited | you checked too fast. Slow down and retry |
| 5xx | server error | retry later. If it persists, treat it as broken |
| no status | DNS failure, TLS error, or timeout | verify by hand. Dead domains and expired certificates both land here |
How to check the links on a page
A HEAD request is the polite way to test a URL, because it asks for the status without downloading the body. Some servers do not implement it and answer 405, 403, or 501, so any checker worth using retries those with GET before reporting breakage.
# status only, following redirects, showing the chain
curl -sIL -o /dev/null -w '%{http_code} %{url_effective}\n' https://example.com/page
# retry with GET when the server rejects HEAD
curl -sL -o /dev/null -w '%{http_code}\n' https://example.com/page
To collect the links first, the console is enough. This gives you every unique destination on the page, which is the input to any checking loop:
[...new Set([...document.querySelectorAll('a[href]')]
.map(a => a.href)
.filter(href => href.startsWith('http')))]
Two cautions. Requests you fire from the page inherit its cookies and origin, so results can differ from what an anonymous crawler sees. And checking a few hundred links in parallel looks like an attack: keep concurrency low, around five or six at a time, with a timeout of about ten seconds.
Fix by category
- Internal 404: the highest priority. Either restore the page, or point the link at the URL that replaced it. Do not leave it pointing at a redirect chain.
- External 404: find the page again on the current site, replace it with an equivalent source, or link the Internet Archive copy when the original is genuinely gone and the citation matters.
- Redirect chains: two or more hops to reach a 200. Link directly to the final URL. Chains slow readers down and can be truncated by crawlers.
- HTTP links on an HTTPS page: mixed content that browsers may block outright. Switch them to HTTPS, which almost always works now.
- Anchors to missing ids: a link to
#section-3when no element has that id returns 200 and still fails the reader. Check fragments against the target page. - Typos in mailto and tel links: never show as broken in any checker because there is no request to make. Read them by eye.
False positives to expect
- Sites behind a bot-protection layer answer 403 to anything without a browser fingerprint. Verify one by hand before mass-editing.
- Login-gated pages return 401 or 403 for the checker and 200 for you, because you have a session.
- Aggressive rate limits turn a working site into a wall of 429s. Recheck slowly.
- Soft 404s return 200 with a "page not found" body. No status-based checker catches these; only reading the page does.
- Slow origins time out under a ten-second budget and are not actually broken.
Keep them from coming back
- Whenever you retire a URL, add the redirect in the same change. Broken internal links are almost always self-inflicted.
- Prefer linking to a stable canonical URL over a dated or paginated one.
- Re-check your highest-traffic and most-linked pages on a schedule. External links rot whether or not you touch the page.
- Watch Search Console for a rise in "not found" URLs, which usually points at a template that generates bad links.
Common questions
Do broken outbound links hurt rankings?
Google has said a handful of broken outbound links is not a ranking factor in itself. The real costs are worse user experience and the signal that a page is unmaintained. Broken internal links matter more, because they waste crawling and break the path authority takes through your site.
Why does a link work in my browser but fail the checker?
Usually because your browser sends cookies, a familiar user agent, and a full set of headers, while the checker does not. Bot protection, login walls, and rate limits all produce this. It can also be the reverse of a redirect chain: your browser follows every hop silently while the checker reports the first non-200 it sees.
How often should I check for broken links?
Before publishing any page with a lot of citations, after any site migration or URL change, and quarterly for your most important pages. External links decay continuously, so a page you checked a year ago is not a page you have checked.
Should I remove a link or use nofollow when a page 404s?
Remove it or repoint it. A nofollow on a dead link still sends the reader to a dead end, and that is the part that costs you.