md サブドメインの DNS と Bootstrap installer

md サブドメイン (md.example.com 等) を実際にブラウザから到達可能にするための DNS 設定と、md ホスト用 index.php を管理画面から自動生成する Bootstrap installer の使い方を、サーバ環境別に詳細解説します。

md サブドメインとは

本プラグインの中核コンセプトは「メインサイト (www.example.com) の HTML 出力には一切手を入れず、専用サブドメイン (md.example.com) で AI クローラ向けの Markdown 並行版を配信する」ことです。詳細は 基本概念 を参照してください。本ページではサーバ側のセットアップに集中します。

md サブドメインの概念図
図: メインサイト (HTML) と md サブドメイン (Markdown) の並行配信。同じ WordPress インスタンスから 2 種類の出力を行う。

md サブドメインの 3 つの構成要素

このうち 3 つ目の「index.php 配置」を管理画面から自動化するのが Bootstrap installer です。SSH/FTP が使えない環境でも、ボタン 1 つで md ホストを動かせます。

DNS 設定の基本

DNS レジストラ (お名前.com / VALUE-DOMAIN / Cloudflare / Route 53 等) の管理画面で、md サブドメイン用のレコードを追加します。サーバ構成によって A レコードか CNAME レコードかが変わります。

A レコードの場合

サーバが固定 IP を持つ場合 (専用サーバ / VPS / 一部の共有レンタルサーバ) は A レコードを使います。メインサイトの A レコードと同じ IP を指す形が一般的です。

TypeName (Host)ValueTTL
Amd192.0.2.103600
A (参考: メイン)www192.0.2.103600
A (参考: ルート)@192.0.2.103600

CNAME レコードの場合

マネージド PaaS (Heroku / WP Engine / Kinsta 等) ではホスト名 (例: your-site.kinsta.cloud) を割り当てられることが多く、IP が動的に変わるため CNAME を使います。

TypeName (Host)ValueTTL
CNAMEmdyour-site.kinsta.cloud.3600

📝 Note

CNAME の Value は末尾にドット (.) を付けるのが正確 (FQDN) ですが、多くの DNS UI は省略しても自動で補ってくれます。

Cloudflare 等の CDN 経由

Cloudflare をフロントに置く場合、DNS レコードはオレンジ雲 (Proxy ON) にすることで CDN を経由します。Universal SSL も自動発行されるため、別途 Let's Encrypt 等を用意する必要はありません (詳しくは下記「SSL 証明書の取り扱い」を参照)。

TypeNameValueProxy
Amd192.0.2.10Proxied (オレンジ雲)

⚠️ Warning

Cloudflare の WAF / Bot Fight Mode が AI クローラを過剰にブロックする場合があります。md サブドメインに対して Page Rule や Configuration Rule で「Bot Fight Mode を OFF」「WAF Sensitivity を Low」にしておくと、ChatGPT / Claude / Perplexity / Gemini からのアクセスが安定します。

SSL 証明書の取り扱い

md サブドメインも HTTPS で配信するのが原則です。本プラグインの Bootstrap installer の verify 機能 (ksmd_bootstrap_verify()) は home_url() の scheme を継承するため、メインサイトが HTTPS なら md サブドメインも HTTPS で疎通確認します。

Let's Encrypt のワイルドカード証明書

もっとも一般的な選択肢です。*.example.com 用のワイルドカード証明書を取得しておけば、md / blog / app 等の任意のサブドメインがすべて 1 枚でカバーされます。

# certbot DNS-01 challenge の例 (DNS API 連携必須)
sudo certbot certonly --manual --preferred-challenges=dns \
  -d 'example.com' -d '*.example.com' \
  --agree-tos --email admin@example.com

Cloudflare Universal SSL

Cloudflare 経由 (オレンジ雲) なら、サブドメイン分も含めて Universal SSL が自動発行されます。設定は不要です。Cloudflare ダッシュボードの「SSL/TLS → Edge Certificates」で発行状況を確認できます。

サブドメイン単位の証明書

「md サブドメインのみ」を別途発行することもできます。Apache/Nginx の VirtualHost 設定で md サブドメイン用の証明書ファイルパスを指定します。

⚠️ Warning

