切り分けは範囲の確認から始める
「サイトが重い」は原因がひとつとは限らない。回線、名前解決、サーバーの応答、転送量のどれが詰まっても同じ症状になる。そのため、手間が少なく可能性の高い順に、影響範囲 → 表示時間の内訳 → サーバー内部 → 配信内容と絞り込む。順序を逆にすると、サーバーの設定を何時間もいじった末に「自分の回線だけが遅かった」と分かることになる。以下の手順はSSHで使える一般的なコマンドとブラウザの開発者ツールだけで進められる。
1. 遅いのは自分だけか、全員か
最初に確認するのは影響範囲。自分の環境だけの問題を、サーバーの問題と誤認するのが最も多い失敗になる。確認は3つで足りる。スマートフォンのWi-Fiを切ってモバイル回線で開く(別回線)、別の端末やブラウザで開く(別環境)、シークレットウィンドウで開く(拡張機能とキャッシュの排除)。さらに、PageSpeed Insights、GTmetrix、WebPageTestといった外部計測サービスで、自分のネットワークの外から測る。
自分だけ遅いなら、原因はローカル側にある。回線速度、DNS設定、ブラウザ拡張、セキュリティソフトのHTTPS検査、社内プロキシを順に疑う。1.1.1.1や8.8.8.8など別のDNSに切り替えて改善するなら名前解決の問題。全員が遅いなら次の段階へ進む。
この時点で範囲をもう少し絞っておくと後が早い。特定のページだけ遅いならアプリケーションの処理、全ページが一様に遅いなら基盤側。特定の時間帯だけ遅いなら、アクセス集中かバックアップ・cronとの重なりを疑う。
2. 表示時間の内訳を分解する
ブラウザの開発者ツール(多くはF12)のネットワークタブを開き、キャッシュ無効にチェックを入れて再読み込みする。一覧の一番上にあるHTML文書の行をクリックし、タイミング(Timing)の内訳を見る。ここで時間がどこに偏っているかによって、次に見る場所が決まる。
| 時間が偏っている箇所 | 意味 | 次に進む先 |
|---|---|---|
| DNS Lookup | ドメイン名からIPを引くのが遅い | DNSの設定、権威サーバーの応答 |
| Initial connection / SSL | TCP接続とTLSハンドシェイクが遅い | ネットワーク経路、証明書チェーン、HTTP/2の有無 |
| Waiting for server response (TTFB) | サーバーが最初の1バイトを返すまでが遅い | 本記事の3章(サーバー側) |
| Content Download、総転送量、リクエスト数 | 配信するデータが大きい・多い | 本記事の4章(配信側) |
同じ内訳はコマンドでも取れる。SSHや手元の端末から次を実行すると、各段階の累積秒数が並ぶ。
curl -o /dev/null -s -w 'dns:%{time_namelookup} tcp:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n' https://example.com/目安として、静的なページのTTFBは数十〜200ミリ秒程度、動的なページでも500ミリ秒を超えると体感で遅さが分かる。TTFBが十分短いのに表示が遅い場合、サーバーの増強では改善しない。原因は転送量かブラウザ側の描画にある。
3. TTFBが遅い場合:サーバー内部を上から見る
負荷の全体像
uptimeでload averageを見る。3つの数字は1分・5分・15分の平均で、CPUコア数(nprocで確認)を継続的に上回っていれば処理が待たされている。vmstat 1 5を実行し、r列(実行待ちプロセス数)、b列(I/O待ち)、CPUのus・sy・waを見る。usが高ければアプリの計算、waが高ければディスク待ち、syが高ければシステムコールや接続処理が疑わしい。
メモリとスワップ
free -hでavailableが極端に少なく、swapが使われているならメモリ不足。vmstatのsi・so列が継続的に0でない場合、スワップの読み書きが起きており、これは体感速度を大きく落とす。dmesg -T | grep -i oomでOut of Memoryによるプロセス強制終了の記録も確認する。MySQLやPHPのワーカーが落とされていれば、遅さではなくエラーとして表面化しているはず。
ディスクI/O
iostat -x 1 5(sysstatパッケージ)で%utilとawaitを見る。%utilが常時100%近く、awaitが数十ミリ秒以上ならディスクが飽和している。共用環境やHDDベースのVPSではここが原因になりやすい。あわせてdf -hで空き容量、df -iでinodeの空きも確認する。ディスクが満杯だとログもキャッシュも書けず、原因の分かりにくい遅さになる。
同時接続と処理の詰まり
ss -sで接続数の概況、ss -tan | awk '{print $1}' | sort | uniq -cで状態別の内訳を見る。ESTABLISHEDが想定より多ければアクセス集中、TIME-WAITが極端に多ければ接続の使い捨てが起きている。Webサーバーやアプリ側のワーカー上限に張り付いているとリソースに余裕があってもリクエストは待たされる。ApacheならMaxRequestWorkers、PHP-FPMならpm.max_childrenを確認し、エラーログに上限到達の警告が出ていないか見る。
どの層で待っているか
ここまででリソースに明確な異常がなければ、待ちの発生場所を層ごとに切り分ける。まず小さな静的ファイル(画像やテキスト)のTTFBをcurlで測り、動的ページと比べる。静的が速く動的だけ遅いなら、原因はWebサーバーではなくアプリケーションかデータベース。
データベースはスロークエリログで確認する。MySQL/MariaDBならslow_query_logを有効にしlong_query_timeを1秒程度に下げ、mysqldumpslowで集計する。遅い最中であればSHOW FULL PROCESSLIST;で、長時間走っているクエリやロック待ちがそのまま見える。アプリケーションが外部APIを呼んでいる場合は、その応答待ちがTTFBに丸ごと乗る。決済、SNS連携、地図、外部フォントの取得などが該当し、サーバーの負荷は低いままTTFBだけが延びるのが特徴になる。PHP-FPMのslowlogを有効にすると、時間を消費している関数まで特定できる。
4. 転送量が大きい場合:配信側を見直す
TTFBが速いのに表示が遅いなら、送っているデータが重い。開発者ツールのネットワークタブでサイズ順に並べ替え、上位を確認する。多くの場合は画像で、表示サイズより大幅に大きい原寸画像をそのまま置いているケースが目立つ。表示幅に合わせて縮小し、WebPやAVIFなどの形式を検討し、画面外の画像にはloading="lazy"を付ける。
圧縮の有無はcurl -I -H 'Accept-Encoding: gzip, br' https://example.com/のレスポンスにContent-Encodingが含まれるかで分かる。含まれなければHTML・CSS・JavaScriptが未圧縮で送られている。キャッシュはCache-Controlヘッダーで判断し、静的ファイルに長い有効期限が付いていなければ毎回転送が発生する。それでも重い場合、画像や静的ファイルの配信をCDNに逃がすと、サーバーの負荷と応答時間の両方が下がる。
解決しないときに揃えておく情報
自力で特定できない場合、ホスティング事業者のサポートに問い合わせることになる。そのとき「遅いです」だけでは調査が進まない。発生した日時(タイムゾーン付き)、対象URL、症状が出る頻度と再現手順、curlの計測結果、uptime・free -h・iostat -xの出力、該当時刻のアクセスログとエラーログの抜粋、外部計測サービスの結果を添える。共用サーバーで自分の環境に異常が見当たらないなら、同居する他利用者の影響やホスト側の障害の可能性も含めて確認を依頼する。
チューニングで済むか、増強が必要かの見分け
判断の基準は、遅さが常時なのかピーク時だけなのかにある。load averageがコア数を常時上回り、swapが恒常的に使われ、ディスクの%utilが高止まりしているなら、リソースが足りていない。プランの増強かサーバーの分割を検討する段階になる。一方、平常時は余裕があり特定時間帯だけ悪化するなら、増強しても費用が増えるだけで根本は変わらない。キャッシュの導入、スロークエリの改善、外部API呼び出しの非同期化のほうが効く。TTFBが速いのに遅いと感じられている場合は、サーバーには一切手を入れず、画像と配信方法だけを見直す。まずは本記事の1章と2章を実行し、時間がどこに偏っているかを数字で押さえてから、次に触る場所を決めてほしい。