ミドルウェアとは、OS(オペレーティングシステム)とアプリケーションの中間で動き、両者の橋渡しをするソフトウェアの総称です。WebサーバーのNginxやApache、データベースのMySQL・PostgreSQLなどが代表例で、「アプリを作るときに毎回自分で書くのは大変だが、どのアプリでも必要になる共通機能」をまとめて提供してくれます。
ただ、言葉の説明だけを読んでも「結局どれがミドルウェアなのか」はピンときません。この記事ではUbuntu 24.04の実機で、いま自分のサーバーで動いているミドルウェアを一覧するところから始めます。まずは次のコマンドを実行してみてください。
systemctl list-units --type=service --state=running --no-legend --plain
containerd.service loaded active running containerd container runtime
cron.service loaded active running Regular background program processing daemon
docker.service loaded active running Docker Application Container Engine
mariadb.service loaded active running MariaDB 10.11.14 database server
nginx.service loaded active running A high performance web server and a reverse proxy server
php8.3-fpm.service loaded active running The PHP 8.3 FastCGI Process Manager
rsyslog.service loaded active running System Logging Service
ssh.service loaded active running OpenBSD Secure Shell server
この中のnginx(Webサーバー)、php8.3-fpm(アプリケーションサーバー)、mariadb(データベース)が、まさにミドルウェアです。この記事では定義と種類を整理したうえで、確認コマンド、Web3層構造をDockerで実際に組んで動かす手順、そしてどの層で障害が起きたかを切り分ける実測結果までを扱います。
ミドルウェアとは|OSとアプリの間で共通機能を提供するソフトウェア
コンピューター上で動くソフトウェアは、大きく3つの層に分けて考えられます。下からOS、その上にミドルウェア、いちばん上にアプリケーションです。ミドルウェアという名前は、この「middle(中間)」の位置に由来します。
OSはCPU・メモリ・ディスク・ネットワークといったハードウェアを管理します。アプリケーションは「商品を検索する」「注文を受け付ける」といった業務そのものを担当します。その間にあるミドルウェアが引き受けるのは、どんな業務アプリでも必ず必要になるが、業務そのものではない共通処理です。HTTPリクエストを受け取る、データを永続化する、複数の処理を並べて待たせる、といった仕事がこれにあたります。
3層の関係をプロセスで確認する
この「層」は概念図の話ではなく、実際のプロセスの親子関係として見えます。
ps -eo pid,ppid,comm --sort=pid | awk 'NR==1 || $3 ~ /^(systemd|nginx|mariadbd|dockerd)$/'
PID PPID COMMAND
1 0 systemd
1193 1 mariadbd
1310 1 dockerd
1366 1 nginx
1367 1366 nginx
1368 1366 nginx
1369 1366 nginx
PID 1のsystemdがOS側の起点で、そこからmariadbd(DB)やnginx(Webサーバー)といったミドルウェアが直接起動されています。ミドルウェアはOSから見れば「ただの常駐プロセス」だと分かります。特別な仕組みではなく、OSの上で動くプログラムのうち、役割が「共通機能の提供」であるものをそう呼んでいるだけです。
nginxが親1つと複数の子(ワーカー)で構成されている点にも注目してください。多くのミドルウェアはマスタープロセスがワーカーを管理する構成を取っています。
pstree -ps $(pgrep -o nginx)
systemd(1)---nginx(1366)-+-nginx(1367)
|-nginx(1368)
|-nginx(1369)
この構造を知っておくと運用で役立ちます。設定を反映するときにreloadで済むのは、マスターが設定を読み直してワーカーだけを入れ替えられるためです。逆にkillでワーカーを1つ落としても、マスターがすぐ作り直すので停止しません。プロセスの止め方はkillコマンドとプロセス管理にまとめています。
OS・ミドルウェア・アプリケーションの違い
| 層 | 役割 | 具体例 | 入れ替えると影響する範囲 |
|---|---|---|---|
| アプリケーション | 業務そのものを実行する | 自社のWebアプリ、WordPress、社内システム | そのアプリだけ |
| ミドルウェア | アプリ共通の機能を提供する | Nginx、Apache、MySQL、Tomcat、Redis | そのミドルウェアを使う全アプリ |
| OS | ハードウェアを管理する | Ubuntu、Rocky Linux、Windows Server | サーバー上のすべて |
| ハードウェア | 物理的な計算資源 | CPU、メモリ、ディスク、NIC | すべて |
混同しやすいのが「ソフトウェア」との違いです。ソフトウェアはプログラム全般を指す最も広い言葉で、OSもミドルウェアもアプリケーションも、すべてソフトウェアに含まれます。ミドルウェアはその中の一区分にすぎません。OSとカーネルの関係はLinuxとは?OS・カーネル・ディストリビューションの違いで整理しています。
もうひとつ、線引きが絶対ではないことも押さえておいてください。たとえばDockerは「コンテナ実行基盤」であり、OSに近い立場とも、アプリを動かすミドルウェアとも解釈できます。分類そのものを暗記するより、「このソフトは何を肩代わりしてくれているのか」で捉えるほうが実務では役に立ちます。
ミドルウェアを使うメリット
- 開発量が減る。 HTTPの受信処理やSQLの実行エンジンを自作せずに済みます。既製品を使うので、実装期間もバグも減ります。
- OSやハードウェアの違いを吸収してくれる。 同じMySQLならUbuntuでもRocky LinuxでもSQLの書き方は変わりません。アプリ側はOSの差を意識しなくてよくなります。
- 高度な機能を設定だけで使える。 負荷分散、SSL/TLS終端、キャッシュ、トランザクション、レプリケーションといった機能が、コードを書かずに設定ファイルで手に入ります。
裏返すと、ミドルウェアが止まればその上の全アプリが止まるということでもあります。共通化した分だけ影響範囲が広がるので、可用性の設計では「同じミドルウェアに何本のサービスがぶら下がっているか」が重要になります。この観点は可用性とは?稼働率の計算と高可用性の作り方で扱っています。
ミドルウェアの種類と代表的な製品
ミドルウェアは役割ごとに分類されます。Webシステムでよく挙げられるのが次の3種類で、これを縦に並べた構成をWeb3層構造と呼びます。
3大分類|Webサーバー・APサーバー・データベース
| 種類 | 担当する仕事 | 代表的な製品 | よく使うポート |
|---|---|---|---|
| Webサーバー | HTTPリクエストの受信、静的ファイルの配信、SSL終端、リバースプロキシ | Nginx、Apache HTTP Server、LiteSpeed | 80 / 443 |
| アプリケーションサーバー(APサーバー) | プログラムの実行、動的なページの生成、DBとの接続管理 | PHP-FPM、Tomcat、Unicorn、Gunicorn、Node.js | 9000 / 8080 |
| データベース管理システム(DBMS) | データの保存・検索・整合性の維持、トランザクション | MySQL、MariaDB、PostgreSQL、Oracle Database | 3306 / 5432 |
役割分担のイメージはこうです。ブラウザからのリクエストをまずWebサーバーが受け取り、画像やCSSのような「いつ見ても同じもの(静的コンテンツ)」なら自分で返します。ログイン後のマイページのように「人や状況で内容が変わるもの(動的コンテンツ)」なら、APサーバーにプログラムの実行を任せます。APサーバーは必要なデータをDBMSに問い合わせ、結果を組み立ててWebサーバーに返します。
なお、Webサーバーとアプリケーションサーバーの境目は製品によって曖昧です。Apacheはモジュール(mod_php)を組み込めば自分でPHPを実行できるので、1つで両方の役割を果たせます。「3つ用意しなければならない」という決まりではなく、役割で分けると責任範囲が明確になり、片方だけスケールさせられるので分けることが多い、という理解が正確です。
運用で使うその他のミドルウェア
3大分類以外にも、サーバー運用の現場では次のようなミドルウェアが使われます。
| 分類 | 担当する仕事 | 代表的な製品 |
|---|---|---|
| キャッシュ・KVS | よく使うデータをメモリに置いて高速化、セッション保存 | Redis、Memcached |
| メッセージキュー | 処理を非同期に受け渡しし、急な負荷を吸収する | RabbitMQ、Apache Kafka |
| 監視 | 死活・リソース・ログの収集と通知 | Zabbix、Prometheus、Nagios |
| ジョブ管理 | バッチ処理の実行順序・依存関係の制御 | JP1、Rundeck、Apache Airflow |
| バックアップ | データの複製と世代管理、復元 | Bacula、Amanda、NetBackup |
| 高可用性(HA)クラスタ | 障害時に待機系へ自動で切り替える | Pacemaker、keepalived |
| 検索エンジン | 全文検索とインデックスの管理 | Elasticsearch、OpenSearch |
| コンテナ実行基盤 | アプリの実行環境を隔離して配布する | Docker、containerd、Kubernetes |
Linuxに標準で入っているcron(定期実行)やrsyslog(ログ収集)も、広い意味では運用系ミドルウェアの一種です。実際、冒頭のsystemctlの出力にも並んでいました。cronの設定はcronの設定方法|crontabの書き方・オプション・実行例を参照してください。
サーバーのミドルウェアをコマンドで確認する
ここからが実務です。引き継いだサーバーや久しぶりに触るサーバーで最初にやるのは、「何が入っていて、何が動いているか」の把握です。4つの角度から確認します。
①稼働中のミドルウェアを一覧する
冒頭でも使ったsystemctlが基本です。名前で絞り込むと見やすくなります。
systemctl list-units --type=service --state=running --no-legend --plain \
| grep -E 'nginx|apache|mysql|mariadb|postgres|redis|php|docker'
docker.service loaded active running Docker Application Container Engine
mariadb.service loaded active running MariaDB 10.11.14 database server
nginx.service loaded active running A high performance web server and a reverse proxy server
php8.3-fpm.service loaded active running The PHP 8.3 FastCGI Process Manager
ここで知らないと混乱する落とし穴があります。mysqlで調べているのにmariadbしか出てこない、あるいはその逆が起きることです。理由はサービス名のエイリアスにあります。
systemctl show mysql -p Id -p Names --value
mariadb.service
mariadb.service mysqld.service mysql.service
mysql.serviceもmysqld.serviceも、実体は同じmariadb.serviceです。UbuntuのMariaDBはMySQLの名前でも起動・停止できるようエイリアスが張られています。「systemctl start mysqlは通るのにsystemctl status mysqlではmariadbと表示されて不安になる」というのは、この仕組みによる正常な挙動です。systemctlの詳しい使い方はsystemctlコマンドの使い方|起動・停止・自動起動を今すぐ確認にまとめています。
②待ち受けポートから逆引きする
サービス名を知らなくても、ポートを開いているプロセスから正体を突き止められます。むしろ「systemd管理外で手動起動されているミドルウェア」を見つけるにはこちらが確実です。
ss -tulnp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1372,fd=5),("nginx",pid=1371,fd=5))
tcp LISTEN 0 4096 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1372,fd=6),("nginx",pid=1371,fd=6))
tcp LISTEN 0 80 127.0.0.1:3306 0.0.0.0:*
オプションの意味は-tがTCP、-uがUDP、-lが待ち受け中のみ、-nが名前解決せず数値表示、-pがプロセス名の表示です。
この出力の22番と3306番のProcess列が空になっている点に注目してください。ポートは開いているのに、誰が開いているか分かりません。これは-pで他人のプロセス名を見るにはroot権限が必要だからです。一般ユーザーで実行すると自分が起動したプロセスしか名前が出ず、rootが動かしているsshdやmariadbdは空欄になります。sudoを付ければ埋まります。
sudo ss -tulnp | grep -E ':(22|3306)\b'
「ポートは埋まっているのに正体が分からない」と悩んだときは、まずsudoを付け忘れていないか確認してください。
もうひとつ読み取るべきはLocal Addressの部分です。上の例でDBの3306番は127.0.0.1:3306、つまり自分自身からの接続しか受け付けない設定になっており、これは適切な状態です。もし0.0.0.0:3306になっていれば外部からも接続できてしまうため、本当に必要な設定か、ファイアウォールで守られているかを確認すべきサインになります。ポートまわりの詳細はLinuxのポート確認方法とポート競合の解消手順で解説しています。
③バージョンを確認する
バージョンは、セキュリティ対応でもトラブル調査でも最初に聞かれる情報です。ミドルウェアごとにオプションが微妙に違う点に注意してください。
nginx -v
php -v | head -1
mariadb --version
docker --version
nginx version: nginx/1.24.0 (Ubuntu)
PHP 8.3.32 (cli) (built: Jul 2 2026 18:10:58) (NTS)
mariadb Ver 15.1 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64) using EditLine wrapper
Docker version 29.6.2, build dfc4efb
2つ注意点があります。1つ目はnginx -vは標準出力ではなく標準エラー出力に書くことです。nginx -v | grep 1.24のようにパイプでつないでも何も出ないので、nginx -v 2>&1 | grep 1.24と書く必要があります。
2つ目はmariadb --versionの「Ver 15.1」はクライアントのプロトコル互換バージョンで、サーバーのバージョンではないことです。実際のサーバー版数は同じ行のDistrib 10.11.14-MariaDBのほうです。ここを読み違えると、脆弱性情報の照合で見当違いのバージョンを調べることになります。
停止中のミドルウェアも含めて「インストールされているか」を調べるなら、パッケージ管理側から見ます。
# Debian / Ubuntu系
dpkg -l | awk '$1=="ii" && $2 ~ /^(nginx|mariadb-server|php8.3-fpm|docker-ce)/ {printf "%-28s %s\n", $2, $3}'
# Red Hat / Rocky Linux系
rpm -qa | grep -E '^(nginx|mariadb-server|postgresql-server|httpd)'
docker-ce 5:29.6.2-1~ubuntu.24.04~noble
mariadb-server 1:10.11.14-0ubuntu0.24.04.1
nginx 1.24.0-2ubuntu7.15
php8.3-fpm 8.3.32-1+ubuntu24.04.1+deb.sury.org+1
パッケージのバージョン表記(1.24.0-2ubuntu7.15)は、本家の版数にディストリビューション側の修正リリース番号を足したものです。ハイフンより後ろが増えている場合、本家のバージョンは同じでもセキュリティ修正が当たっています。「1.24.0のままだから未対応」と早合点しないでください。OS側のバージョン確認はLinuxのバージョン確認方法まとめにまとめています。
まとめて一覧するスクリプト
ここまでを1本にまとめておくと、サーバーを触るたびに使い回せます。
#!/bin/bash
# mw-list.sh - 稼働中のミドルウェアとバージョンを一覧する
for s in nginx apache2 mysql mariadb postgresql redis-server memcached php8.3-fpm docker; do
state=$(systemctl is-active "$s" 2>/dev/null)
[ "$state" = "active" ] || continue
case "$s" in
nginx) ver=$(nginx -v 2>&1 | grep -oE '[0-9.]+') ;;
apache2) ver=$(apache2 -v 2>/dev/null | awk 'NR==1{print $3}') ;;
mysql|mariadb) ver=$(mariadb --version 2>/dev/null | grep -oE 'Distrib [0-9.]+' | cut -d' ' -f2) ;;
postgresql) ver=$(psql --version 2>/dev/null | awk '{print $3}') ;;
php8.3-fpm) ver=$(php -v 2>/dev/null | awk 'NR==1{print $2}') ;;
docker) ver=$(docker --version 2>/dev/null | grep -oE '[0-9.]+' | head -1) ;;
*) ver="-" ;;
esac
printf '%-14s %-8s %s\n' "$s" "$state" "${ver:--}"
done
bash mw-list.sh
nginx active 1.24.0
mysql active 10.11.14
mariadb active 10.11.14
php8.3-fpm active 8.3.32
docker active 29.6.2
mysqlとmariadbが2行出ているのは、先ほど確認したエイリアスによる重複です。同じ実体が2回数えられているだけで、DBが2つ動いているわけではありません。台数を数える用途に使うときは、systemctl show <名前> -p Id --valueで正規名に寄せてから重複を除いてください。
④設定ファイルとログの場所を突き止める
ミドルウェアの調査でいちばん時間を取られるのが「設定ファイルがどこにあるか分からない」問題です。推測せず、起動中のプロセスに聞くのが確実です。
systemctl show nginx -p FragmentPath --value
systemctl cat nginx | grep -E '^ExecStart'
/usr/lib/systemd/system/nginx.service
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ユニットファイルの場所と、実際に叩かれているコマンドが分かります。nginxならさらに一歩踏み込んで、読み込まれた設定ファイルを全部列挙できます。
sudo nginx -T | grep '^# configuration file'
# configuration file /etc/nginx/nginx.conf:
# configuration file /etc/nginx/mime.types:
# configuration file /etc/nginx/conf.d/default.conf:
# configuration file /etc/nginx/fastcgi_params:
includeで読み込まれるファイルまで展開されるので、「編集したのに反映されない」ときの調査に効きます。編集したファイルがこの一覧に出てこなければ、そのファイルは読まれていません。-Tは設定を読むためにrootが必要で、一般ユーザーだとconfiguration file test failedで終わります。
ログは、ファイルとjournalの両方を見ます。
ls /var/log/nginx/
journalctl -u nginx -n 3 --no-pager
access.log access.log.1 access.log.2.gz error.log
Aug 01 09:15:45 menfukurou systemd[1]: Starting nginx.service - A high performance web server...
Aug 01 09:15:45 menfukurou systemd[1]: Started nginx.service - A high performance web server...
使い分けは明確です。アクセスログやアプリのエラーは/var/log/配下のファイル、起動・停止・クラッシュといったプロセスの生死はjournalctl -uを見ます。「設定ミスで起動に失敗した」ケースはファイル側に何も残らないことが多いので、journalを見る癖をつけると早く解決します。ログの肥大対策はログローテーションの基本設計:最小構成から運用までにまとめています。
Web3層構造をDockerで組んで動きを見る
ここまでで個々のミドルウェアは確認できました。次は3種類が連携する様子を、実際に組んで確かめます。Dockerを使えば手元を汚さずに5分で用意できます。Docker自体の基礎はDockerとは?初心者向け完全ガイドを参照してください。
3層を起動する
まずAPサーバーで動かすプログラムを用意します。DBに接続してバージョンを聞くだけの内容です。
mkdir -p /tmp/mwdemo/www
cat > /tmp/mwdemo/www/index.php <<'PHP'
<?php
$pdo = new PDO('mysql:host=mwdb;dbname=shop', 'app', 'apppass');
$row = $pdo->query('SELECT VERSION() AS v')->fetch();
echo "Web : nginx (受信)\n";
echo "AP : PHP " . PHP_VERSION . " (実行)\n";
echo "DB : MariaDB " . $row['v'] . " (問い合わせ)\n";
PHP
次にWebサーバーの設定です。.phpで終わるURLが来たら自分では処理せず、APサーバー(PHP-FPM)の9000番ポートへ丸投げするという指定になります。
cat > /tmp/mwdemo/site.conf <<'CONF'
server {
listen 80;
root /var/www/html;
index index.php;
location ~ \.php$ {
fastcgi_pass 172.30.0.20:9000;
fastcgi_connect_timeout 2s;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
include fastcgi_params;
}
}
CONF
3つのコンテナを、後の障害テストで挙動が変わらないよう固定IPで起動します。
docker network create --subnet 172.30.0.0/24 mwnet
# DB層
docker run -d --name mwdb --network mwnet --ip 172.30.0.10 \
-e MARIADB_ROOT_PASSWORD=rootpass -e MARIADB_DATABASE=shop \
-e MARIADB_USER=app -e MARIADB_PASSWORD=apppass mariadb:11
# AP層(PDOのMySQLドライバを追加して再起動)
docker run -d --name mwap --network mwnet --ip 172.30.0.20 \
-v /tmp/mwdemo/www:/var/www/html php:8.3-fpm
docker exec mwap docker-php-ext-install pdo_mysql
docker restart mwap
# Web層
docker run -d --name mwweb --network mwnet --ip 172.30.0.30 -p 18090:80 \
-v /tmp/mwdemo/www:/var/www/html \
-v /tmp/mwdemo/site.conf:/etc/nginx/conf.d/default.conf:ro nginx:alpine
docker ps --filter name=mw --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
NAMES IMAGE STATUS PORTS
mwweb nginx:alpine Up 8 seconds 0.0.0.0:18090->80/tcp
mwap php:8.3-fpm Up 8 seconds 9000/tcp
mwdb mariadb:11 Up 37 seconds 3306/tcp
PORTS列に注目してください。外部に公開されているのはWeb層の18090番だけで、AP層の9000番とDB層の3306番はコンテナ間ネットワークからしか見えません。これが本番構成の基本形です。DBを直接インターネットに晒さない設計が、コンテナのポート指定だけで実現できています。
1回のリクエストが3層を通ることを確認する
curl -s -m 10 -w 'HTTP %{http_code}\n' http://localhost:18090/
Web : nginx (受信)
AP : PHP 8.3.33 (実行)
DB : MariaDB 11.8.8-MariaDB-ubu2404 (問い合わせ)
HTTP 200
1回のcurlが3つのミドルウェアを通過しています。これはログ側からも裏が取れます。
docker logs mwweb 2>&1 | tail -1 # Web層
docker logs mwap 2>&1 | tail -1 # AP層
172.30.0.1 - - [02/Aug/2026:07:06:06 +0000] "GET / HTTP/1.1" 200 117 "-" "curl/8.5.0" "-"
172.30.0.30 - 02/Aug/2026:07:06:06 +0000 "GET /index.php" 200
読み取れることが2つあります。まず時刻が同じ07:06:06で、1回のcurlが両方のログに記録されています。次に接続元IPが違います。Web層のログにある172.30.0.1はDockerホスト(curlの実行元)ですが、AP層のログは172.30.0.30、つまりWeb層のコンテナ自身です。AP層から見ればリクエスト元はブラウザではなくWebサーバーであり、層をひとつ経由したことがIPの違いに表れています。
URLもGET /からGET /index.phpに変わっています。Webサーバーのindex設定によって補完された結果です。障害調査でログを突き合わせるときは、この「同じ時刻の別ログ」を並べるのが基本動作になります。
どの層で落ちたかを切り分ける(実測)
3層構成の弱点は、どこか1つが壊れただけで「サイトが見られない」という同じ症状になることです。層を1つずつ落として、症状がどう変わるかを実際に測ってみます。
DB層だけを止めるとHTTPは200のまま
docker stop mwdb
curl -s -m 20 -w 'HTTP %{http_code} 経過 %{time_total}秒\n' http://localhost:18090/
Fatal error: Uncaught PDOException: PDO::__construct(): php_network_getaddresses:
getaddrinfo for mwdb failed: Name or service not known in /var/www/html/index.php:2
HTTP 200 経過 0.018879秒
ここが最重要ポイントです。データベースが完全に停止しているのに、HTTPステータスは200(成功)のまま返っています。エラーは本文の中に文字列として入っているだけです。
これは、PHPが致命的エラーを起こしてもデフォルトでは200を返す仕様によるものです。実務への影響は深刻で、「HTTPステータスが200かどうか」だけを見る死活監視は、この障害を検知できません。監視が全部グリーンなのに問い合わせが殺到する、という事故はこのパターンで起きます。対策は2つです。
- アプリ側で例外を捕捉し、失敗時は500を返すようにする(PHPなら
display_errors=Offと例外ハンドラの設定) - 監視側でステータスだけでなく本文の内容も検査する(想定文字列が含まれているかを確認する)
# ステータスと本文の両方を見る死活チェック
if curl -s -m 5 http://localhost:18090/ | grep -q '^DB *:'; then
echo "OK"
else
echo "NG: 期待する応答が返っていない"
fi
あわせて、本番環境でエラー内容をブラウザに表示させてはいけません。上の出力にはファイルの絶対パスまで含まれており、攻撃の材料になります。display_errors=Offにしてログにだけ出す設定が必須です。
もうひとつ知っておきたいのは、この0.019秒という速さは幸運な結果だという点です。今回はDBをホスト名で指定していたため、コンテナ停止で名前解決に失敗し即座にエラーが返りました。接続先をIPアドレスで指定した状態で同じ実験を行うと、パケットが捨てられるだけで応答がなく、20秒待ってもcurlに応答が返りませんでした(curl -m 20がタイムアウト)。
本番でこれが起きると深刻です。DBの応答を待つリクエストがAP層のワーカーを1つずつ占有していき、ワーカーを使い切った時点で「DBを使わないページまで含めて全部が止まる」という連鎖障害になります。DB接続には必ずタイムアウトを設定してください(PHPのPDOならPDO::ATTR_TIMEOUT)。この「1か所の遅延が全体に波及する」構造は可用性とは?稼働率の計算と高可用性の作り方で扱った直列構成の話そのものです。
AP層が落ちると502または504になる
次にAP層を止めます。Web層から見て「渡す相手がいない」状態です。
docker stop mwap
curl -s -m 10 -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:18090/
docker logs mwweb 2>&1 | grep error | tail -1
HTTP 504
2026/08/02 07:06:25 [error] 22#22: *3 upstream timed out (110: Operation timed out)
while connecting to upstream, client: 172.30.0.1, request: "GET / HTTP/1.1",
upstream: "fastcgi://172.30.0.20:9000", host: "localhost:18090"
一方、AP層は正常に動いているのに設定でポート番号を間違えている場合は、別のエラーになります。転送先を存在しない9001番にしたWeb層をもう1つ立てて比べてみます。
docker start mwap
sed 's/172.30.0.20:9000/172.30.0.20:9001/' /tmp/mwdemo/site.conf > /tmp/mwdemo/badport.conf
docker run -d --name mwweb-bad --network mwnet -p 18091:80 \
-v /tmp/mwdemo/www:/var/www/html \
-v /tmp/mwdemo/badport.conf:/etc/nginx/conf.d/default.conf:ro nginx:alpine
curl -s -m 10 -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:18091/
docker logs mwweb-bad 2>&1 | grep error | tail -1
HTTP 502
2026/08/02 07:06:21 [error] 21#21: *1 connect() failed (111: Connection refused)
while connecting to upstream, client: 172.30.0.1, request: "GET / HTTP/1.1",
upstream: "fastcgi://172.30.0.20:9001", host: "localhost:18091"
502と504は原因が違います。 502(Connection refused)は「相手のIPには届いたが、そのポートで誰も待ち受けていない」状態で、ポート番号の設定ミスやプロセスの停止が疑わしいケースです。504(timed out)は「パケットを投げたが返事がない」状態で、ホストごと落ちている、ファイアウォールで遮断されている、あるいは相手が処理に詰まっているケースです。エラーコードから調べる先を絞り込めます。
| 止まった層 | HTTPステータス | ログに出る内容 | 最初に確認すること |
|---|---|---|---|
| 正常 | 200 | 各層にアクセスログ | — |
| DB層 | 200(本文にエラー) | AP層のエラーログに接続失敗 | systemctl status mariadb、DBのポート疎通 |
| AP層(プロセス停止) | 502 | Web層にConnection refused | systemctl status php8.3-fpm、ss -tulnp |
| AP層(到達不可) | 504 | Web層にupstream timed out | ファイアウォール、ホストの死活、AP層の高負荷 |
| Web層 | 接続不可(curlは000) | Web層のログ自体が増えない | systemctl status nginx、80/443の待ち受け |
切り分けの順番は外側から内側へです。ブラウザに近いWeb層から順に「そこまでは届いているか」を確認していけば、必ずどこかで止まります。500番台のエラーを実サーバーで追う手順は500エラーの原因切り分け|Nginx・PHP-FPM・アプリを5分で特定する手順にまとめています。
検証が終わったら片づけておきます。
docker rm -f mwweb mwweb-bad mwap mwdb
docker network rm mwnet
ミドルウェア運用でつまずくポイント
実際の運用でよく踏むパターンと、最初に打つ手をまとめます。
| 症状 | ありがちな原因 | 確認コマンド |
|---|---|---|
| 起動しない(Address already in use) | 同じポートを別のミドルウェアが使用中 | sudo ss -tulnp | grep :80 |
| 設定を変えたのに反映されない | 読まれていないファイルを編集した/reload忘れ | sudo nginx -T | grep 'configuration file' |
| reloadで設定ミスに気づかず全断 | 構文チェックをせずに反映した | sudo nginx -t(成功後にreload) |
| 再起動後だけ動かない | 自動起動が無効のまま | systemctl is-enabled nginx mariadb |
| DBに繋がらない | bind-addressが127.0.0.1のまま/権限設定 | sudo ss -tulnp | grep 3306 |
| 突然503や応答遅延 | ワーカー数・最大接続数の上限に到達 | sudo ss -s、各ミドルウェアのステータス |
| ディスクが埋まって全部止まる | アクセスログ・スロークエリログの肥大 | df -h、du -sh /var/log/* |
特に事故につながりやすいのが構文チェックの省略です。多くのミドルウェアには設定ファイルを検証するオプションがあり、反映前に必ず通す習慣をつけてください。
sudo nginx -t && sudo systemctl reload nginx # Nginx
sudo apachectl configtest # Apache
sudo sshd -t # OpenSSH
&& でつなぐのがポイントで、チェックに失敗したらreloadが実行されません。特にSSHは設定を壊すとログインできなくなり復旧手段を失うため、sshd -tは必須です。負荷が原因の遅延を追う手順は高負荷時の初動対応|CPU・メモリ・I/O・コネクションを最短で切り分ける手順を参照してください。
よくある質問
ミドルウェアとOSの違いは何ですか?
OSはCPUやメモリといったハードウェアを直接管理し、その上で動くすべてのプログラムに実行環境を提供します。ミドルウェアはそのOSの上で動く常駐プログラムのひとつで、アプリ共通の機能(HTTP受信、データ保存など)を提供します。ps -eo pid,ppid,commを実行すると、PID 1のsystemd(OS側)からnginxやmariadbdが起動されているのが確認でき、ミドルウェアがOSにとっては一般的なプロセスにすぎないことが分かります。
ミドルウェアとアプリケーションの違いは何ですか?
判断基準は「業務内容に依存するかどうか」です。「注文を受け付ける」「在庫を引き当てる」のようにその事業でしか意味を持たない処理はアプリケーション、「HTTPを受ける」「データを保存する」のようにどんな業務でも共通して必要な処理はミドルウェアです。別の会社にそのまま持っていって役に立つならミドルウェア、と考えると判別しやすくなります。
Webサーバーとアプリケーションサーバーは両方必要ですか?
必須ではありません。Apacheにmod_phpを組み込む構成や、Node.jsが直接HTTPを受ける構成なら1つで済みます。それでも分けることが多いのは、静的ファイルの配信はWebサーバーのほうが圧倒的に速く、負荷のかかるプログラム実行だけをAP層としてスケールさせられるためです。加えてWeb層でSSL終端や不正リクエストの遮断を行えば、AP層をインターネットから隔離できます。小規模なら1台にまとめても問題ありません。
サーバーに入っているミドルウェアを一覧するには?
3つの角度から見ると漏れがありません。稼働中のものはsystemctl list-units --type=service --state=running、ポートを開いているものはsudo ss -tulnp、インストール済み(停止中を含む)はdpkg -lやrpm -qaです。systemd管理外で手動起動されたミドルウェアはsystemctlに出ないため、ssでの確認を省略しないでください。
DBが落ちているのにHTTPが200で返るのはなぜですか?
HTTPステータスを決めるのはWebサーバーとアプリで、アプリがエラーを起こしてもステータスを変更しなければ200のまま返るためです。この記事の実測でも、MariaDBを完全に停止した状態でHTTP 200が返り、本文にPHPの致命的エラーが混ざっているだけという結果になりました。ステータスコードだけの死活監視ではこの障害を検知できないので、応答本文に想定の文字列が含まれるかまで検査してください。
502と504はどう違いますか?
502 Bad Gatewayは、転送先まで到達したものの接続を拒否された状態(Connection refused)です。プロセスが停止している、ポート番号の指定を間違えている、といった原因が中心になります。504 Gateway Timeoutは、送ったパケットへの応答が返らずタイムアウトした状態(upstream timed out)で、ホストごとダウンしている、ファイアウォールに遮断されている、相手が処理に詰まっている場合に出ます。どちらもWebサーバーのエラーログに理由が明記されるので、まずログを見てください。
ミドルウェアのバージョンはどこを見ればよいですか?
コマンドの-vや--versionが基本ですが、読み方に注意が必要です。mariadb --versionの「Ver 15.1」はクライアントのプロトコル版数で、サーバーの版数は同じ行のDistrib 10.11.14-MariaDBのほうです。またnginx -vは標準エラー出力に書くため、パイプでつなぐときは2>&1が必要です。脆弱性を照合する場合は、ディストリビューションの修正が反映されたdpkg -l/rpm -qaのパッケージ版数も確認してください。
まとめ
- ミドルウェアはOSとアプリの中間で動き、業務に依存しない共通機能を提供するソフトウェア
- 代表格はWebサーバー・APサーバー・DBMSの3種類で、縦に並べた構成をWeb3層構造と呼ぶ
- 稼働状況は
systemctl list-units、ポートからの逆引きはsudo ss -tulnpで確認する - Ubuntuでは
mysql.serviceがmariadb.serviceのエイリアスになっており、同じ実体が2つの名前で出る mariadb --versionの「Ver 15.1」はクライアント版数。サーバー版数はDistrib以降を読む- 設定ファイルは推測せず
systemctl show -p FragmentPathとsudo nginx -Tで特定する - 実測ではDB層を完全停止してもHTTPは200のままだった。ステータスだけの監視では検知できない
- AP層の障害は502(Connection refused)と504(timed out)で原因が分かれる
まずは自分のサーバーでsystemctl list-units --type=service --state=runningとsudo ss -tulnpを実行し、どのミドルウェアがどのポートを持っているかを書き出すところから始めてみてください。構成が頭に入っていれば、障害が起きたときに見るべき場所が最初から絞れます。
関連記事
- Linuxとは?OS・カーネル・ディストリビューションの違いを初心者向けに解説
- systemctlコマンドの使い方|起動・停止・自動起動を今すぐ確認
- Linuxのポート確認方法とポート競合の解消手順|netstat・lsof・ss完全ガイド
- 500エラーの原因切り分け|Nginx・PHP-FPM・アプリを5分で特定する手順
- Dockerとは?初心者向け完全ガイド:コンテナの基礎から環境構築・デプロイまで
- 可用性とは?稼働率の計算と高可用性の作り方をコマンドで解説
- 高負荷時の初動対応|CPU・メモリ・I/O・コネクションを最短で切り分ける手順
- ログローテーションの基本設計:最小構成から運用まで
- Linuxのバージョン確認方法まとめ|OS・ディストリビューション・カーネルの調べ方
- linux コマンド一覧|用途別に整理した初心者向け完全ガイド
- Linuxトラブルシューティング総合ハブ|まず試す簡易対処と詳細ガイド

コメント