md ホストでも SSL は必須です。HSTS を発行しているメインドメインのサブドメインに HTTP でアクセスするとブラウザがブロックすることがあります。また AI クローラは https:// URL を優先するため、md サブドメインが HTTP のみだと参照されにくくなります。

md ホスト用ドキュメントルート

DNS と SSL が揃ったら、Web サーバ (Nginx / Apache / コントロールパネル) で md サブドメインの「ドキュメントルート」を決めます。Bootstrap installer がサポートするのは以下の 2 構成のみです (includes/md-bootstrap-installer.php:108)。

パターン A: Xserver 等の ABSPATH 直下構成 (subdir)

Xserver の標準は WP 本体が /home/user/example.com/public_html/ にあり、サブドメインは「サブドメイン作成」機能で /home/user/example.com/public_html/md/ のようにディレクトリを切るタイプです。

/home/user/example.com/public_html/        # ABSPATH (= WP 本体)
├── wp-config.php
├── wp-blog-header.php
├── wp-admin/
├── wp-content/
├── wp-includes/
└── md.example.com/                          # md サブドメイン用 DocumentRoot (subdir)
    └── index.php                             # ← Bootstrap installer が生成

この構成では、md.example.com ホストへのリクエストは md.example.com/index.php を実行し、その中で __DIR__ . "/../wp-blog-header.php" = /home/user/example.com/public_html/wp-blog-header.phprequire することで WordPress を起動します。

パターン B: Bedrock 流の兄弟構成 (sibling)

Bedrock や WP-CLI 標準の「web/wp/ に WP 本体、web/ をドキュメントルートに」とする構成では、web/ の直下に md ディレクトリを切る形になります。Bootstrap installer の sibling パターンが該当します。

/srv/example.com/web/                       # 親ディレクトリ
├── wp/                                      # ABSPATH (= WP 本体)
│   ├── wp-config.php
│   ├── wp-blog-header.php
│   ├── wp-admin/
│   ├── wp-content/
│   └── wp-includes/
└── md/                                      # md サブドメイン用 DocumentRoot (sibling)
    └── index.php                             # ← Bootstrap installer が生成

生成される index.php の require は __DIR__ . "/../wp/wp-blog-header.php" となり、basename(ABSPATH) を埋め込みます。

⚠️ Warning

Bedrock 完全版 (= composer.json + web/app/ 構成) や、ABSPATH の孫以下に md を置く構成は Bootstrap installer の対象外です。手動で SSH 経由で index.php を配置してください。判定は ksmd_bootstrap_validate_path() の gate #4-#5 で行われ、不適合なら拒否されます。

パターン C: 標準 WP の別ドキュメントルート

VirtualHost を完全に分離して、たとえば /var/www/md.example.com/ のような別パスをドキュメントルートにする構成は Bootstrap installer の対象外です。realpath() で解決した md ディレクトリの親が ABSPATH か ABSPATH の親と一致しないため、validate_path() 内で gate #4-#5 が失敗します。

// includes/md-bootstrap-installer.php より
if ( $md_parent === $abs_real_norm ) {
    // ABSPATH 直下サブディレクトリ構成 (XServer 標準)
    $layout_type = 'subdir';
    // ...
} elseif ( $md_parent === $abs_parent ) {
    // 兄弟構成 (target-v3 互換)
    $layout_type = 'sibling';
} else {
    // ABSPATH の孫以下 / 非関連パス (Bedrock 等) / その他 = 拒否
    $result['errors'][] = sprintf(
        __( 'md ドキュメントルートが WP 本体の兄弟ディレクトリでも ABSPATH 直下サブディレクトリでもありません ...', ... ),
        ...
    );
    return $result;
}

このような構成では SSH 経由で手動配置することになります。詳しくは「インストールされる index.php の中身」セクションを参照してください。

Bootstrap installer とは

md ホスト用の index.php (= WP の入口となる 3 行 PHP) を、管理画面から 1 クリックで配置・削除・復元できる機能です。includes/md-bootstrap-installer.php に実装されており、診断タブの下部に UI が出ます。

