<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Onsite Tools</title>
    <description>The latest articles on DEV Community by Onsite Tools (@onsitetools).</description>
    <link>https://dev.to/onsitetools</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4237064%2F3e61592b-07f5-4743-8a3d-f0af188707f5.png</url>
      <title>DEV Community: Onsite Tools</title>
      <link>https://dev.to/onsitetools</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/onsitetools"/>
    <language>en</language>
    <item>
      <title>Why a link checker reports links that work</title>
      <dc:creator>Onsite Tools</dc:creator>
      <pubDate>Tue, 10 Nov 2026 12:27:12 +0000</pubDate>
      <link>https://dev.to/onsitetools/why-a-link-checker-reports-links-that-work-1lbj</link>
      <guid>https://dev.to/onsitetools/why-a-link-checker-reports-links-that-work-1lbj</guid>
      <description>&lt;p&gt;You click the link. It opens. The report still says broken. Nothing is wrong with your eyes and usually nothing is wrong with the link: &lt;strong&gt;the server answered you and refused the checker&lt;/strong&gt;, and the two arrive looking almost identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;A link checker is a program asking a web server a question. A browser is a person asking the same server the same question. Increasingly, servers give those two askers different answers.&lt;/p&gt;

&lt;p&gt;When a checker gets refused, what comes back is a status code that is not &lt;code&gt;200 OK&lt;/code&gt;. So is what comes back from a page that has genuinely been deleted. On the face of it they look the same: both are a failure to fetch the page. The reasons behind them are nothing alike, and the number alone does not sort them out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why servers refuse the checker but serve you
&lt;/h2&gt;

&lt;p&gt;None of this is malice. Each of these is a real defence doing its job.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bot filtering.&lt;/strong&gt; Large platforms block automated traffic on purpose. Your request carries a browser, a history, cookies and a mouse. The checker's request carries none of that, and gets shown the door.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A firewall in front of the site.&lt;/strong&gt; A content delivery network or a security service may issue a challenge that a browser passes silently and a checker cannot pass at all. The checker sees a page saying &lt;em&gt;enable JavaScript and cookies to continue&lt;/em&gt;, which is not the page it asked for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting.&lt;/strong&gt; Check forty links on the same domain in a minute and the fortieth gets refused, even though the first one was fine. The link is not broken. You are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The server dislikes the method.&lt;/strong&gt; A checker usually asks &lt;code&gt;HEAD&lt;/code&gt; first, because it only wants the status and not the whole page. Some servers do not answer &lt;code&gt;HEAD&lt;/code&gt; at all, and reply with an error to a question they would have answered had it been phrased as &lt;code&gt;GET&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own server is the one being blocked.&lt;/strong&gt; The check leaves from wherever the tool runs. If that address has a poor reputation, or sits in a range someone has blocked, the refusal has nothing to do with the destination at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to tell a refusal from a dead page, in ten seconds
&lt;/h2&gt;

&lt;p&gt;Take the status code the report gives you. If it does not give you one, that is worth knowing on its own, because without it there is nothing to reason about.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;What it usually means&lt;/th&gt;
&lt;th&gt;Is the link broken?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;404&lt;/code&gt;, &lt;code&gt;410&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The page is genuinely not there. &lt;code&gt;410&lt;/code&gt; means the server is certain it is gone for good.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes.&lt;/strong&gt; Fix it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;403&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forbidden. The server understood and decided not to answer. Almost always bot filtering.&lt;/td&gt;
&lt;td&gt;Usually &lt;strong&gt;no&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;999&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not a real HTTP code. It is a private "no" invented by a large network to turn away automation.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;429&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Too many requests. You asked too fast.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No.&lt;/strong&gt; Slow down and ask again.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;503&lt;/code&gt;, &lt;code&gt;502&lt;/code&gt;, &lt;code&gt;504&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The far side is unwell or behind a challenge, right now.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not yet.&lt;/strong&gt; Ask again later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;405&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Method not allowed. The server will not answer &lt;code&gt;HEAD&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No.&lt;/strong&gt; A &lt;code&gt;GET&lt;/code&gt; would have worked.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A timeout&lt;/td&gt;
&lt;td&gt;Nobody answered in time. Slow server, or a check that gave up early.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Unknown.&lt;/strong&gt; That is the honest answer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;200&lt;/code&gt; on a page that says "not found"&lt;/td&gt;
&lt;td&gt;A soft 404. The page is missing but the server says it is fine.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt;, and no status code will tell you.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second-to-last row is the one worth sitting with. &lt;strong&gt;"I could not check this" and "this is broken" are different findings&lt;/strong&gt;, and collapsing them into one word leaves the reader guessing which of the two they are looking at. The last row is the opposite trap: a status code alone cannot catch every dead page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do about each kind
&lt;/h2&gt;

&lt;h3&gt;
  
  
  If it is genuinely dead
&lt;/h3&gt;

&lt;p&gt;Find the new address if there is one, and replace it everywhere it appears rather than in the one post you happened to be looking at. If the page is gone for good, decide between removing the link and pointing it at an archive copy.&lt;/p&gt;

&lt;h3&gt;
  
  
  If the check was refused
&lt;/h3&gt;

&lt;p&gt;Do nothing to the link. There is nothing wrong with it. What you want is for the tool to stop telling you about it: a good report separates these out by itself, and a list of domains that never answer robots should be something you can set once and forget.&lt;/p&gt;

&lt;p&gt;Checking that link by hand every month is not a solution. Neither is deleting a working link to make the warning go away. That costs you a link you meant to keep, and it is the quiet way a list of false alarms ends up shaping your site.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you are not sure
&lt;/h3&gt;

&lt;p&gt;Open it. You are the control group. If it opens for you and the checker says &lt;code&gt;403&lt;/code&gt;, you have your answer and the tool is describing its own experience, not the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check one link, right now
&lt;/h2&gt;

&lt;p&gt;If you have a link in front of you that you are arguing with, you can settle it here without installing anything. It shows you the status code, the redirects it followed and what the server actually said.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://onsitetools.dev/check-a-link/" rel="noopener noreferrer"&gt;&lt;strong&gt;Check a single link →&lt;/strong&gt;&lt;/a&gt; Free, no account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://onsitetools.dev/does-403-mean-a-link-is-broken/" rel="noopener noreferrer"&gt;Does a 403 mean the link is broken?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://onsitetools.dev/links-that-always-look-broken/" rel="noopener noreferrer"&gt;Sites whose links always look broken&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://onsitetools.dev/will-a-link-checker-change-my-site/" rel="noopener noreferrer"&gt;Will a link checker change my site?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Doing this across a whole site
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://onsitetools.dev/onsite-link-verdict/" rel="noopener noreferrer"&gt;Onsite Link Verdict&lt;/a&gt; is our WordPress plugin for this. It checks every link on your site from your own server, and it will not call a link broken without showing you the evidence: the status, the redirects and the reason, in plain language. Refused checks live in their own list, described as what they are, because that is the distinction the whole job turns on.&lt;/p&gt;

&lt;p&gt;It is free on the WordPress.org directory and free for good, not a trial.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://onsitetools.dev/links-that-work-but-report-as-broken/" rel="noopener noreferrer"&gt;onsitetools.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>webdev</category>
      <category>seo</category>
      <category>http</category>
    </item>
  </channel>
</rss>
