torabit.dev を IPv6 only で公開した
2026年10月18日
Intro
自分のプロフィールページを https://torabit.dev に公開した。
index.html 1 枚のペライチで、JS は 1 行も書いていない。
ソースは torabit/torabit.github.io で公開している。

このサイトは IPv6 only で配信している。 IPv4 しかない環境からは、ページを開くことすらできない。
きっかけは mozaic.fm だ。 自分はこの Podcast のリスナーで、久しぶりに聴こうとページを開いたら、DNS のエラーでつながらなかった。 会社のネットワークからだったので、どこかで何かが壊れているのだろう、くらいに思っていた。
後日、サイトのネットワーク周りを刷新したという記事が公開された。 読むと、A レコードを落として IPv6 only にしたとある。 DNS のエラーだったことを思い出すと、原因はそれだったのかもしれない。
IPv6 only は、その刷新で採用された技術の一つだった。 これまであまり気にしてこなかった分野で、読むうちに自分でも試したくなった。 ちょうどプロフィールページを作るところだったので、そのまま実験場にした。
今回は、torabit.dev がどう動いているかを構成要素ごとに解説する。 作る途中で分かったことや、まだ分かっていないことは、次の記事にまとめる。
no A Record
DNS には、名前に結びつける情報の種類ごとにレコードがある。
レコードは、ドメインごとに管理する範囲に分かれて置かれている。
この範囲を ゾーン と呼ぶ。
たとえば .dev のゾーンは .dev の運営者が、torabit.dev のゾーンは自分(実際には Cloudflare の DNS サーバ)が管理している。
レコードはこの記事にいくつも出てくるので、先に一覧にしておく。
| レコード | 中身 | torabit.dev での使い方 |
|---|---|---|
| A | IPv4 アドレス | 置かない |
| AAAA | IPv6 アドレス | GitHub Pages のアドレスを 4 本 |
| CNAME | 別の名前を指すレコード。この名前を引くと、どの種類を聞いても指した先のレコードが返る | 置かない |
| NS | このゾーンを管理する DNS サーバ | Cloudflare のサーバ |
| SOA | ゾーンの管理情報 | Cloudflare が自動で置く |
普通のサイトは A レコードと AAAA レコードの両方を持っていて、クライアントは自分の環境で使える方に接続する。
torabit.dev は A レコードを持たない。
$ dig +short A torabit.dev
$ dig +short AAAA torabit.dev
2606:50c0:8000::153
2606:50c0:8001::153
2606:50c0:8002::153
2606:50c0:8003::153
IPv6 のアドレスを持たない PC では、OS は IPv6 を使えないものとして扱い、A レコードだけを引きにいく。
ところが torabit.dev には A レコードがないので、接続先が 1 つも見つからない。
接続を試す段階まで進めないので、Chrome では名前解決のエラー ERR_NAME_NOT_RESOLVED になる。

IPv4 を断るだけなら、A レコードを残したまま、IPv4 で来た接続に 403 を返す方法もある。 それでも A レコードを置かないのは、403 だと X や Slack にリンクを貼ったときの表示がおかしくなることがあるからだ。 X や Slack は、リンクが貼られるとページの OGP タグを読みに来て、タイトルや画像付きの カード を作る。 このクローラーが IPv4 で来ると、403 のエラーページを読んで、その内容でカードを作ってしまう1。 A レコードがなければ、クローラーは名前を引けずページを取得できないので、カード自体が作られない。
実際、OGP のタグは置いてあるが、X に torabit.dev を投稿してもカードは出ず、URL の文字列が出るだけだった。 X のクローラーが IPv4 で来ていて、名前を引けていないのだろう。

