ウェブサイトが「中途半端に読み込まれる」もどかしさ
新しいLinuxサーバーにWebアプリをデプロイしたばかりだとします。ローカルの1Gbps光回線では爆速で、ログも綺麗です。しかし、ユーザーが企業のVPNや遠隔地の支店から接続すると、状況は一変します。小さなページは瞬時に読み込まれますが、重いJavaScriptや高解像度のアセットが詰まったメインダッシュボードは、接続がタイムアウトするまで延々と読み込み中のままになります。
多くのチームが、これを解決するために週末を返上してSQLクエリをリファクタリングしたり、ロードバランサーのインスタンスをアップグレードしたりするのを見てきました。彼らはコードが遅いと思い込みがちですが, 問題は通常、ネットワークの配管部分の奥深くにあります。一部のユーザーには正常に動作するのに、他のユーザーでは固まってしまう場合、パケットサイズの不一致(ミスマッチ)を疑うべきです。
この挙動は、MTU(最大転送単位)の競合を示す典型的な兆候です。パケットがルーターで処理できないほど大きく、かつそのルーターがサーバーに制限を通知できない場合、データは単に消失します。これが恐ろしい「PMTUDブラックホール」です。以下に、その診断と修正方法を説明します。
基本:MTUとMSSの違い
配管を修理するには、その配管がどれだけの水を保持できるかを知る必要があります。データは特定の塊(チャンク)としてインターネット上を移動します。
MTU (Maximum Transmission Unit)
MTUは、インターフェースが処理できる最大のパケットサイズです。標準的なEthernetでは**1500バイト**のMTUを使用します。これにはIPヘッダー、TCPヘッダー、および実際のデータペイロードが含まれます。
トラフィックが制限された経路に当たると問題が発生します。例えば、WireGuard VPNは1420 MTUをよく使用し、一部のPPPoE DSL接続は1492が上限です。サーバーが1500バイトのパケットを1420バイトのトンネルに送信すると、パケットは断片化(フラグメンテーション)されるか、破棄される必要があります。
MSS (Maximum Segment Size)
MSSはTCPヘッダー内に存在します。これは、1つのセグメントで送信できる実際のデータ(ペイロード)の量を相手側に伝えます。MTUとは異なり、MSSにはヘッダーは含まれません。
計算は単純です。**MSS = MTU – 40バイト**(標準的なIPv4の場合)。標準的なネットワークでは、MTU 1500に対してMSSは1460になります。 IPv6を使用している場合はヘッダーが大きくなるため、MSSはより小さくなります。
Pingでボトルネックを特定する
推測ではなく、計測しましょう。pingコマンドを使用して、サーバーとクライアント間の正確なMTU制限を見つけることができます。
「断片化禁止」テスト
ローカルマシンからサーバーに向けて以下を実行します:
ping -s 1472 -M do [server_ip]
-s 1472: ペイロードサイズを設定します。(1472ペイロード + 8 ICMPヘッダー + 20 IPヘッダー = 1500バイト)。-M do: 断片化が必要な場合にネットワークでパケットを破棄するように強制します。
もしpingが「Frag needed」と返してきた場合、その経路は1500バイトを処理できません。成功するまで-sの値を10ずつ下げてください。最終的に1432で通った場合、最大MTUは1460(1432 + ヘッダー28バイト)となります。
Linuxサーバーを修正する2つの方法
制限を特定したら、2つの選択肢があります。ハードウェアインターフェースの設定を変更するか、TCPハンドシェイクで小さいサイズを使用するよう強制するかです。
方法1:インターフェースのMTUを調整する
サーバーがGREやWireGuardのような特定のトンネルの背後にある場合、これが最適なアプローチです。まず、現在の設定を確認します:
ip link show eth0
すぐに低い制限値をテストするには、以下を使用します:
sudo ip link set dev eth0 mtu 1460
UbuntuやDebianでこの設定を永続化するには、**Netplan**の設定(通常は/etc/netplan/内)を更新します:
network:
version: 2
ethernets:
eth0:
dhcp4: true
mtu: 1460
sudo netplan applyを実行して変更を適用します。
方法2:MSSクランピング(汎用的な回避策)
インターネット上のすべてのルーターのMTUを制御することは不可能です。MSSクランピングは、最初のTCP接続(SYNパケット)をインターセプトすることでこれを解決します。クライアントが要求したMSSを指定した値に置き換え、それ以降のすべてのパケットが通過可能なサイズに収まるようにします。
iptablesを使用してこれを自動化します。このコマンドは現在の経路に基づいて最適なMSSを算出します:
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
ルーターではなくWebサーバー上で直接作業している場合は、代わりにOUTPUTチェーンに適用します:
sudo iptables -t mangle -A OUTPUT -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360
1360という値は、ほぼすべてのVPNやカプセル化された経路に対して一般的に安全です。
結果の確認
修正を適用すると、「フリーズ」はすぐに解消されるはずです。tcpdumpを使用して、ネゴシエーションがリアルタイムで行われる様子を確認できます。サイトを更新しながら、サーバーで以下を実行してください:
sudo tcpdump -i eth0 -n tcp[tcpflags] == tcp-syn
出力のmss値を確認してください。新しい制限値と一致していれば、サーバーとクライアントがより小さく安全なパケットを使用することに合意したことが確認できます。
なぜ自動的に機能しないのか?
インターネットにはPath MTU Discovery (PMTUD)という仕組みが組み込まれています。ルーターは大きすぎるパケットを受け取ると、送信元にICMPの「Destination Unreachable(到達不能)」メッセージを返すことになっています。これにより、送信元はより小さなサイズで再試行するように促されます。
残念なことに、多くのファイアウォール管理者は、ping攻撃を防げると考えてすべてのICMPトラフィックをブロックしてしまいます。そうすることでPMTUDを殺してしまいます。ルーターはパケットを破棄しますが、サーバーにはその理由を伝えません。サーバーは届くはずのない確認応答を待ち続け、ユーザーは読み込み中の画面を見続けることになります。
結論
ネットワークのフリーズは、常に高レイテンシやメモリ不足が原因とは限りません。多くの場合、流そうとしているデータに対して配管が細すぎるだけです。パスMTUを測定し、MSSクランピングを使用することで、接続タイプに関係なく、すべてのユーザーに対してLinuxサーバーのレスポンスを維持できます。次回、特定のユーザーでサイトが固まったときは、コードを疑う前にパケットサイズを確認してみてください。