診断タブの md ホストブートストラップ
診断タブの「md ホスト ブートストラップ」セクション。WP 本体パス (read-only)、md 側 DocumentRoot 入力、状態カード、生成 / 削除 / 復元ボタンが揃う。

解決する問題

解決しない問題 (= 範囲外)

Bootstrap installer の使い方

Bootstrap installer の使用フローは以下の 4 ステップです。診断タブの下部「md ホスト ブートストラップ」セクションで操作します。

md ホストブートストラップの詳細 UI
md 側ドキュメントルートの入力 → パス保存 → 状態カード → 生成 / 削除 / 復元のボタン群。

ステップ 1: md ホストパスを入力する

1

「md 側ドキュメントルート」入力欄に、md サブドメインの絶対パスを入力します (例: /home/user/public_html/md.example.com)。

<input type="text" name="ksmd_bootstrap_path"
       placeholder="/home/user/public_html/md.example.com"
       class="regular-text" style="width:30em;" />

ステップ 2: パスを保存する

2

「パスを保存」ボタンをクリックします。ksmd_handle_bootstrap_save_path アクションが起動し、入力値は ksmd_sanitize_bootstrap_path() で sanitize された後 ksmd_settings.bootstrap_path に保存されます。

function ksmd_sanitize_bootstrap_path( $raw ) {
    $p = sanitize_text_field( (string) $raw );
    $p = trim( $p );
    if ( $p === '' ) {
        return '';
    }
    // 絶対パスのみ許可
    if ( substr( $p, 0, 1 ) !== '/' ) {
        return '';
    }
    // 末尾スラッシュ除去
    $p = rtrim( $p, '/\\' );
    return $p;
}

ステップ 3: 「状態を再確認」で 8 段検証を走らせる

3

パス保存後、状態カードに「index.php の存在 / md ホスト動作確認 (verify)」が表示されます。「状態を再確認」ボタンで再評価できます。内部では ksmd_bootstrap_check_status()ksmd_bootstrap_validate_path() を呼び、以下の 8 段チェックを行います。

  • gate #1: 絶対パス + realpath() 解決
  • gate #2: ディレクトリ存在
  • gate #3: 書き込み可能 (静的判定)
  • gate #4-a: ABSPATH 自身を md として指定していないこと
  • gate #4-b: WP コアディレクトリ (wp-admin / wp-includes / wp-content) と一致しないこと (case-insensitive)
  • gate #5: 兄弟構成 OR ABSPATH 直下サブディレクトリ構成のいずれか
  • gate #6: 親ディレクトリで wp-blog-header.php 到達確認
  • gate #7: 既存 index.php の安全性 (本プラグイン生成物のみ上書き許可)
  • gate #8: probe ファイル書き込みテスト (open_basedir / SELinux 等の実環境差吸収)

ステップ 4: 「index.php を生成」を実行

4

すべての gate を通過したら、「index.php を生成」ボタンをクリックします。ksmd_handle_bootstrap_install アクションが ksmd_bootstrap_install() を呼び、以下の通り atomic に書き込みます。

  • tempnam() で同一ディレクトリ内に一時ファイル作成
  • テンプレート文字列を一時ファイルに file_put_contents()
  • rename 直前に realpath() 再検証 (TOCTOU 二重ガード、F1)
  • rename($tmp, $target) で atomic 置換
  • rename 後の is_link チェック + realpath() 再検証 (TOCTOU race 対策)
  • chmod( $target, 0644 ) (F10)
  • hash_file('sha256', $target)ksmd_bootstrap_meta に保存 (F4)

ステップ 5: verify で疎通確認

5

状態カードに「md ホスト動作確認 (verify)」の結果が表示されます。ksmd_bootstrap_verify($md_host)https://md.example.com/ に対して HTTP リクエストを送り、以下の条件を確認します。

  • HTTP 200
  • Content-Type に text/markdown が含まれる
  • X-Ksmd-Cache ヘッダが付いている

