この記事が扱う症状
以下に心当たりがある人向けです。
- レジストラでネームサーバーを変更したのに、ドメインが引けない
digがstatus: SERVFAILを返す- 権威サーバーに直接聞くと
status: REFUSEDが返る - なのにレジストリの委任は正しい4本が入っている
- レジストラで保存したはずのカスタムネームサーバーが、いつの間にか標準に戻っている
- Netlify で primary domain を設定したのに、
*.netlify.appが 200 を返し続ける - Search Console でサイトマップを送信すると「サイトマップ アドレスが無効です」
結論から言うと、SERVFAIL の原因は Netlify 側に DNS ゾーンが作られていなかったことでした。ネームサーバーは正しく向いていて、向いた先が空っぽだった。
やろうとしたこと
Netlify の *.netlify.app サブドメインで運用していたこのブログを、独自ドメインに移すだけの作業です。
ドメインは Squarespace(旧 Google Domains)で .dev を取得。DNS は Netlify DNS に任せる構成にしました。手順としては「レジストラでネームサーバーを Netlify のものに変える」だけのはずでした。
実際には4回ハマりました。順番に書きます。
症状1: 委任は通っているのに SERVFAIL
ネームサーバーを dns1〜4.p02.nsone.net に設定して保存。しばらく待ってから引いてみると、何も返ってきません。
$ dig A orangevaper.dev @1.1.1.1
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 38097「まだ伝播していないのだろう」と思いがちな場面ですが、SERVFAIL は伝播待ちの顔ではありません。委任先が見つからないなら NXDOMAIN、委任が未反映ならそもそも古い応答が返ります。SERVFAIL は「委任先に聞きに行ったが、まともな答えが得られなかった」という意味です。
そこで、公開リゾルバを信用せずにレジストリへ直接聞きます。.dev の TLD サーバーに +norecurse で問い合わせると、レジストリに登録されている委任そのものが見えます。
$ dig NS orangevaper.dev @ns-tld1.charlestonroadregistry.com +norecurse
;; AUTHORITY SECTION:
orangevaper.dev. 10800 IN NS dns1.p02.nsone.net.
orangevaper.dev. 10800 IN NS dns2.p02.nsone.net.
orangevaper.dev. 10800 IN NS dns3.p02.nsone.net.
orangevaper.dev. 10800 IN NS dns4.p02.nsone.net.委任は正しく通っています。レジストラ側の設定は問題なし。
では向き先はどうか。権威サーバーに直接聞きます。
$ for ns in dns1.p02.nsone.net dns2.p02.nsone.net dns3.p02.nsone.net dns4.p02.nsone.net
dig SOA orangevaper.dev @$ns +norecurse
end
dns1.p02.nsone.net status: REFUSED
dns2.p02.nsone.net status: REFUSED
dns3.p02.nsone.net status: REFUSED
dns4.p02.nsone.net status: REFUSED4本とも REFUSED。
REFUSED が意味すること
ここが分かれ目でした。3つのステータスは別物です。
| ステータス | 意味 |
|---|---|
NXDOMAIN | そのゾーンは持っているが、その名前は存在しない |
SERVFAIL | 問い合わせの過程で失敗した(DNSSEC 不整合、上流が無応答、など) |
REFUSED | そのゾーン自体をホストしていない。答える立場にない |
つまり NS1 は「orangevaper.dev なんて知らない」と言っています。ネームサーバーは向いているのに、向いた先にゾーンが存在しない。だから公開リゾルバは委任をたどった先で門前払いされ、SERVFAIL を返していたわけです。
原因は単純でした。Netlify で DNS ゾーンを作っていなかった。
Netlify は「サイトにカスタムドメインを追加する」のと「Netlify DNS のゾーンを作る」が別の操作です。前者だけやってネームサーバーを向けても、NS1 側には何も無い。
ここのことですかね
Domain management の Add a domain → Add a domain you already own でドメインを追加したあと、別途 Set up Netlify DNS を実行する必要があります。この2段構えが分かりにくい。
ちなみにこの時点で Netlify の画面は「update your DNS records at your registrar」(レジストラで DNS レコードを更新してください)と案内してきます。これは Netlify DNS を使わず、レジストラの DNS に A レコードと CNAME を手で足す前提の案内です。ネームサーバーをすでに NS1 に向けてしまっている場合、この案内どおりに動くと迷子になります。
症状2: 払い出されるネームサーバーはゾーンごとに違う
ゾーンを作ると、Netlify が4本のネームサーバーを表示します。ここで表示されたのは:
dns1.p04.nsone.net
dns2.p04.nsone.net
dns3.p04.nsone.net
dns4.p04.nsone.netp02 ではなく p04 でした。
NS1 のネームサーバーは pXX の部分がゾーンごとに割り当てられます。ドキュメントや他人のブログ記事に載っている値をそのままコピペすると、存在しないゾーンを指すことになる。症状1で見た REFUSED は、まさにこれが重なっていた可能性もあります。
ゾーンを作ったあと p04 に直接聞くと、ちゃんと答えが返ってきました。
$ dig A orangevaper.dev @dns1.p04.nsone.net +norecurse +short
52.74.6.109
13.215.239.219ゾーンは存在し、Netlify の A レコードも入っている。あとはレジストラ側を p02 から p04 に直すだけです。
症状3: 保存したはずのネームサーバーが標準に戻る
p04 に入れ替えてもらって、確認します。
p04に入れ替えました、確認お願いします
$ dig NS orangevaper.dev @ns-tld1.charlestonroadregistry.com +norecurse
orangevaper.dev. 10800 IN NS nsd1.squarespacedns.com.
orangevaper.dev. 10800 IN NS nsd2.squarespacedns.com.
orangevaper.dev. 10800 IN NS nsd3.squarespacedns.com.
orangevaper.dev. 10800 IN NS nsd4.squarespacedns.com.p04 どころか p02 ですらなく、Squarespace の標準ネームサーバーに戻っていました。
さらに厄介なことに、この状態では名前解決が成功します。ただし返ってくるのは Squarespace のパーキング用 IP で、www は ext-sq.squarespace.com を向いている。「ドメインは生きているが、まったく別のところを指している」という、SERVFAIL より気づきにくい状態です。
原因はレジストラの UI でした。ネームサーバーの設定画面には「Squarespace のネームサーバーを使う」と「カスタムネームサーバー」の切り替えがあり、カスタム側を明示的に選んだ状態で保存しないと標準に差し戻される。保存直後の画面では入力した値が見えているので、成功したように錯覚します。
入れ直してもらって、今度は通りました。
ちょっと手間取ったからwどうでしょうか
$ dig NS orangevaper.dev @ns-tld1.charlestonroadregistry.com +norecurse
orangevaper.dev. 10800 IN NS dns1.p04.nsone.net.
(以下 dns2〜4)
$ dig +short A orangevaper.dev @8.8.8.8
52.74.6.109
13.215.239.219証明書も自動で発行され、https:// で開けるようになりました。.dev は HSTS preload 済みで HTTPS 必須なので、証明書が出るまでは「繋がらない」ように見える点も覚えておくと焦らずに済みます。
症状4: primary domain を設定しても netlify.app は 301 されない
ここは私が説明を間違えたところです。
Netlify で「primary domain を独自ドメインに設定すれば、旧 *.netlify.app からは自動で 301 が張られる」と思っていました。実際に設定してもらって確認すると:
$ curl -sSI "https://oranges-blog.netlify.app/?cb=12345"
HTTP/2 200
age: 0
cache-control: public,max-age=0,must-revalidate
server: Netlify200 が返り続けます。 しかも age: 0。CDN のキャッシュではなく origin から直接返ってきている、つまり仕様としてそうなっている。
紛らわしいのは、www → apex の 301 はちゃんと効くことです。
https://www.orangevaper.dev/ -> 301 https://orangevaper.dev/
https://oranges-blog.netlify.app/ -> 200答えは Netlify の管理画面自身が書いていました。
Your project is always accessible at a netlify.app subdomain
always。primary domain を何に設定しようと、netlify.app サブドメインは常に生き続けて内容を返します。これを放置すると、新旧2つの URL で同じ内容が配信される=重複コンテンツです。
対策は netlify.toml に自分で書きます。
[[redirects]]
from = "https://oranges-blog.netlify.app/*"
to = "https://orangevaper.dev/:splat"
status = 301
force = trueforce = true が要ります。これが無いと、同じパスに静的ファイルが存在する場合はそちらが優先されてリダイレクトが効きません。今回はまさに全パスにファイルが存在するので、付けないと何も起きません。
なお from にドメインを含むフルURLを書けるのは Netlify の仕様です。deploy-preview-N--oranges-blog.netlify.app はホストが違うので、デプロイプレビューには影響しません。
反映後:
oranges-blog.netlify.app/ -> 301 orangevaper.dev/
oranges-blog.netlify.app/posts/<slug>/ -> 301 orangevaper.dev/posts/<slug>/
oranges-blog.netlify.app/rss.xml -> 301 orangevaper.dev/rss.xmlパスもクエリも保持されます。RSS も転送されるので、旧 URL で購読していた人もそのまま追従します。
テーマが canonical を出していないと逃げ場がない
このブログが使っている Fuwari テーマは <link rel="canonical"> を出力していませんでした。canonical があれば「正規 URL はこっち」と伝えて重複を緩和できますが、それが無い以上、301 が唯一の防御になります。
移行前に自分のテーマが canonical を出しているか確認しておくと、判断が早くなります。
$ grep -o 'rel=canonical[^>]*' dist/index.html
(何も出なければ未出力)ビルド後の HTML が minify されていると属性のクォートが落ちるので、rel="canonical" で grep すると空振りします。これで一度「出ていない」と誤判定しかけました。
症状5: ドメインプロパティのサイトマップは完全URLが要る
最後は Search Console です。独自ドメインは Search Console 的にはまったく別のサイトなので、新規にプロパティを追加します。
サブドメインもプロトコルもまとめて扱えるドメインプロパティを選びました。所有権の確認は apex の TXT レコードです。
$ dig +short TXT orangevaper.dev @8.8.8.8
"google-site-verification=..."Netlify DNS 側で TXT を足すとき、Name は @ を入れます(空欄だと必須エラー、ドメイン名を入れると orangevaper.dev.orangevaper.dev になって通りません)。
サイトマップですが、送信してないかも!
所有権が通ったあと、サイトマップを送信しようとすると弾かれました。
サイトマップ アドレスが無効です入力したのは sitemap-index.xml。URL プレフィックスプロパティなら、入力欄の左にドメインが固定表示されていて相対パスで通ります。しかしドメインプロパティには固定のプレフィックスが無い(サブドメインもプロトコルも全部含むため)ので、完全な URL が必要です。
https://orangevaper.dev/sitemap-index.xmlこれで通りました。送信直後は「取得できませんでした」「検出されたページ数 0」と表示されますが、Google がまだ取りに来ていないだけです。
診断の型: dig を上から降りる
今回のハマりは全部、どの段で崩れているかを切り分けるだけで原因が一意に決まりました。上から順に3段です。
# 1. レジストリに直接(委任そのものを見る。リゾルバのキャッシュを一切挟まない)
dig NS example.dev @ns-tld1.charlestonroadregistry.com +norecurse
# 2. 権威サーバーに直接(ゾーンが存在するか)
dig SOA example.dev @dns1.p04.nsone.net +norecurse
# 3. 公開リゾルバ(実際の利用者から見える姿)
dig A example.dev @1.1.1.1| どこで崩れたか | 原因 |
|---|---|
| 1 が期待と違う | レジストラの設定が反映されていない(保存できていない・標準に戻された) |
| 1 は正しいが 2 が REFUSED | 委任先にゾーンが無い。DNS ホスティング側でゾーン未作成、または NS の割り当てセット違い |
| 1・2 は正しいが 3 が古い | 本当に伝播待ち。待てば直る |
+norecurse を付けるのが要点です。これが無いと問い合わせ先が勝手に再帰して、自分のキャッシュ越しの答えを返してくることがあります。
.dev 以外の TLD なら、その TLD の権威サーバーを使います(dig NS dev. のように引けば分かります)。
教訓
1. 「保存した」は「反映された」ではない。 レジストラの管理画面は、保存に失敗しても入力値を表示し続けることがあります。信用すべきはレジストリの応答だけです。設定したら dig でレジストリに直接聞く。これを最初からやっていれば、症状3は5分で気づけました。
2. REFUSED・SERVFAIL・NXDOMAIN は別物として読む。 まとめて「引けない」で片付けると伝播待ちに見えてしまい、何時間でも待てます。REFUSED は「ゾーンが無い」という、待っても絶対に直らない種類のエラーです。
3. 管理画面の文言は仕様の宣言として読む。 「Your project is always accessible at a netlify.app subdomain」は、親切な補足ではなく動作の定義でした。期待した挙動が起きないときは、画面に書いてある英語を読み直すのが近道です。
ドメイン移行の手順記事はたくさんありますが、今回踏んだ4つはどれも手順どおりに進めた結果として出てくるものでした。手順が正しいことと、期待どおりに動くことは別です。


このブログではまだnetlifyのサブドメインですが、ちゃんとドメイン買ったほうがいいかな。