2026年5月24日
2026年5月24日
帯域制限(QoS)でトラフィックを制御する方法(Linuxのtcとアプリ側の制限)
バックアップの転送や大きなファイルの配信が回線を占有し、その間だけ WordPress の表示が遅くなる。こうした問題は、サーバーから出ていく通信に優先度と上限を付ける「帯域制御(QoS)」で和らげられます。この記事では、Linux の tc(traffic control)で送信帯域を分け合う設定と、rsync や Nginx などアプリケーション側で上限を付ける方法を、確認手順と失敗しやすい点を含めて説明します。読み終えると「Web の応答を優先し、裏方の大量転送は上限付きで流す」構成を自分のサーバーで組めるようになります。
帯域制御でできること・できないこと
最初に範囲をはっきりさせておきます。ここで扱う QoS は「自分のサーバーが送り出すパケットの順番と速さを調整する」ことです。
- できること:送信(egress)方向の速度に上限を付ける、通信の種類ごとに最低保証と優先度を決める、キューに溜まるパケットを減らして遅延を抑える
- 難しいこと:受信(ingress)方向の制御。届いたパケットは回線をすでに使ってしまっているため、受信側で絞っても回線の混雑は解消しにくく、設定には IFB という仮想デバイスを使う必要があります
- できないこと:インターネット上の経路やクラウド事業者側の帯域上限を変えること。パケットに優先度の印(DSCP)を付けても、一般的なインターネット経路では尊重されないと考えたほうが安全です
つまり「サーバーの出口で、何をどれだけ先に出すか」を決める仕組みです。Web サーバーの通信はほとんどが応答の送信なので、出口の制御だけでも効果が出やすい場面は多くあります。
まず現状を測る
制御を入れる前に、どの通信が帯域を使っているかを把握します。原因が帯域ではなく CPU やディスク、PHP の処理時間だった場合、帯域制限をしても速くなりません。
# インターフェース名を確認(eth0, ens3 など環境で異なる)
ip -br link
# 現在のキュー規則(qdisc)を確認
tc qdisc show dev eth0
# インターフェースごとの転送量をリアルタイムで見る(要インストール)
sudo iftop -i eth0
sudo nload eth0
iftop で「バックアップ先のホストへの送信が回線の上限近くに張り付いている時間帯に、サイトの応答が遅い」と分かれば、帯域制御が効く状況です。回線の実効速度は、iperf3 などで事前に測っておくと上限値を決めやすくなります。
tc qdisc show の結果に fq_codel や fq が表示されることがあります。多くのディストリビューションでは既定でこうした公平キューが使われていて、単一の大きな転送が他の通信の遅延を極端に悪化させることはある程度抑えられています。それでも足りない場合に、次の HTB による明示的な分割を検討します。
tc と HTB で送信帯域を分け合う
HTB(Hierarchical Token Bucket)は、帯域を木構造のクラスに分け、クラスごとに「保証する速度(rate)」と「空いていれば借りられる上限(ceil)」を設定できる仕組みです。ここでは回線を 100Mbps と仮定し、Web の応答を優先、バックアップ転送は最大 20Mbps に抑える例を示します。
作業前の安全策
リモートのサーバーで tc を誤設定すると、SSH の通信まで詰まって操作できなくなることがあります。設定を入れる前に、一定時間後に元に戻す予約をしておくと安心です。
# 10分後に root の qdisc を削除して既定に戻す予約(at が必要)
echo "tc qdisc del dev eth0 root" | sudo at now + 10 minutes
問題なく動くことを確認したら atq で予約番号を調べ、atrm 番号 で取り消します。本番に入れる前に、同じ構成の検証環境で試すことも強くおすすめします。
設定例
DEV=eth0
# ルートに HTB を置き、分類されない通信はクラス 1:10 に入れる
sudo tc qdisc add dev $DEV root handle 1: htb default 10
# 親クラス:回線全体(実効速度より少し低めにするとキューが自分の側に溜まり制御しやすい)
sudo tc class add dev $DEV parent 1: classid 1:1 htb rate 95mbit ceil 95mbit
# 1:10 Web など通常の通信:75Mbps を保証、空きがあれば 95Mbps まで
sudo tc class add dev $DEV parent 1:1 classid 1:10 htb rate 75mbit ceil 95mbit prio 0
# 1:30 バックアップ転送:最低 5Mbps、上限 20Mbps
sudo tc class add dev $DEV parent 1:1 classid 1:30 htb rate 5mbit ceil 20mbit prio 2
# 各クラスの中は fq_codel で公平にキューイング
sudo tc qdisc add dev $DEV parent 1:10 handle 10: fq_codel
sudo tc qdisc add dev $DEV parent 1:30 handle 30: fq_codel
# 宛先がバックアップサーバー(例: 192.0.2.50)の通信をクラス 1:30 へ
sudo tc filter add dev $DEV protocol ip parent 1: prio 1 u32 \
match ip dst 192.0.2.50/32 flowid 1:30
ポイントは次のとおりです。
- default に指定したクラスを作ること:
default 10と書いたのにクラス 1:10 が無いと、分類されない通信が制御を経ずに流れ、意図した効果が出ません - 単位に注意:tc では
mbitがメガビット毎秒、mbpsがメガバイト毎秒です。20mbpsと書くと約160Mbps の意味になり、まったく絞れていない状態になります - rate の合計は親を超えない:子クラスの rate の合計が親の rate を上回ると、保証の意味が崩れます
- 振り分け条件は複数書ける:宛先 IP のほか、
match ip dport 873 0xffff(rsync デーモン)のように宛先ポートでも分類できます
効いているかを確認する
# クラスごとの送信量・ドロップ数・待ち状態を表示
tc -s class show dev eth0
# フィルタの一覧
tc filter show dev eth0
バックアップ転送中に tc -s class show を数回実行し、1:30 の送信バイト数が増え、速度が ceil 付近で頭打ちになっていれば分類が成功しています。1:30 のカウンタが増えずに 1:10 だけが増える場合は、フィルタの条件(宛先 IP やポート)が実際の通信と合っていません。ss -tn で実際の接続先を確認してください。
元に戻すときは次の1行で、HTB 配下のクラスとフィルタもまとめて消えます。
sudo tc qdisc del dev eth0 root
再起動後も残す
tc の設定はメモリ上にあるだけで、再起動すると消えます。動作を確認できたら、上の設定をシェルスクリプトにまとめ、systemd のユニットから起動時に実行する形にすると管理しやすくなります。
# /etc/systemd/system/tc-shaping.service
[Unit]
Description=Traffic shaping with tc
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/tc-shaping.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
スクリプトの先頭で tc qdisc del dev eth0 root 2>/dev/null を実行しておくと、二重に適用されてエラーになるのを防げます。クラウドのサーバーではインターフェース名が再作成時に変わることがあるため、スクリプト内の名前も確認してください。
もっと簡単に済ませたい場合:CAKE
比較的新しいカーネルには CAKE という qdisc があり、回線速度を1つ指定するだけで、フロー単位の公平化と遅延抑制をまとめて行えます。
sudo tc qdisc replace dev eth0 root cake bandwidth 95mbit
細かいクラス分けは HTB ほど自由ではありませんが、「1本の大きな転送がほかを押しのける」状況の改善には十分なことがあります。利用できるかはカーネルと iproute2 のバージョンによるため、modinfo sch_cake で確認してから使ってください(2026年10月現在、主要ディストリビューションの多くで利用可能です)。
アプリケーション側で上限を付ける
カーネルの設定を触らずに済むなら、そのほうが影響範囲は小さくなります。帯域を使っている原因がはっきりしている場合は、まずアプリケーション側の上限を検討してください。
rsync・rclone の転送速度を絞る
# rsync:--bwlimit の既定単位は KiB/s(この例は約 2MiB/s)
rsync -az --bwlimit=2048 /var/www/ backup@192.0.2.50:/backup/www/
# rclone:オブジェクトストレージへの転送を 2MiB/s に制限
rclone sync /var/backups remote:wp-backups --bwlimit 2M
rclone は時間帯ごとに上限を変える書き方にも対応しています。WordPress のバックアッププラグインが外部ストレージへ送る場合は、プラグイン側に速度やチャンクサイズの設定があるかを確認してください。設定名はプラグインやバージョンによって異なるため、公式ドキュメントで確かめてください。
Nginx で1接続あたりの配信速度を絞る
大きな動画や ZIP の配信が帯域を占有する場合は、Nginx の limit_rate が使えます。
location /downloads/ {
limit_rate_after 10m; # 最初の 10MB は制限なし
limit_rate 1m; # 以降は 1接続あたり 1MB/s
}
limit_rate は1接続あたりの上限で、単位はバイト毎秒です。同じ利用者が複数の接続を張ると合計はその分増えるため、必要に応じて limit_conn で同時接続数も制限します。リクエスト数の制限(limit_req)とは別物なので、ボット対策が目的ならそちらを使います。
よくあるつまずき
- 制限したのに速くならない:遅さの原因が帯域ではない可能性があります。PHP-FPM のプロセス数、DB の応答、ディスク I/O を
topやiostatで確認してください - SSH まで遅くなった:SSH が絞ったクラスに分類されていないか確認します。宛先 IP で分類している場合、同じホストへの SSH も一緒に絞られます。ポートや向きで条件を細かくしてください
- 受信を絞りたい:アップロードの受信を制御したい場合、tc では IFB を使った設定が必要で難度が上がります。多くの場合はアプリケーション側(アップロードサイズ上限や、送信元でのレート制限)で対処するほうが現実的です
- コンテナ環境で効かない:Docker などでは通信が仮想ブリッジを経由するため、どのインターフェースに設定するかで結果が変わります。ホスト側の外向きインターフェースで分類条件を確認してください
まとめ
- 帯域制御は「サーバーから出ていく通信」の順番と速さを調整する仕組みで、受信方向やインターネット経路の混雑までは解決できない
- まず iftop などで帯域を使っている通信を特定し、原因が帯域でないなら別の対策を優先する
- tc と HTB では、default クラスの作成、
mbitとmbpsの違い、rate の合計に注意し、tc -s class showで効果を確かめる - リモート作業では at コマンドで自動復旧を予約し、検証環境で試してから本番に入れる。再起動後に残すには systemd で起動時に適用する
- 原因が特定できているなら、rsync の
--bwlimitや Nginx のlimit_rateのようなアプリケーション側の上限から試すと影響範囲が小さい