もう一つ、torabit.dev は新しく取ったドメインなので、移行を考えずに IPv6 only から始められた。 すでに公開しているサイトを移すなら、IPv4 で取得されたページやカードが、古い内容のままキャッシュに残りうる。 torabit.dev は A レコードを一度も持ったことがないので、そうしたキャッシュもない。
DNS only
構成は次の 3 つでできている。
| 役割 | 使っているもの |
|---|---|
| ドメインの取得 | Cloudflare Registrar |
| DNS | Cloudflare DNS(DNS only。後述) |
| 配信 | GitHub Pages |
IPv6 only にするには、Cloudflare の プロキシ を避ける必要がある。 プロキシを通すと、DNS は配信元ではなく Cloudflare 自身のアドレスを返す。 そのアドレス2には IPv4 もあるので、A レコードが必ず返ってしまう3。
Cloudflare Pages や Workers で配信するとプロキシを通るので、配信は GitHub Pages に任せた。 GitHub Pages は IPv6 で到達でき、無料で、自分でサーバを管理しなくてよい。 Cloudflare は DNS にだけ使い、レコードはすべてプロキシを通さない DNS only(グレー雲)にしている。
GitHub Pages に独自ドメインをつなぐ方法は、そのドメインが zone apex かどうかで変わる。
zone apex は、torabit.dev のように頭に何も付けないドメインそのものを指す。
www.torabit.dev のようなサブドメインなら、torabit.github.io への CNAME を置けばよい。
しかし zone apex には CNAME を置けないため、torabit.dev では GitHub Pages が公開している IP アドレスを直接書いている4。
GitHub Pages は IPv4 と IPv6 のアドレスを 4 つずつ公開していて、torabit.dev には IPv6 の 4 つだけを AAAA レコードとして書いた。
Cloudflare には CNAME flattening という機能もあるが、今回は採用しなかった。
torabit.github.io は A レコードも持つので、flattening すると torabit.dev の応答に A レコードまで含まれる可能性があるからだ。
実際に何が返るかはまだ確かめていないので、次の記事で書く。
no www
www.torabit.dev は用意していない。
GitHub Pages のドキュメントは、www を <user>.github.io への CNAME にするよう勧めている。
CNAME で向けておけば、GitHub Pages のアドレスが変わっても自分で書き換えずに済む。
しかし CNAME はレコードの種類を問わず効くので、AAAA レコードを聞かれても A レコードを聞かれても、torabit.github.io の応答が返る。
torabit.github.io は A レコードも持っているので、IPv4 の人も www.torabit.dev には接続できてしまう。
受け取るのは GitHub Pages が返す torabit.dev への転送だけでページ本体ではないが、IPv4 で応答する入口が増えてしまう。
www に AAAA レコードを直接書けば、DNS の上では www も IPv6 only にできる。
ただ、GitHub Pages のドキュメントが案内しているのは CNAME だけで、この形で torabit.dev への転送が働くかは確かめていない。
入口は torabit.dev 1 つで足りるので、www 自体を用意していない。
入口を増やさなくても、IPv4 で応答する名前は 1 つ残っている。
torabit.github.io だ。
独自ドメインを設定した時点で、torabit.github.io は https://torabit.dev/ へ 301 で転送するようになる。
古いリンクや検索結果から、IPv4 の人がここに来ることはありうる。
$ curl -4 -sSI https://torabit.github.io/ | grep -i -E '^HTTP|^location'
HTTP/2 301
location: https://torabit.dev/
IPv4 で来た人も、この 301 までは受け取る。 しかし転送先の名前が引けないので、ページ本体には届かない。 ここは許容している。 この 301 は GitHub Pages が返すもので、自分では止められないからだ。
Certificate
HTTPS で接続するとき、ブラウザはサーバに 証明書 を求める。 証明書は、接続先が本当に torabit.dev であることを認証局(CA)が保証するもので、これがないとブラウザは通信を始めない。
torabit.dev の証明書は、GitHub Pages が Let’s Encrypt から取得している。 Let’s Encrypt の証明書は 90 日で有効期限が切れるので、GitHub Pages が期限の前に自動で差し替える。
$ openssl s_client -connect torabit.dev:443 -servername torabit.dev </dev/null 2>/dev/null | openssl x509 -noout -issuer -enddate
issuer=C=US, O=Let's Encrypt, CN=YR2
notAfter=Jan 15 12:36:20 2027 GMT
Let’s Encrypt は、ドメインの持ち主であることを確かめてから証明書を出す。
GitHub Pages は、HTTP で確かめる方式を使っている模様だ。
この方式では、GitHub Pages が http://torabit.dev/.well-known/acme-challenge/ に合言葉を置き、Let’s Encrypt が :80 に平文でそれを取りに来る5。
torabit.dev には AAAA レコードしかないので、この確認は IPv6 で行われることになる。
実際、A レコードがなくても証明書は発行された。
ここは作る前に一番不安だったところで、ダメなら VPS を借りて自前で配信するつもりだった。
no External Origin
ページは index.html とアバター画像だけでできていて、外部のオリジンを一切参照しない。
CDN の JS も Web フォントも読まない。
これはシンプルさのためでもあるが、IPv6 only の都合でもある。
GitHub のアバターは https://github.com/<user>.png で手軽に貼れる。
このアドレスを開くと、ブラウザはまず github.com に接続し、そこから avatars.githubusercontent.com へ転送される。
転送先には AAAA レコードがあるが、最初の github.com には AAAA レコードがない。
$ dig +short AAAA github.com
$ dig +short AAAA avatars.githubusercontent.com
2606:50c0:8001::154
2606:50c0:8002::154
2606:50c0:8003::154
2606:50c0:8000::154
IPv6 しか持たない端末は、最初の github.com に接続できず、画像を表示できない6。
影響を受ける環境は限られるが、サイトを IPv6 only にするなら、読み込むものも IPv6 で届くものにそろえておきたい。
そこでアバターはダウンロードしてリポジトリに置き、同じオリジンから配信している。
Verification
IPv6 と IPv4 のそれぞれから叩く。
$ curl -6 -sSI https://torabit.dev/ | head -1
HTTP/2 200
$ curl -4 -sSI https://torabit.dev/
curl: (6) Could not resolve host: torabit.dev
IPv6 では 200 が返り、IPv4 では名前解決の段階で止まる。 この非対称がそのまま torabit.dev の仕様になる。
Outro
torabit.dev は、DNS の段階で IPv4 を断っている。
ただ、GitHub Pages に配信を任せている以上、torabit.github.io が IPv4 に返す 301 は残る。
完全に IPv4 を断つなら、自分でサーバを持って管理する必要がある。
それでも、無料で、自分でサーバを管理せずに、ここまではできた。 プロフィールという、誰にでも見てほしいはずのページを、あえて一部の人には見えないようにしたのは、実験のためだ。 自分のドメインを実験場として持っていると、仕事では決してやらない構成を気軽に試せる。 IPv6 only はその第一弾で、作ってみるとドキュメントからは分からないことがいくつも出てきた。
- プロキシを使っていないのに、Cloudflare が証明書を取っていたらしい
- GitHub Pages の設定画面が、今も「設定が間違っている」と言い続けている
- CNAME flattening を
torabit.github.ioに向けると、何が返るのか
次の記事では、これらについて分かったことと、まだ分かっていないことを書く。
Footnotes
-
クローラーだけを User-Agent で見分けて通す手もある。ただ、それは IPv6 only とは別のものになる。 ↩
-
このアドレスは anycast で運用されている。anycast は、同じ IP アドレスを世界中の複数の拠点から同時に経路広告する方式だ。どの国から同じアドレスに接続しても、パケットはネットワーク上で一番近い拠点に届く。Cloudflare は anycast で利用者を近い拠点に集め、そこでキャッシュを返したり Workers を動かしたりしている。 ↩
-
設定画面には「IPv6 互換性」という項目もある。しかしこれは求めているのとは逆で、A ではなく AAAA レコードを返すかどうかを切り替えるものだ。しかも、それを変更できるのは Enterprise プランだけだ。 ↩
-
CNAME を置いた名前にほかのレコードもあると、手元のレコードと指した先のレコードのどちらが正しいかが決まらない。そのため、CNAME はほかのレコードと同じ名前に置けない。zone apex には SOA や NS が必ずあるので、CNAME を置く余地がない。 ↩
-
:80を開けたままにしている理由と、それでも平文の HTTP で中身を返さない仕組みは、torabit.dev の HTTPS only と Domain Protection に書いた。 ↩ -
IPv6 だけのモバイル回線の多くは、NAT64 などの変換で IPv4 のサイトにも届くようにしている。表示できないのは、そうした変換のない、純粋な IPv6 only の環境に限られる。 ↩