ネットワーク 公開日 2026.09.12 更新日 2026.09.12

HEADで確認すると生きているページが死んで見える|リンク切れチェックの誤検知をなくす

リンク切れや死活の確認を HEAD だけで済ませると、配信されているページを死んでいると報告することがあります。仕様では HEAD は本文を返さない GET ですが、実装がそう作られているとは限りません。標準ライブラリで HEAD が501、GET が200を返す状態を再現し、応答の読み分けと、測れなかった回を判定にしない設計までまとめます。

先に要点

  • リンク切れや死活の確認を HEAD だけで済ませると、生きているページを死んでいると報告することがあります。仕様では HEAD は本文を返さない GET ですが、実装がそう作られているとは限りません
  • 標準ライブラリだけで再現できます。GET だけ実装したサーバーに同じ URL で両方を投げると、HEAD は 501、GET は 200 を返しました。
  • 対処は単純です。2xx 以外が返ったら、必ず GET で取り直してから結論を出します。1回の応答で判定を確定させないでください。
  • もう1つ重要なのが、「測れなかった」を「判定」として保存しないことです。取得に失敗したのに採点すると、偽の異常として記録が残ります。とくに公開状態を調べる確認は、失敗したら危険側に倒してください。

自動チェックが「このページは死んでいる」と言ってきたので開いてみたら、普通に表示された。この食い違いで最初に疑うのは、ページではなく確認の方法です。

この記事では、HEAD で確認したときに起きる誤検知を手元で再現し、応答をどう読み分ければ誤報を消せるかを整理します。自分でチェックを書く人にも、他人のツールの結果を読む人にも効く話です。

仕様と実装の間にすき間がある

HTTP の仕様では、HEAD への応答は本文を含まず、ヘッダーは GET だったときと同じ値を示すとされています。だから軽く確認したいときの定番として使われてきました。

問題は、この「同じ値になる」を成立させるのは実装側だということです。経路をメソッドの文字列で振り分ける作りになっていると、GET しか登録していない経路に HEAD が来たとき、一致するものが無いと判断されます。返るのは 404 や 405、あるいは 501 です。

ページは正常に配信されているのに、確認の方法だけが失敗している状態になります。

手元で再現する

Python の標準ライブラリだけで確かめられます。do_GET だけを実装したサーバーを立て、同じ URL に両方のメソッドを投げます。

from http.server import BaseHTTPRequestHandler, HTTPServer
import threading, http.client

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        body = b"ok"
        self.send_response(200)
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

srv = HTTPServer(("127.0.0.1", 8731), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()

for method in ("HEAD", "GET"):
    c = http.client.HTTPConnection("127.0.0.1", 8731, timeout=5)
    c.request(method, "/article/1")
    r = c.getresponse()
    print(method, r.status, r.reason)
    r.read()
srv.shutdown()

実行した結果です。

メソッド 応答
HEAD 501 Unsupported method
GET 200 OK

同じサーバー、同じ URL、同じ瞬間です。違うのはメソッドだけ。これを「リンク切れ」として記録すると、直しようのない指摘が毎回出続けます。

このサンプルは標準ライブラリの挙動ですが、メソッドごとに経路を登録する仕組みなら、どの言語でも同じことが起こりえます。自分が使っている枠組みで HEAD がどう扱われるかは、実際に投げて確かめるのが確実です。

非2xxはGETで取り直す

対処はこれだけです。

  1. まず HEAD で確認する(帯域を使わないので速い)
  2. 2xx 以外が返ったら、同じ URL に GET を投げ直す
  3. GET の結果で判定する

速さのために HEAD を使う利点は残したまま、誤検知だけ消せます。最初の応答で確定させないことが要点です。

「返ってきた答え」と「届かなかったこと」は別物

もう1つ、応答の読み分けがあります。次の3つは、同じ「確認できなかった」に見えて意味がまったく違います。

起きたこと 意味 どう扱うか
サーバーが 404 を返した 相手が明確に「無い」と答えた 判定してよい
接続が切れた、名前が引けない、時間切れ そもそも答えを聞けていない 判定しない。1回は取り直す
429 が返った 回数制限に当たっただけ 障害ではない

この3つを1つの「失敗」にまとめると、通知の大半が誤報になります。プログラムの中では、返ってきた状態コードと、通信そのものの失敗を、別の型として持つのが安全です。同じ変数に入れると、必ずどこかで混ざります。

とくに 429 は注意してください。相手は正常に動いていて、こちらが急ぎすぎただけです。これを障害として数えると、本物の停止が通知の山に埋もれます。

測れなかったものを、判定として保存しない

ここがいちばん実害になります。本文を取得できなかったのに、そのまま採点して保存すると、記録の上では「急に悪化した」ように見えます。あとから見返しても、悪化したのか測れなかったのかを区別できません。

守ることは2つです。

  • 取得に成功したかどうかを、先に1つの値として持つ。成功していない回は、保存も通知もしない
  • 接続そのものの失敗は、結論を出す前に1回やり直す。1回届かなかっただけで断定しない

そして、失敗したときにどちら側へ倒すかを、項目ごとに決めておきます。

公開状態を調べる確認は、危険側に倒す

設定ファイルが外から読めてしまっていないかを調べる、といった確認を考えます。ここで接続に失敗したとき、「読めなかった=公開されていない」と結論すると、本当に公開されている状態を隠してしまいます。

安全な方は明らかです。確認できなかったら「不明」として残し、安全だと記録しない。見落としと誤報のどちらが痛いかは項目ごとに違うので、書くときに決めて、決めた理由も残しておきます。

該当しない項目に点を与えない

チェック項目には、そのサイトには当てはまらないものがあります。単一言語のサイトに多言語向けの指定が無いのは正常で、欠陥ではありません。

ここで「当てはまらない」を満点として数えると、2つ問題が起きます。

  • 点が水増しされます。測っていないものが得点になっている
  • あとで実際に測れるようにした瞬間、改善なのに点が下がったように見えます

該当しない項目は、分母から外してください。測れた項目だけで割れば、どちらの問題も起きません。

自動チェックの誤検知に関するよくある質問

Q. 最初から全部 GET にすればよいのでは?

数が少なければそれで構いません。数千 URL を定期的に確認するなら、本文を受け取らない分の差は効いてきます。HEAD で速く回し、怪しいものだけ GET で取り直す形が現実的です。

Q. 405 と 501 のどちらが返りますか?

実装によります。大事なのは番号を覚えることではなく、2xx 以外をそのまま結論にしないことです。

Q. 1回のやり直しで足りますか?

一時的な取りこぼしはほぼ拾えます。何度も続く失敗は本当の異常なので、回数を増やすより、連続して失敗した回数で通知を出す方が有効です。

Q. 他人のツールの結果を読むときは何を見ますか?

その指摘がどのメソッドで、何回試して出たものかを確認してください。分からない場合は、自分で同じ URL に GET を投げて突き合わせるのが早いです。

まとめ

自動チェックが出す「壊れている」は、対象ではなく測り方が壊れていることがあります。HEAD の扱いは実装に委ねられていて、同じ URL でも GET と結果が変わります

書くときも読むときも、次の4つを守れば誤報はかなり減ります。2xx 以外は GET で取り直す。届かなかったことと相手の答えを分ける。測れなかった回は保存も通知もしない。該当しない項目は分母から外す。

参考リンク

あとで見返すならここで保存

読み終わったあとに残しておきたい記事は、お気に入りからまとめて辿れます。