ミドルウェアとは?種類・具体例とサーバーでの確認コマンドを解説

実務レシピ

ミドルウェアとは、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で実際に組んで動かす手順、そしてどの層で障害が起きたかを切り分ける実測結果までを扱います。

  1. ミドルウェアとは|OSとアプリの間で共通機能を提供するソフトウェア
    1. 3層の関係をプロセスで確認する
    2. OS・ミドルウェア・アプリケーションの違い
    3. ミドルウェアを使うメリット
  2. ミドルウェアの種類と代表的な製品
    1. 3大分類|Webサーバー・APサーバー・データベース
    2. 運用で使うその他のミドルウェア
  3. サーバーのミドルウェアをコマンドで確認する
    1. ①稼働中のミドルウェアを一覧する
    2. ②待ち受けポートから逆引きする
    3. ③バージョンを確認する
    4. まとめて一覧するスクリプト
    5. ④設定ファイルとログの場所を突き止める
  4. Web3層構造をDockerで組んで動きを見る
    1. 3層を起動する
    2. 1回のリクエストが3層を通ることを確認する
  5. どの層で落ちたかを切り分ける(実測)
    1. DB層だけを止めるとHTTPは200のまま
    2. AP層が落ちると502または504になる
  6. ミドルウェア運用でつまずくポイント
  7. よくある質問
    1. ミドルウェアとOSの違いは何ですか?
    2. ミドルウェアとアプリケーションの違いは何ですか?
    3. Webサーバーとアプリケーションサーバーは両方必要ですか?
    4. サーバーに入っているミドルウェアを一覧するには?
    5. DBが落ちているのにHTTPが200で返るのはなぜですか?
    6. 502と504はどう違いますか?
    7. ミドルウェアのバージョンはどこを見ればよいですか?
  8. まとめ
  9. 関連記事
  10. 関連記事

ミドルウェアとは|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に近い立場とも、アプリを動かすミドルウェアとも解釈できます。分類そのものを暗記するより、「このソフトは何を肩代わりしてくれているのか」で捉えるほうが実務では役に立ちます。

ミドルウェアを使うメリット

  1. 開発量が減る。 HTTPの受信処理やSQLの実行エンジンを自作せずに済みます。既製品を使うので、実装期間もバグも減ります。
  2. OSやハードウェアの違いを吸収してくれる。 同じMySQLならUbuntuでもRocky LinuxでもSQLの書き方は変わりません。アプリ側はOSの差を意識しなくてよくなります。
  3. 高度な機能を設定だけで使える。 負荷分散、SSL/TLS終端、キャッシュ、トランザクション、レプリケーションといった機能が、コードを書かずに設定ファイルで手に入ります。

裏返すと、ミドルウェアが止まればその上の全アプリが止まるということでもあります。共通化した分だけ影響範囲が広がるので、可用性の設計では「同じミドルウェアに何本のサービスがぶら下がっているか」が重要になります。この観点は可用性とは?稼働率の計算と高可用性の作り方で扱っています。

ミドルウェアの種類と代表的な製品

ミドルウェアは役割ごとに分類されます。Webシステムでよく挙げられるのが次の3種類で、これを縦に並べた構成をWeb3層構造と呼びます。

3大分類|Webサーバー・APサーバー・データベース

種類担当する仕事代表的な製品よく使うポート
WebサーバーHTTPリクエストの受信、静的ファイルの配信、SSL終端、リバースプロキシNginx、Apache HTTP Server、LiteSpeed80 / 443
アプリケーションサーバー(APサーバー)プログラムの実行、動的なページの生成、DBとの接続管理PHP-FPM、Tomcat、Unicorn、Gunicorn、Node.js9000 / 8080
データベース管理システム(DBMS)データの保存・検索・整合性の維持、トランザクションMySQL、MariaDB、PostgreSQL、Oracle Database3306 / 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.servicemysqld.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

mysqlmariadbが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層(プロセス停止)502Web層にConnection refusedsystemctl status php8.3-fpmss -tulnp
AP層(到達不可)504Web層に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 -hdu -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 -lrpm -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 -lrpm -qaのパッケージ版数も確認してください。

まとめ

  • ミドルウェアはOSとアプリの中間で動き、業務に依存しない共通機能を提供するソフトウェア
  • 代表格はWebサーバー・APサーバー・DBMSの3種類で、縦に並べた構成をWeb3層構造と呼ぶ
  • 稼働状況はsystemctl list-units、ポートからの逆引きはsudo ss -tulnpで確認する
  • Ubuntuではmysql.servicemariadb.serviceのエイリアスになっており、同じ実体が2つの名前で出る
  • mariadb --versionの「Ver 15.1」はクライアント版数。サーバー版数はDistrib以降を読む
  • 設定ファイルは推測せずsystemctl show -p FragmentPathsudo nginx -Tで特定する
  • 実測ではDB層を完全停止してもHTTPは200のままだった。ステータスだけの監視では検知できない
  • AP層の障害は502(Connection refused)と504(timed out)で原因が分かれる

まずは自分のサーバーでsystemctl list-units --type=service --state=runningsudo ss -tulnpを実行し、どのミドルウェアがどのポートを持っているかを書き出すところから始めてみてください。構成が頭に入っていれば、障害が起きたときに見るべき場所が最初から絞れます。

関連記事

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント