問い合わせ失敗、応答差、変更が見えない現象を分ける。名前・型・server・時刻を記録し、権威・再帰・システム解決を比較する。例の domain は変更対象ではない。
診断中は status を省かない
最初に応答全体を読む
dig example.com A
status・flags・ANSWER・AUTHORITY・SERVER・時間を記録する。例の TTL 300 は秒で、SERVER が問い合わせ先を示す。
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
www.example.com. 300 IN A 192.0.2.10
;; SERVER: 192.0.2.53#53(192.0.2.53) (UDP)
dig @1.1.1.1 example.com A +noall +comments +answer +authority +stats
@ はサーバー指定。表示オプションは状態・回答・authority・統計を残すためで、レコードは変更しない。
目的に合うレコード型を選ぶ
dig example.com AAAA
IPv4 のみ通る場合、AAAA と IPv6 経路を調べる。AAAA なしでも名前自体がないとは限らない。
dig example.com MX
通常、数値が小さいほど優先される。交換器名のアドレスは別に調べる。TXT/CAA/CNAME も明示し、ANY を全件取得と考えない。
dig -x 192.0.2.10
実 IP に置き換える。PTR と A/AAAA は別管理で、一方だけでは他方の設定を証明できない。
アプリの名前解決経路も確認する
getent ahosts example.com
hosts・DNS・他の NSS モジュールからの可能性がある。dig と違えば来源を調べる。独自 DNS や DoH を使うアプリもある。
resolvectl query example.com
resolved を使わない環境では不要。resolvectl status でリンク別サーバーと routing domain を見る。
| 応答 | 意味と次の確認 |
| NOERROR + answer | 応答あり。CNAME・型・TTL・SERVER を確認 |
| NOERROR + empty answer | 型の NODATA や referral 等。authority を見る |
| NXDOMAIN | その view で名前不存在。negative cache を確認 |
| SERVFAIL | 解決処理失敗。DNSSEC・権威への到達等を調べる |
| timeout | 応答が時間内にない。経路・UDP/TCP53・server を確認 |
dig は DNS、getent は hosts 等を含む NSS、resolvectl は systemd-resolved。経路が違う。+short は状態確認後の抽出向け。ANY は全記録の列挙を保証せず必要な型を指定する。
TTL は各 cache 項目の期限
図を準備しています
権威変更後に二つの cache は同時更新するかTTL120秒の旧記録をt=0と30に cache、権威はt=60に変更。旧 copy はそれぞれt=120と150で期限になる。
{"id":"ops-dns-cache","title":"権威変更後に二つの cache は同時更新するか","summary":"TTL120秒の旧記録をt=0と30に cache、権威はt=60に変更。旧 copy はそれぞれt=120と150で期限になる。","height":600,"html":"<main><p class=\"quiet\" data-i18n=\"scope\"></p><label class=\"range\" for=\"time\"><span data-i18n=\"time\"></span><output id=\"timeValue\"></output><input type=\"range\" id=\"time\" min=\"30\" max=\"180\" value=\"90\"></label><div class=\"row code\">t=0 A ← old · TTL 120\nt=30 B ← old · TTL 120\nt=60 authority ← new</div><div class=\"cards\"><section class=\"card \"><h3 data-i18n=\"auth\"></h3><strong id=\"auth\"></strong></section><section class=\"card \"><h3 data-i18n=\"r1\"></h3><strong id=\"r1\"></strong></section><section class=\"card \"><h3 data-i18n=\"r2\"></h3><strong id=\"r2\"></strong></section></div><div class=\"explain\"><div class=\"stable \"><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"hint\"></p><p id=\"message\" aria-live=\"polite\"></p></div></div></main>\n","css":"main {\n display: grid;\n gap: 18px;\n min-width: 0;\n}\np {\n overflow-wrap: anywhere;\n}\n.controls {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n align-items: center;\n}\n.presets {\n display: flex;\n flex-wrap: wrap;\n}\n.presets button {\n flex: 1 1 85px;\n font-size: 15px;\n}\n.cards {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 10px;\n}\n.card {\n border: 1px solid var(--rule);\n border-radius: 10px;\n padding: 14px;\n min-width: 0;\n background: var(--surface);\n}\n.card h3 {\n font-size: 15px;\n color: var(--muted);\n margin-bottom: 8px;\n}\n.card strong {\n font-size: 23px;\n font-weight: 500;\n overflow-wrap: anywhere;\n}\n.code {\n font-family: var(--viz-font-mono);\n font-size: 15px;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.stable {\n display: grid;\n min-width: 0;\n}\n.stable > * {\n grid-area: 1/1;\n margin: 0;\n}\n.stable > .reserve {\n visibility: hidden;\n pointer-events: none;\n}\n.explain {\n border-left: 3px solid var(--accent);\n padding: 12px 14px;\n background: var(--accent-soft);\n}\n.arrow {\n text-align: center;\n color: var(--accent);\n font-size: 25px;\n}\n.row {\n padding: 12px;\n border: 1px solid var(--rule);\n border-radius: 8px;\n min-width: 0;\n}\n.row.on {\n border-color: var(--accent);\n background: var(--accent-soft);\n}\n.row.off {\n color: var(--muted);\n}\n.bar {\n height: 18px;\n background: var(--surface);\n border: 1px solid var(--rule);\n border-radius: 5px;\n overflow: hidden;\n margin-top: 8px;\n}\n.bar > i {\n display: block;\n height: 100%;\n background: var(--accent);\n width: 0;\n transition: width 0.22s;\n}\n.checks {\n display: grid;\n gap: 8px;\n}\n.checks label {\n display: flex;\n gap: 10px;\n align-items: center;\n padding: 9px;\n border: 1px solid var(--rule);\n border-radius: 7px;\n font-size: 16px;\n}\n.checks input {\n width: 18px;\n height: 18px;\n accent-color: var(--accent);\n flex-shrink: 0;\n}\nlabel.range {\n display: block;\n font-size: 16px;\n}\nlabel.range output {\n float: right;\n font-variant-numeric: tabular-nums;\n}\n.pair {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n}\n.flow {\n display: grid;\n gap: 6px;\n}\n.quiet {\n font-size: 14px;\n color: var(--muted);\n}\n.badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 5px;\n background: var(--accent-soft);\n color: var(--accent);\n}\nbutton {\n overflow-wrap: anywhere;\n}\n.large {\n font-size: 32px;\n color: var(--accent);\n font-weight: 500;\n}\n.layers {\n display: grid;\n gap: 12px;\n}\n.layer {\n border-left: 3px solid var(--rule);\n padding-left: 12px;\n}\n.layer.on {\n border-color: var(--accent);\n}\n@media (max-width: 520px) {\n .cards,\n .pair {\n grid-template-columns: 1fr;\n }\n .card {\n padding: 12px;\n }\n .card strong {\n font-size: 22px;\n }\n}\n.card strong {\n font-size: 19px;\n min-height: 3.2em;\n display: block;\n}\n","js":"const $ = (s) => document.querySelector(s);\nconst $$ = (s) => [...document.querySelectorAll(s)];\nconst t = (k) => viz.t(k);\nconst put = (id, v) => ($(\"#\" + id).textContent = v);\nconst say = (k) => put(\"message\", t(k));\nconst state = (v) => ($(\"main\").dataset.state = JSON.stringify(v));\nconst press = (key, v) =>\n $$(\"[data-\" + key + \"]\").forEach((b) =>\n b.setAttribute(\"aria-pressed\", String(b.dataset[key] === v)),\n );\nfunction draw() {\n let time = +$(\"#time\").value;\n put(\"timeValue\", time + \" s\");\n const auth = time < 60 ? \"old\" : \"new\",\n a = time < 120 ? \"old\" : auth,\n b = time < 150 ? \"old\" : auth;\n put(\"auth\", t(auth));\n put(\"r1\", t(a) + \" · TTL \" + (time < 120 ? 120 - time : 120));\n put(\"r2\", t(b) + \" · TTL \" + (time < 150 ? 150 - time : 120));\n state({ time, auth, a, b });\n}\n$(\"#time\").oninput = draw;\nsay(\"hint\");\ndraw();\n","audio":false,"strings":{"auth":"権威記録","hint":"権威変更は既存 cache に push されない。A が先に期限となり、新旧が混在し得る。期限後の問い合わせで新値を取得する。","new":"新 IP","old":"旧 IP","r1":"cache A:t=0 取得","r2":"cache B:t=30 取得","scope":"prefetch・serve-stale・追加 cache なし。指定時刻に一回問い合わせた結果を比較する。連続再生ではない。","time":"問い合わせ時刻 t(秒)"}}
権威 DNS を確認してからキャッシュを比較する
dig example.com NS
子 domain の委任も確認する。親の NS がすべての子を担当するとは限らない。
dig @ns1.example.com www.example.com A +norecurse
サーバー名を置き換え、aa と SOA/委任を確認する。aa がなければ zone を見直す。複数権威を比較して反映差を調べる。
dig @1.1.1.1 www.example.com A
問い合わせ時刻を記録する。権威更新後も旧回答なら旧 TTL 待ちの可能性がある。負のキャッシュも新規レコードの可視性を遅らせる。
dig +trace www.example.com A
各階層 DNS への直接アクセスが必要。企業の転送・分割 DNS・再帰キャッシュは再現しない。
ローカルキャッシュが原因と判断してから消す
sudo resolvectl flush-caches
任意の修復で、問い合わせ前に毎回実行するものではない。再問い合わせと実アプリを確認する。他のキャッシュは残る。
権威 server を置き換え AA・委任・zone を確認する。+trace は再帰器の cache・forwarder・split view を再現しない。応答差は地理・view・cache 等でも起こる。TTL を下げても既存 cache の期限は短くならず、負応答も cache される。
record の識別を保存して各層を検証
cloud 変更前に zone/record ID・名前・型・値・TTL・経路方針を保存する。record ID で更新し意図せぬ追加を避ける。Alibaba UpdateDomainRecord は RR・Type・Value が必要。無関係な TXT を残し、権威→再帰器→アプリで確認する。
resolvectl flush-caches はローカル resolved だけで、ISP・browser・container・外部再帰器の cache は消さない。ACME DNS-01 は委任・TXT 名と値も確認する。証明書参照。
暗号化転送と DNSSEC 検証を分ける
DNSSEC データの要求と検証を区別する
dig @1.1.1.1 example.com A +dnssec
RRSIG などと ad フラグを読む。dig 自体による信頼連鎖の検証ではない。
delv example.com A
validated・unsigned・validation failure を区別する。時刻・信頼アンカー・ネットワークを確認する。未署名と署名不正は別。
転送方式を比べる際は問い合わせを揃える
dig @1.1.1.1 example.com A +tcp
UDP のみ失敗するならフィルター・断片化・経路を調べる。TCP DNS は暗号化ではない。
kdig @1.1.1.1 example.com A +tls +tls-ca +tls-hostname=cloudflare-dns.com
+tls は TLS、+tls-ca は信頼検証、+tls-hostname は期待する名前。失敗時はポート・時刻・証明書を調べ、検証を切らない。対応方式は提供元で確認する。
kdig は TLS 対応の Knot tools と対応 endpoint が必要。+dnssec は要求で dig 自体の検証ではない。AD は再帰器の主張で信頼できる経路が前提。delv は trust anchor と正しい時計で検証する。暗号化転送と zone の署名は別。
BIND dig/delv · DNS negative caching RFC 2308 · Alibaba UpdateDomainRecord · Cloudflare DoT