3 つすべて満たせば SUCCESS。SSL エラーは PENDING (Let's Encrypt プロビジョン待ち)、403/520 系は FAILED (cf カテゴリ)、その他は FAILED (http カテゴリ) として表示されます。

インストールされる index.php の中身

Bootstrap installer が配置する index.php はテンプレートとして ksmd_bootstrap_generate_template() 関数で生成されます (includes/md-bootstrap-installer.php)。

テンプレート (subdir 構成)

<?php
/**
 * KSMD-BOOTSTRAP-MARKER v1
 * Generated by wp-plugin-kashiwazaki-llmo-md-subdomain on 2025-04-30T12:00:00Z
 * Do not edit manually. Use plugin admin UI to regenerate or remove.
 */
define( "WP_USE_THEMES", true );
require __DIR__ . "/../wp-blog-header.php";

テンプレート (sibling 構成)

<?php
/**
 * KSMD-BOOTSTRAP-MARKER v1
 * Generated by wp-plugin-kashiwazaki-llmo-md-subdomain on 2025-04-30T12:00:00Z
 * Do not edit manually. Use plugin admin UI to regenerate or remove.
 */
define( "WP_USE_THEMES", true );
require __DIR__ . "/../wp/wp-blog-header.php";

違いは require 行の path だけです。sibling 構成では basename(ABSPATH) (例: wp) を埋め込み、subdir 構成では __DIR__ . "/../wp-blog-header.php" を直接呼びます (ksmd_bootstrap_generate_template():340)。

なぜこれで動くのか

  1. md.example.com へのリクエストが Web サーバ経由で /md.example.com/index.php を実行する
  2. require で WP の入口 wp-blog-header.php が読み込まれる
  3. wp-blog-header.phpwp-load.php 経由で wp-config.php を読み、WordPress を完全初期化する
  4. 初期化の途中、template_redirect アクションが priority -1 で発火する
  5. 本プラグインの ksmd_handle_subdomain_request()$_SERVER['HTTP_HOST'] を見て md ホストと判定 → Markdown を返してそのまま exit

つまり「index.php は単なる WordPress 入口であり、md/seo の判定はプラグインが行う」というアーキテクチャです。詳しくは アーキテクチャ ページを参照してください。

WP_USE_THEMEStrue の理由

テンプレートでは define( "WP_USE_THEMES", true ); としています。これは「WP の通常の template_redirect ライフサイクルを通す」という意味であり、本プラグインの template_redirect priority -1 フック発火を保証するために必要です。false にしてしまうと template-loader.php がスキップされ、本プラグインの介入機会も失われます。

Bootstrap installer 復元 / アンインストール

生成した index.php は管理画面から削除と復元ができます。両者の使い分けを把握しておきましょう。

「index.php を削除」(uninstall)

診断タブの「index.php を削除」ボタンで ksmd_handle_bootstrap_uninstall が呼ばれ、内部で ksmd_bootstrap_uninstall() が動作します。動作内容は次の通りです。

function ksmd_bootstrap_uninstall( $verified_dir ) {
    // ...
    // 削除前 backup を保存 (24h TTL)
    $backup_content = @file_get_contents( $target );
    if ( $backup_content !== false ) {
        update_option( KSMD_BOOTSTRAP_BACKUP_OPTION, array(
            'content'    => $backup_content,
            'sha256'     => @hash_file( 'sha256', $target ),
            'target_dir' => $verified_dir,
            'removed_at' => time(),
        ) );
    }
    // unlink
    if ( ! @unlink( $target ) ) {
        // ...
    }
    delete_option( KSMD_BOOTSTRAP_META_OPTION );
}

「直前の削除を元に戻す」(restore)

削除後 24 時間以内なら、状態カードに「直前の削除を元に戻す (残り約 N 時間)」ボタンが表示されます。ksmd_handle_bootstrap_restoreksmd_bootstrap_restore() を呼び、ksmd_bootstrap_last_removed_backup オプションから内容を復元します。

使い分けの指針

状況使うアクション
md ホストを一時的に止めたい (DNS は残す)一般タブの kill switch
md ホストを完全に止めたい (404 でなく 403)診断タブの「index.php を削除」
誤って削除してしまったが 24h 以内状態カードの「直前の削除を元に戻す」
プラグイン本体を WordPress から削除する先に上記の「index.php を削除」 → プラグイン削除 (uninstall.php が動く)

Bootstrap installer のセキュリティ

Bootstrap installer は md ホストのドキュメントルートに PHP ファイルを書き込む機能なので、以下のセキュリティ対策が施されています (includes/md-bootstrap-installer.php)。

admin 経由のみ動作

機能ファイル md-bootstrap-installer.phpkashiwazaki-llmo-md-subdomain.phpksmd_bootstrap_plugin() 内で is_admin() ガード付きでのみ require_once されます。フロントエンドの opcode footprint を増やさないだけでなく、フロントエンドからは関数自体が見えないことになります。

if ( is_admin() ) {
    $admin = KSMD_PLUGIN_PATH . 'admin/admin-loader.php';
    if ( file_exists( $admin ) ) {
        require_once $admin;
    }

    // F9: md ホスト ブートストラップ機能は admin 限定で読み込む
    $bootstrap = KSMD_PLUGIN_PATH . 'includes/md-bootstrap-installer.php';
    if ( file_exists( $bootstrap ) ) {
        require_once $bootstrap;
    }
}

nonce 保護 (F7: action と 1:1 対応)

5 種のアクションすべてに wp_nonce_field() + check_admin_referer() が掛かっており、action 名と nonce action 名が 1:1 対応するよう揃えられています。

// admin/admin-loader.php
add_action( 'admin_post_ksmd_bootstrap_save_path', 'ksmd_handle_bootstrap_save_path' );
add_action( 'admin_post_ksmd_bootstrap_check',     'ksmd_handle_bootstrap_check' );
add_action( 'admin_post_ksmd_bootstrap_install',   'ksmd_handle_bootstrap_install' );
add_action( 'admin_post_ksmd_bootstrap_uninstall', 'ksmd_handle_bootstrap_uninstall' );
add_action( 'admin_post_ksmd_bootstrap_restore',   'ksmd_handle_bootstrap_restore' );

書き込み権限チェック (gate #3, gate #8)

パス検証では静的判定 (is_writable()) と動的判定 (probe ファイル書き込み) の両方を実施します。これは open_basedir 制約 / SELinux / chrooted PHP-FPM 等で is_writable() が真でも実際には書けない環境への対策です。

// gate #8: probe ファイル書き込みテスト
$probe_name = '.ksmd-write-probe-' . uniqid( '', true );
$probe_path = $real . '/' . $probe_name;
$probe_ok   = @file_put_contents( $probe_path, 'probe' );
if ( $probe_ok === false ) {
    $result['errors'][] = sprintf(
        __( 'probe ファイルの書き込みに失敗しました ...', 'kashiwazaki-llmo-md-subdomain' ),
        $real
    );
    return $result;
}
$unlink_ok = @unlink( $probe_path );
if ( ! $unlink_ok ) {
    @unlink( $probe_path );
    $result['errors'][] = sprintf(
        __( 'probe ファイルの削除に失敗しました ...', 'kashiwazaki-llmo-md-subdomain' ),
        $probe_name
    );
    return $result;
}

WP コアディレクトリ拒否 (gate #4-b)

md ディレクトリの basename が wp-admin / wp-includes / wp-content と一致 (case-insensitive) すると拒否します。WP コアの index.php を上書きする事故を防ぎます。

// gate #4-b: ABSPATH 直下の WP コア dir を md として指定するのは禁止
$core_dirs       = ksmd_bootstrap_wp_core_dirs(); // ["wp-admin","wp-includes","wp-content"]
$md_basename_lc  = strtolower( $md_basename );
$core_dirs_lc    = array_map( 'strtolower', $core_dirs );
if ( in_array( $md_basename_lc, $core_dirs_lc, true ) ) {
    $result['errors'][] = sprintf(
        __( '指定パスが WordPress コアディレクトリ (%2$s のいずれか) と一致しています: %1$s ...', ... ),
        $md_basename,
        implode( ' / ', $core_dirs )
    );
    return $result;
}

TOCTOU 対策 (F1)

rename 直前と直後の 2 回 realpath() を再検証し、間に攻撃者がディレクトリを symlink にすり替えたケースに対応します。tempnam() も「指定ディレクトリ内に作成されたか」を検証します。

verify は HMAC ヘッダ + sslverify フィルタ付き (F12 / F13)

verify では ksmd_generate_test_token('/') で HMAC-SHA256 トークンを生成し X-Ksmd-Test-Token ヘッダに乗せて送信します。受信側は core-functions.php:112 でトークンを検証してキャッシュ bypass。SSL 検証は ksmd_bootstrap_sslverify フィルタで上書き可能です。

$args = array(
    'timeout'     => 8,
    'redirection' => 0,
    'sslverify'   => apply_filters( 'ksmd_bootstrap_sslverify', true ), // F13
    'headers'     => $headers,
    'user-agent'  => 'KSMD-Bootstrap-Verify/' . KSMD_VERSION,
);

DNS 反映確認

DNS レコードを設定してから実際にブラウザから到達できるまでには TTL に応じた時間 (数分〜数時間) が必要です。コマンドラインで反映状況を確認できます。

dig コマンド (Linux / macOS)

dig md.example.com

# ;; ANSWER SECTION:
# md.example.com.    3600    IN    A    192.0.2.10

ANSWER SECTION に期待した IP が出ていれば反映済みです。反映前は ANSWER SECTION が空、もしくは古い値が返ります。

nslookup コマンド (Windows)

nslookup md.example.com

# Server:  192.168.1.1
# Address: 192.168.1.1#53
#
# Non-authoritative answer:
# Name:    md.example.com
# Address: 192.0.2.10

Cloudflare のキャッシュ

Cloudflare 経由 (オレンジ雲) の場合、Cloudflare のエッジキャッシュも考慮する必要があります。ダッシュボードの「Caching → Configuration → Purge Everything」でエッジキャッシュをパージできます。

TTL の注意点

よくある DNS トラブル

md サブドメイン到達時のトラブルでよくあるパターンを表でまとめます。

症状原因対処
DNS が反映されない TTL の残留キャッシュ / DNS API の遅延 dig md.example.com @8.8.8.8 で外部 DNS から確認 / TTL を待つ / OS の DNS キャッシュをクリア
SSL 証明書エラー (NET::ERR_CERT_COMMON_NAME_INVALID) md サブドメインが証明書に含まれていない Let's Encrypt ワイルドカード証明書を再発行 / Cloudflare Universal SSL を有効化
verify が PENDING (SSL category) Let's Encrypt のプロビジョン待ち (Cloudflare Universal SSL 含む) 5 分後に再試行 / Cloudflare のダッシュボードで「Edge Certificates」の発行状況を確認
verify が FAILED (cf category, 403/520) Cloudflare WAF / Bot Fight Mode によるブロック Cloudflare の「Configuration Rules」で md サブドメインの WAF Sensitivity を Low / Bot Fight Mode を OFF に
verify が FAILED (http category, 200 だが Content-Type 不一致) master switch (enabled) が OFF / プラグイン未有効化 一般タブで「プラグインを有効化」をオンにする
DNSSEC 競合 レジストラ側の DNSSEC が中途半端に設定されている レジストラ管理画面で DNSSEC を一旦 OFF → DNS 反映後に再度 ON に / DS レコードと KSK の整合を取り直す
md.example.com にアクセスすると www サイトが表示される md ホストの DocumentRoot 設定漏れ / Bootstrap installer 未実行 / Cloudflare Page Rule の干渉 Web サーバの VirtualHost / 仮想ホスト設定で md ホストの DocumentRoot を分離 / Bootstrap installer で index.php を生成 / Cloudflare の Page Rule を確認
nslookup で IP は引けるが ブラウザで「このサイトに到達できません」 サーバ側 VirtualHost に md サブドメインが登録されていない / FW が 80/443 を絞っている Web サーバ管理画面で md サブドメインを追加 / FW のインバウンド HTTPS を許可

💡 Tip

困ったときは診断タブの「md ホスト動作確認 (verify)」で category 列を見ます。cf なら Cloudflare 設定、ssl なら証明書、http ならプラグイン or サーバ設定の問題、と切り分けが可能です。実装は ksmd_bootstrap_verify() 内の $result['category']

まとめ

本ページでは md サブドメインを実際に到達可能にするための DNS / SSL / DocumentRoot / index.php について、Bootstrap installer の自動化機能と合わせて解説しました。

次は具体的な設定タブの詳細に進みます。最初は「一般タブ」です。