blog
開発メモ反映したはずなのに、本番が変わらない——確かめ方を間違えた3つの現場(Shopify・FTPS・DNS移行)
受託の現場で、形の違う同じ失敗を3回踏みました。本番に反映したはずなのに変わっていない。あるいは、切り替えたはずの古いものが消えていない。
どれも、調べてみるとデプロイそのものは失敗していませんでした。失敗していたのは、反映できたかどうかを確かめる、そのやり方のほうです。
先に結論から
3つの現場から持ち帰った勘どころは、次の4つに尽きます。
- 転送ツールが出す「成功」は、中身が届いた証明ではない。送った後にもう一度取ってきて、ハッシュ値で突き合わせるまで確認は終わらない。
- 確認に使った経路が、実態を映しているとは限らない。 コマンドで取得した結果とブラウザで見た画面は、別のものを見ていることがある。
- 「切り替えた」は「消えた」ではない。ドメインの向き先を変えても、サーバーの中身は1バイトも減らない。
- 食い違いが出ても即断しない。改行コードのような表面的な差で「中身が違う」と誤検知することがある。
その1:転送は「完了」と言うが、中身が届いた証明ではない——FTPSデプロイと往復のハッシュ照合
ある案件の本番サーバーは、SSHが全ポート閉じられていて、ファイルを反映する手段がFTPS(通信を暗号化したFTP)しか残っていませんでした。素の平文FTPは接続した直後に拒否されます。
FTPSでファイルを送ると、226 Transfer complete という応答が返ってきます。いかにも「送れた」と読めますが、これはFTPという通信規約が定めた応答で、意味は「データ用の接続を閉じました。要求されたファイル操作は成功しました」です(RFC 959 の応答コード一覧に、この定義がそのまま載っています)。
つまり手続きが最後まで走ったことは言っていても、送ったファイルと届いたファイルが1バイトも違わないことまでは言っていません。テキスト用とバイナリ用の転送モードを取り違えれば改行コードが書き換わりますし、途中で切れた部分転送でも応答だけは正常に見えることがあります。
そこで、送った後に同じファイルをもう一度ダウンロードし直して、手元の送信元とSHA256(ファイルの中身から計算する指紋のような値)を突き合わせるようにしました。往復させて指紋が一致すれば、転送モードの事故も途中切れも無い「完全に同じもの」だと確定できます。一致しなければ、転送設定を疑う合図になります。
ドリフトを見るときは、手元の作業中ファイルではなく直近のコミットと比べる
もう一つ、上書き事故を避けるために見ているものがあります。本番は、管理画面からの緊急修正や別の担当者の直編集で、手元より先に進んでいることがあります。これを「ドリフト(ズレ)」と呼んでいます。
ここで本番と手元の作業中ファイルを比べてはいけません。自分の書きかけの編集まで差分に混ざって、どこからが本番側の変更なのか判別できなくなるからです。比べる相手はコミット済みの内容(git HEAD)にします。
git show HEAD:<path> | sha256sum
# 一致 → 本番=直近コミット。差分は自分の未コミット編集だけ → そのまま送って安全
# 不一致 → 本番側に自分の知らない変更が乗っている → 先に取り込んでコミット
その2:確認に使った経路が、実態を映していなかった——Shopifyのキャッシュとtheme pullでの直接確認
別の案件、Shopifyのストアです。トップページに新しいセクションを追加して本番テーマへ反映し、動作確認のつもりでコマンドラインからHTMLを取得したところ、何度取り直しても新しいセクションが出てきません。表示されるのは反映前のままでした。
まず疑ったのは反映そのものの失敗です。これは本番テーマのファイルを直接ダウンロードすれば切り分けられます。
shopify theme pull –theme <live_id> –only templates/index.json
取ってきた中身は最新でした。反映は成功していたのです。ではなぜ、同じURLを取得すると古いものが返るのか。容疑者を一つずつ消していきました。
- 前段のCDN(Cloudflare):応答ヘッダーの
cf-cache-statusがDYNAMICでした。この値は「そのリクエストの時点で、キャッシュの対象外だとCloudflareが判断した場合にだけ返る」と公式ドキュメントに書かれています(Cloudflare Docs: Cache responses)。つまりここは素通ししている=無罪。 - 管理画面のプレビュー用パラメータ:これを付けても効きませんでした。管理画面のログイン状態が前提の仕組みで、認証情報を持たない素の取得は通常の配信経路に落ちるようでした。
手詰まりになりかけたところで、念のため普通のブラウザで同じURLを開いたら、最新のセクションが即座に表示されました。
正直に言うと、コマンドでの取得とブラウザでここまで見え方が分かれた仕組みは、いまも特定できていません。ブラウザと違ってCookieやセッション情報を送らないことが、どこかの層の判定に影響した可能性はありますが、断定はできません。
ここで押さえたいのは原因ではなく順序のほうです。「サーバー側の実データが正しいか」と「特定の方法で取得したときの見え方」は別問題で、後者だけを見て「反映されていない」と判断するのは早合点になりえます。以来、見た目の確認はまずブラウザで行い、機械的に確かめたいときは配信経路を通らない直接取得(theme pull)を使うようにしました。
なお、切り分けのために取得を繰り返していたら途中で 503 が返りました。本番URLへの連打は、検証目的であっても負荷やボット判定のリスクがあります。数回で切り上げるのが無難です。
ついでの落とし穴:警告は出ているのに「成功」で終わる
同じ作業で、もう一つ踏みました。Shopifyのコマンドは --path . を付けると実行時のカレントディレクトリを基準に相対パスを解決します。テーマのフォルダより一つ上で実行すると、ファイルを絞り込む指定が対象を見つけられず、何も送っていないのに成功メッセージで終わります。
It doesn’t seem like you’re running this command in a theme directory.
→ 警告は出る。しかしエラー扱いにはならず、成功メッセージで終わる
ログをざっと眺めただけでは気づけません。対処は単純で、必ずテーマのフォルダ直下へ移動してから実行することです。
その3:「切り替えた」は「消えた」ではない——DNS移行後も生きていた旧サーバー
3つ目は、外部からの指摘で発覚しました。「このドメインに古いWordPressが置いてある」という内容です。こちらの認識では、そのドメインはすでにDNSを切り替えて別のサービスを向いており、旧サーバーのファイルは使っていないはずでした。
調べた結果、どちらも正しいという結論になりました。DNSは確かに移行先を向いていて、そのドメイン経由では旧サーバーに一切届きません。ところが旧サーバーのファイルは、別の入口から外部に公開され続けていたのです。
正体は「初期ドメイン」でした。共用レンタルサーバーには、独自ドメインとは別に契約時から割り当てられるアカウント名.事業者ドメイン形式のURLがあります。このサーバーでは、その初期ドメインの公開フォルダが、複数の独自ドメインをまとめて置いているルートそのものに設定されていました。
https://<本番ドメイン>/<旧フォルダ>/wp/readme.html → 404(公開フォルダが別なので無関係)
「独自ドメインが別のサービスを向いている=そのサーバーのファイルは外から見えない」という思い込みが、この裏口を見えなくしていました。ドメインの向き先を変えても、サーバー上のファイルは1バイトも消えません。
危険度の見積もりを2桁変える一手
露出を確認したら、次にPHPが実行されているのか、ソースコードが平文で漏れているのかを分けます。ここを取り違えると、危険度の見積もりが桁違いになります。
# 0 → PHPとして実行されている(ソース漏洩ではない)
# ソースが返る → 重大事故。設定ファイルからDBの認証情報が抜かれうる
このファイルは変数を定義するだけで画面には何も出力しません。だから0バイトの200が返れば実行されている証拠になります。今回は0バイトで、設定ファイルへのアクセスもエラー画面が返り、認証情報の漏洩は無いと確定できました。
ちなみに、WordPress本体はエラーで動いていませんでした。サーバーのPHPが新しくなって非対応になっていたためです。「動いていないなら安全」と片付けたくなりますが、これは危うい判断でした。停止は意図した措置ではなく偶然の副作用で、設定が戻れば無防備なまま起動します。そして見えること自体が的になります——脆弱性を探して回る機械に「古いWordPressあり」と拾われれば、攻撃対象の一覧に載ります。今回の外部指摘も、おそらくその経路です。
消すのではなく、公開領域の外へどける
削除ではなく退避を選びました。外から到達できなくなる効果は同じで、しかも判断を誤っていても戻せるからです。外部からの指摘に急かされているときほど、取り返しのつく手を選びたいところでした。同じファイルシステム内の移動なので一瞬で終わり、コピー途中の中途半端な状態も生まれません。
実行前に副作用も確認しています。旧フォルダの設定ファイルには別ドメインへの転送設定が40行以上あり、「これを消したら旧URLからの導線が切れる」と怯みそうになります。ですがDNSが移行先を向いた時点で、この設定には誰も到達していません。すでに死んでいた設定でした。「消えると困りそう」と感じたときは、そこに本当にリクエストが届いているかを先に確かめると、迷わず判断できます。
食い違いが出ても、即断しない
最後に、逆方向の失敗も書いておきます。「違う」と出たのに、実は同じだったケースです。
FTPSの案件で、上書きしようとした3ファイルがすべて直近コミットと指紋不一致になりました。「本番だけが先に進んでいるのか」と身構えましたが、改行コードを無視して中身を比べると完全一致。差はCRLF(本番)とLF(コミット側)だけでした。決め手は、増えていたバイト数が各ファイルの行数とちょうど一致していたことです。
指紋は改行コードの違いにも敏感に反応します。不一致が出た時点で「ドリフトあり」と機械的に決めつけず、改行を正規化した比較でもう一段掘る。中身の差なのか改行の差なのかを分けて初めて、上書きしてよいかを判断できます。
症状から引ける切り分け表
3つの現場で踏んだものを、症状から引ける形にまとめました。共通しているのは、最初に疑いたくなるものと、実際の犯人がずれていることです。
| 症状 | まず疑いたくなるもの | 実際に確かめること |
|---|---|---|
| 送ったのに中身が違う | 転送の失敗 | 再取得して指紋を照合する(完了応答は根拠にならない) |
| 取得すると古いまま | キャッシュ | ブラウザでも開く。配信を通さない直接取得でサーバー側の実データを見る |
| 指紋が一致しない | 本番が先に進んでいる | 改行を正規化して比べ直す(CRLFとLFの差かもしれない) |
| 絞り込み指定が効かない | 指定の書き間違い | 実行したディレクトリ(警告は出るが成功扱いで終わる) |
| 移行したのに旧環境が見える | DNSの設定漏れ | 初期ドメインなど、別経路から叩いてみる |
この確かめ方が効かない場面
正直に、限界も書いておきます。ここに挙げた手順は万能ではありません。
- 配信の途中で中身が加工される場合、指紋の照合は使えません。 画像を自動で最適化するCDNや、HTMLを圧縮して返す設定が挟まっていると、送ったものと返ってくるものは正当に食い違います。この場合はサーバー上のファイルを直接取得する経路が別途必要です。
- SaaS型のサービスでは、そもそもファイルの実体に触れません。 Shopifyのケースで指紋照合を使わなかったのはこのためで、代わりにテーマの実ファイルを取得するコマンドで代替しました。触れる層が違えば、確かめ方も変わります。
- 初期ドメインの構成は、事業者と契約によって違います。 「このサーバーでは公開フォルダがルートそのものだった」というだけで、すべての共用サーバーが同じ構造とは限りません。自分の環境で一度叩いて確かめる以外に近道はありません。
- そして、その2の根本原因はいまも分かっていません。 取得方法によって見え方が変わる仕組みは特定できておらず、書けるのは「別経路でも見て突き合わせる」という対処までです。
ここまで読んで浮かぶであろう疑問に
「キャッシュを消せば済む話では?」
今回はキャッシュが犯人ではありませんでした。反映は成功しており、前段のCDNも素通ししていた。消しても直りません。むしろ「キャッシュだろう」で止まったことが、原因究明を遅らせました。
「ブラウザで見れば十分では?」
目視で済むならそれが一番早い、というのが正直なところです。問題になるのは自動で検証したいときで、その場合に何を根拠にするかがこの記事の主題です。
「毎回ハッシュを取るのは面倒では?」
手作業なら面倒です。ただ、送信・再取得・照合は1つのコマンド列に畳めます。往復で数秒、上書き事故の1回分と比べれば安いものでした。
現場で使っているチェック
- 送ったら、取り戻して指紋を照合する。 転送ツールの成功表示で終わらせない。
- ドリフトは作業中ファイルではなく直近コミットと比べる。 自分の書きかけを混ぜない。
- 「反映されていない」と判断する前に、別の経路でも見る。 ブラウザ、配信を通さない直接取得、の2つを持っておく。
- 本番URLへの取得の連打は控える。 検証目的でもレート制限に当たる。
- 移行の完了条件に「旧環境の処遇」を必ず入れる。 DNS切替は完了ではなく、旧環境が孤児になる開始点。
- 公開フォルダに何か置いたら、初期ドメイン経由でも叩いてみる。 裏口が開いていないか。
- 危険な後始末は、削除でなく退避から入る。 効果が同じなら、戻せるほうを選ぶ。
共通していたのは、確かめ方が実データに届いていなかったこと
3つとも、デプロイの手順そのものは正しく動いていました。狂っていたのは、それを確かめる側です。転送ツールの応答、特定の方法で取得した結果、DNSの向き先——どれも実データの手前で止まっている情報でした。
この構図は、AIの「公開しました」という報告を信じたら本番には何も反映されていなかった話と根が同じです。あちらは報告そのものが実態とズレていましたが、今回は自分たちの確認手順が実態に届いていませんでした。報告であれ応答コードであれ、実物を見ていない情報は、実物の証明にならないという一点に尽きます。
そして最後の退避の判断は、戻せる作業は任せ、本番反映は自分で止めるで書いた「元に戻せるか」という物差しと同じ軸でした。効果が変わらないなら、可逆なほうを選ぶ。ここまでに踏んだものを一通り整理したClaude Code実務運用ガイドもあわせてどうぞ。
※本記事の内容は2026年8月時点のものです。


