カテゴリー: PC Page 1 of 56

実行中のプロセスのメモリ量を昇順で表示するワンライナー。

概要

LinuxでWebサーバを運用していく中で気になるメモリの使用量。

「どのプロセスが一番メモリを消費しているのか?」

を調査してみます。

ワンライナーの設定

通例、メモリの使用量を見るのは

ps aux

とするのですが、

  • 全ての情報が見えてしまうので探しにくい
  • 単位が分からない

等の問題が発生します。そこで、

現在利用中のプロセスから、一番メモリを使用しているサービスを昇順で5つ抜き出し、そのメモリ量を見やすい形で成形するワンライナー

を生成AIにて作ってもらいます。(Gemini 2.5 Proを利用)

物理メモリ使用量(RSS: Resident Set Size)を基準にして、MB単位で表示するワンライナー

ps aux | tail -n +2 | sort -k 6 -rn | head -n 5 | sort -k 6 -n | awk '{cmd=""; for(i=11;i<=NF;i++){cmd=cmd" "$i}; printf "%10.2f MB   %s\n", $6/1024, cmd}'

コマンドの解説

ps aux
  • 現在実行中の全ユーザーのプロセスを詳細な情報付きで表示します。
tail -n +2
  • ps コマンドが出力する結果の1行目(ヘッダー行)を除外し、2行目以降のプロセス情報のみを次のコマンドに渡します。
sort -k 6 -rn
  • 6列目にある RSS(物理メモリ使用量) を基準に、数値を大きい順(降順)に並べ替えます。
  • -k 6: 6列目をソートキーに指定します。
  • -r: 逆順(降順)でソートします。
  • -n: 文字列ではなく数値としてソートします。
head -n 5
  • 降順にソートされた結果から、上位5つのプロセス(最もメモリを使用している5つ)を抽出します。
sort -k 6 -n
  • 抽出された5つのプロセスを、再度6列目のRSSを基準に、今度は小さい順(昇順)に並べ替えます。
awk '{ ... }'
  • 最終的な出力を見やすい形式に整形します。
  • cmd=""; for(i=11;i<=NF;i++){cmd=cmd" "$i}: 11列目以降の文字列をすべて連結し、スペースを含む完全なコマンド名を変数 cmd に格納します。
  • printf "%10.2f MB %s\n", $6/1024, cmd}: 6列目のRSS(KB単位)を1024で割ってMB単位に変換し、小数点以下2桁までの浮動小数点数としてフォーマットします。メモリ量を右寄せ10桁で表示した後、コマンド名を表示します。

表示例

ps aux | tail -n +2 | sort -k 6 -rn | head -n 5 | sort -k 6 -n | awk '{cmd=""; for(i=11;i<=NF;i++){cmd=cmd" "$i}; printf "%10.2f MB   %s\n", $6/1024, cmd}'
    236.36 MB    node -r dotenv-flow/config dist/server/app.js
    288.86 MB    Passenger RubyApp: /home/www-data/redmine1 (production)
    379.55 MB    Passenger RubyApp: /home/www-data/redmine2 (production)
    543.54 MB    /usr/sbin/mysqld
    654.73 MB    /usr/share/elasticsearch/jdk/bin/java -Des.networkaddress.cache.ttl=60 -Des.networkaddress.cache.negative.ttl=10 -XX:+AlwaysPreTouch -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:-OmitStackTraceInFastThrow -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Dlog4j2.formatMsgNoLookups=true -Djava.locale.providers=CLDR -Dorg.apache.lucene.vectorization.upperJavaFeatureVersion=24 -Des.distribution.type=deb -Des.java.type=bundled JDK --enable-native-access=org.elasticsearch.nativeaccess,org.apache.lucene.core --enable-native-access=ALL-UNNAMED --illegal-native-access=deny -XX:ReplayDataFile=/var/log/elasticsearch/replay_pid%p.log -Des.entitlements.enabled=true -XX:+EnableDynamicAgentLoading -Djdk.attach.allowAttachSelf=true --patch-module=java.base=lib/entitlement-bridge/elasticsearch-entitlement-bridge-8.19.2.jar --add-exports=java.base/org.elasticsearch.entitlement.bridge=org.elasticsearch.entitlement,java.logging,java.net.http,java.naming,jdk.net -XX:+UseG1GC -Djava.io.tmpdir=/tmp/elasticsearch-10892987525338221374 --add-modules=jdk.incubator.vector -XX:+HeapDumpOnOutOfMemoryError -XX:+ExitOnOutOfMemoryError -XX:HeapDumpPath=/var/lib/elasticsearch -XX:ErrorFile=/var/log/elasticsearch/hs_err_pid%p.log -Xlog:gc*,gc+age=trace,safepoint:file=/var/log/elasticsearch/gc.log:utctime,level,pid,tags:filecount=32,filesize=64m -Xms256m -Xmx256m -XX:MaxDirectMemorySize=134217728 -XX:G1HeapRegionSize=4m -XX:InitiatingHeapOccupancyPercent=30 -XX:G1ReservePercent=15 --module-path /usr/share/elasticsearch/lib --add-modules=jdk.net --add-modules=jdk.management.agent --add-modules=ALL-MODULE-PATH -m org.elasticsearch.server/org.elasticsearch.bootstrap.Elasticsearch

と、一番はElasticsearch(Growiで利用)。次いでMySQL、Redmineと続きます。nodeはGrowi関係です。

メモリ使用率(%MEM)を基準にして、MB単位で表示するワンライナー

続いて、システム全体のメモリ使用率を基準にする場合。

ps aux | tail -n +2 | sort -k 4 -rn | head -n 5 | sort -k 4 -n | awk '{cmd=""; for(i=11;i<=NF;i++){cmd=cmd" "$i}; printf "%5s%%   %s\n", $4, cmd}'

ソートの基準をRSS(6列目)から %MEM(4列目) に変更している点が主な違いです。

  4.3%    node -r dotenv-flow/config dist/server/app.js
  4.8%    Passenger RubyApp: /home/www-data/redmine1 (production)
  6.4%    Passenger RubyApp: /home/www-data/redmine2 (production)
  9.1%    /usr/sbin/mysqld
 11.0%    /usr/share/elasticsearch/jdk/bin/java -Des.networkaddress.cache.ttl=60 -Des.networkaddress.cache.negative.ttl=10 -XX:+AlwaysPreTouch -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:-OmitStackTraceInFastThrow -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Dlog4j2.formatMsgNoLookups=true -Djava.locale.providers=CLDR -Dorg.apache.lucene.vectorization.upperJavaFeatureVersion=24 -Des.distribution.type=deb -Des.java.type=bundled JDK --enable-native-access=org.elasticsearch.nativeaccess,org.apache.lucene.core --enable-native-access=ALL-UNNAMED --illegal-native-access=deny -XX:ReplayDataFile=/var/log/elasticsearch/replay_pid%p.log -Des.entitlements.enabled=true -XX:+EnableDynamicAgentLoading -Djdk.attach.allowAttachSelf=true --patch-module=java.base=lib/entitlement-bridge/elasticsearch-entitlement-bridge-8.19.2.jar --add-exports=java.base/org.elasticsearch.entitlement.bridge=org.elasticsearch.entitlement,java.logging,java.net.http,java.naming,jdk.net -XX:+UseG1GC -Djava.io.tmpdir=/tmp/elasticsearch-10892987525338221374 --add-modules=jdk.incubator.vector -XX:+HeapDumpOnOutOfMemoryError -XX:+ExitOnOutOfMemoryError -XX:HeapDumpPath=/var/lib/elasticsearch -XX:ErrorFile=/var/log/elasticsearch/hs_err_pid%p.log -Xlog:gc*,gc+age=trace,safepoint:file=/var/log/elasticsearch/gc.log:utctime,level,pid,tags:filecount=32,filesize=64m -Xms256m -Xmx256m -XX:MaxDirectMemorySize=134217728 -XX:G1HeapRegionSize=4m -XX:InitiatingHeapOccupancyPercent=30 -XX:G1ReservePercent=15 --module-path /usr/share/elasticsearch/lib --add-modules=jdk.net --add-modules=jdk.management.agent --add-modules=ALL-MODULE-PATH -m org.elasticsearch.server/org.elasticsearch.bootstrap.Elasticsearch

と、実メモリ6GB中の11%をElasticsearchが占めていることが明らかになりました。

Apache & PHP-FPM 管理スクリプト (引数対応版)。

Webサーバを運用する上で役立ている

  1. Apacheで有効になっているサイトの設定情報を表示
  2. 構文を確認
  3. 問題が無ければApache再起動
  4. 再起動後にステータスを表示する

スクリプト。非常に便利なものでしたが、VPS新調に際してPHP-FPMを導入。

そこで、

  1. PHP-FPMも構文チェックを行う
  2. 他、引数による処理

を改訂しました。(元ネタ:ChatGPT、リファクタリングはdeepseekとGemini)

スクリプト内容

  • apache2-check.sh
#!/bin/bash

#================================================================
# Apache & PHP-FPM 管理スクリプト (引数対応版)
#================================================================
#
# 機能:
# 1. Apacheで有効になっているサイトの設定情報を表示します。
# 2. ApacheとPHP-FPMの設定ファイルの構文をチェックします。
# 3. ApacheとPHP-FPMの再起動を個別に確認し、実行します。
# 4. 再起動後に各サービスのステータスを表示します。
#
# 引数:
#  -y : 確認プロンプトをすべてスキップし、自動で'yes'と応答します。
#  -a : Apacheのみを対象とします。
#  -p : PHP-FPMのみを対象とします。
#  -h : このヘルプを表示します。
#
#================================================================

# --- 設定項目 ---

# Apacheのサイト設定が格納されているディレクトリ
SITES_DIR="/etc/apache2/sites-enabled"

# 操作対象のPHPバージョン (例: "8.3", "8.2", "7.4")
PHP_VERSION="8.3"

# --- 設定ここまで ---


# --- スクリプトの動作を制御するフラグ ---
AUTO_YES=false
RESTART_APACHE=true  # デフォルトでは両方実行
RESTART_PHP=true     # デフォルトでは両方実行
EXCLUSIVE_MODE=false # -a または -p が指定されたかを判定するフラグ

# --- ヘルプ表示関数 ---
usage() {
    echo "Usage: $(basename "$0") [-y] [-a] [-p] [-h]"
    echo "  -y : 確認プロンプトをすべてスキップし、自動で'yes'と応答します。"
    echo "  -a : Apacheのみを対象とします。"
    echo "  -p : PHP-FPMのみを対象とします。"
    echo "  -h : ヘルプを表示します。"
    echo "引数なしの場合は、ApacheとPHP-FPMの両方が対象となります。"
    exit 0
}

# --- 引数解析 ---
while getopts "yaph" opt; do
  case $opt in
    y)
      AUTO_YES=true
      ;;
    a)
      if ! $EXCLUSIVE_MODE; then
          # -a or -p が初めて指定された場合、排他モードにしてデフォルトをリセット
          RESTART_APACHE=false
          RESTART_PHP=false
          EXCLUSIVE_MODE=true
      fi
      RESTART_APACHE=true
      ;;
    p)
      if ! $EXCLUSIVE_MODE; then
          # -a or -p が初めて指定された場合、排他モードにしてデフォルトをリセット
          RESTART_APACHE=false
          RESTART_PHP=false
          EXCLUSIVE_MODE=true
      fi
      RESTART_PHP=true
      ;;
    h)
      usage
      ;;
    \?)
      # 不正なオプション
      usage
      ;;
  esac
done

# PHP-FPMのサービス名とコマンド名を変数から生成
PHP_FPM_SERVICE="php${PHP_VERSION}-fpm"
PHP_FPM_COMMAND="php-fpm${PHP_VERSION}"

#
# サービスを再起動し、ステータスを表示する関数
#
restart_and_check_service() {
    local service_name="$1"
    local service_label="$2" # 表示用の名前
    local confirm_action="n"

    if [ "$AUTO_YES" = true ]; then
        confirm_action="y"
        echo "${service_label} を再起動します... (-yオプションにより自動確認)"
    else
        read -p "${service_label} を再起動しますか? (y/n): " confirm_action
    fi

    if [[ "$confirm_action" =~ ^[Yy]$ ]]; then
        if [ "$AUTO_YES" = false ]; then
          echo "--- ${service_label} を再起動します... ---"
        fi
        if ! systemctl restart "$service_name"; then
            echo "エラー: ${service_label} の再起動に失敗しました。"
        else
            echo "${service_label} が正常に再起動されました。"
            echo
            echo "==== ${service_label} ステータス ===="
            systemctl status "$service_name" --no-pager
            echo "=================================="
        fi
    else
        echo "${service_label} の再起動はキャンセルされました。"
    fi
    echo
}

# スクリプトを root ユーザーで実行しているかチェック
if [ "$EUID" -ne 0 ]; then
    echo "エラー: このスクリプトは root 権限で実行する必要があります。"
    exit 1
fi

# 1. 有効なサイト設定とドメインを表示
# このセクションは引数に関わらず常に表示される情報として実行します
echo "==== 有効なサイト設定ファイル ===="
if [ -z "$(ls -A "$SITES_DIR" 2>/dev/null)" ]; then
    echo "サイト設定が存在しません。"
else
    shopt -s nullglob
    for site in "$SITES_DIR"/*; do
        echo "設定ファイル: $(basename "$site")"
        entries=$(grep -hi -E "^\s*(ServerName|ServerAlias)\s+" "$site" | sed -E 's/^[[:blank:]]+//;s/[[:blank:]]*#.*//' | awk '{
            original_directive = $1
            directive = tolower(original_directive)
            proper_directive = (directive == "servername") ? "ServerName" : \
                               (directive == "serveralias") ? "ServerAlias" : original_directive
            for (i=2; i<=NF; i++) {
                domain = tolower($i)
                sub(/[;,]*$/, "", domain)
                gsub(/^[[:blank:]]+|[[:blank:]]+$/, "", domain)
                if (domain) {
                    printf "%s %s\n", proper_directive, domain
                }
            }
        }' | sort -u)
        if [ -z "$entries" ]; then
            echo "  ※ ServerName/ServerAliasが定義されていません"
        else
            echo "$entries" | sed 's/^/  /'
        fi
        echo
    done
    shopt -u nullglob
fi
echo "=================================="
echo

# 2. 構文チェック
echo "--- 構文チェック ---"
SYNTAX_OK=true

# Apache構文チェック
if [ "$RESTART_APACHE" = true ]; then
    echo "Apache の構文をチェックしています..."
    if ! apachectl configtest 2>&1 | grep -q "Syntax OK"; then
        echo "エラー: Apacheの構文エラーが検出されました。"
        apachectl configtest # エラー詳細を再表示
        SYNTAX_OK=false
    else
        echo "Apacheの構文は正常です。"
    fi
    echo
fi

# PHP-FPMの存在確認と構文チェック
PHP_FPM_ENABLED=false
if [ "$RESTART_PHP" = true ]; then
    if systemctl list-units --type=service --all | grep -q "${PHP_FPM_SERVICE}.service"; then
        if command -v "$PHP_FPM_COMMAND" &>/dev/null; then
            PHP_FPM_ENABLED=true
            echo "${PHP_FPM_SERVICE} の構文をチェックしています..."
            if ! "$PHP_FPM_COMMAND" -t 2>&1 | grep -q "test is successful"; then
                echo "エラー: ${PHP_FPM_SERVICE} の構文エラーが検出されました。"
                "$PHP_FPM_COMMAND" -t # エラー詳細を再表示
                SYNTAX_OK=false
            else
                echo "${PHP_FPM_SERVICE} の構文は正常です。"
            fi
        else
            echo "警告: ${PHP_FPM_SERVICE} サービスは存在しますが、${PHP_FPM_COMMAND} コマンドが見つかりませ
ん。PHP-FPMのチェックはスキップします。"
            RESTART_PHP=false # 実行フラグをオフにする
        fi
    else
        echo "情報: ${PHP_FPM_SERVICE} サービスが見つかりません。PHP-FPM関連の処理はスキップします。"
        RESTART_PHP=false # 実行フラグをオフにする
    fi
    echo
fi

# 構文エラーがあれば処理を中断
if [ "$SYNTAX_OK" = false ]; then
    echo "構文エラーが検出されたため、処理を中断します。"
    exit 1
fi
echo "--------------------"
echo

# 3. サービスの再起動
if [ "$RESTART_APACHE" = false ] && [ "$RESTART_PHP" = false ]; then
    echo "再起動対象のサービスがありません。"
    exit 0
fi

# Apacheの再起動
if [ "$RESTART_APACHE" = true ]; then
    restart_and_check_service "apache2" "Apache"
fi

# PHP-FPMの再起動
if [ "$RESTART_PHP" = true ] && [ "$PHP_FPM_ENABLED" = true ]; then
    restart_and_check_service "$PHP_FPM_SERVICE" "$PHP_FPM_SERVICE"
fi

スクリプトの動き

  1. sudo bash apache2-check.shとすることで
  2. A@acheを起動するか、PHP-FPMを起動するかの2択が生まれます。
  3. -yオプションの場合は全てyにしてプロンプトを省略します。
  4. -aでApacheのみの再起動-pはPHP-FPMのみの再起動

と、柔軟かつやりやすいスタイルへと仕上がりました。

ケーススタディ・ModSecurity影響下でファイルアップロードできない事象に対応

概要

Redmineでファイルをアップロードしようとした際、ModSecurity (WAF) によってブロックされ、エラーになる事象が発生しました。
その原因と事象解決のメモです。

環境

  • Ubuntu 24.04
  • Apache 2.4
    • RedmineをApacheのMod_Passangerで稼働
  • Redmine 5.1
  • ModSecurity v2 / OWASP Core Rule Set (CRS)

事象の確認

環境をWebArenaからXServerに移行した直後。
Redmineのチケットでファイルをアップロードしようとすると、ファイルアップロードのプログレスバーが完了せず、アップロードできません。

原因の特定

こういうときの答えはたいがい、エラーログに書かれているのでそれを確認します。

見つけたログ(IPアドレスやホスト名は改変済み):

[Wed Aug 13 12:47:21.713637 2025] [security2:error] [pid 11190] [client AAA.BBB.CCC.DDD:40404] ModSecurity: Request body no files data length is larger than the configured limit (131072). [hostname "hoge.example.com"] [uri "/uploads.js"] [unique_id "aJwKye92u8EKc4H_FxCb5QAAABQ"], referer: https://hoge.example.com/issues/123

Request body no files data length is larger than the configured limit (131072)

これは、「ファイル以外のリクエストデータ(no files data)のサイズが、設定された上限値(131072バイト = 128KB)を超えています」という意味です。

Redmineは、ファイルをアップロードする際、ファイルそのものとは別に、チケットの題名や説明といったテキスト情報も同時にサーバーへ送信します。このテキスト情報の合計サイズが、ModSecurityのデフォルトの上限値である128KBを超えてしまったため、攻撃と誤認され、ブロックされる。

というのがAI(Gemini)の回答。

既にApacheのバーチャルサイトの.confファイルには

SecRequestBodyInMemoryLimit 524288000
SecRequestBodyLimit 524288000

と記述していますが、サーバの入れ替えと同時にModSecurityのCRSをアップデートしたことで設定が足りなかったようです。

ここまで分かれば、解決まであと少し。

対処手順

設定ファイルのバックアップ

ModSecurityのメイン設定ファイルで、SecRequestBodyNoFilesLimitの上限値を設定で上書きしていきます。

  • エラーを起こしているバーチャルサイトの.confファイルのバックアップ
sudo cp -pi /etc/apache2/site-available/redmine.conf /path/to/backup/directory/redmine.conf.$(date +%Y%m%d)

.confの名前やバックアップディレクトリは自分の環境に合わせます。

  • バックアップ確認
diff -u /path/to/backup/directory/redmine.conf.$(date +%Y%m%d) /etc/apache2/site-available/redmine.conf

差分がなければ(エラーがなければ)バックアップ成功。

上限値を修正する

上述した設定ファイルを以下のように修正。

修正前(例):

SecRequestBodyInMemoryLimit 524288000
SecRequestBodyLimit 524288000

修正後(例):

SecRequestBodyInMemoryLimit 524288000
SecRequestBodyLimit 524288000
SecRequestBodyNoFilesLimit 524288000

両方の値を同じにしておくと、管理が分かりやすくなります。

なお、500MBとしているのは一時的に大きなファイルを置くこともあるからです。上限値は自分の運用方針やストレージ量に合わせましょう。

修正確認

  • ファイル修正後の差分
diff -u /path/to/backup/directory/redmine.conf.$(date +%Y%m%d) /etc/apache2/site-available/redmine.conf
  • 差分例
SecRequestBodyInMemoryLimit 524288000
SecRequestBodyLimit 524288000
+SecRequestBodyNoFilesLimit 524288000

と、追記した行が出てくることを確認します。

設定反映&動作確認

  • confファイルの構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • Webサービス(Apache)再起動
sudo systemctl restart apache2.service
  • Webサービス(Apache)再起動確認
systemctl status apache2.service

Running(active)を確認します。

  • 動作確認

Redmineのチケット作成/編集画面でファイルが正常にアップロードできるようになれば対処完了です。

まとめ

新環境の構築時、最新のCRSを導入したため、旧環境をそのまま引き継いだというのが直接的な原因でした。
こういう罠があちこちに潜んでいるので、何かあったらログを見て事象を確かめるのが事象解消の近道です。

confファイルのチューニングでPassangerのメモリ量を節約。

WebArenaからXServerに移行し、メモリを6GBまで増強したものの、スワップの使用量増加が止まらず。

これをチューニングによりなんとか抑えられたというメモ書きです。

経緯

  1. スペックの負荷状況を調べるためにGrowiを導入
  2. 十分な性能が見込めたためにNextcloudやBookStackなどを入れていく
  3. それでも稼働していったのでRedmineの移行を終える。
  4. Redmineを入れた途端、スワップ使用率が高くなった。

環境

  • Ubuntu / Debian系OS
  • Apache 2.4
  • libapache2-mod-passengerがインストール済み
  • Redmineを複数稼働

チューニングの目的

上記環境のmod_passengerは、筆者のように複数のアプリケーションを、リソースが限られたVPSで運用する場合(いわゆる逸般的な使い方)は、メモリを過剰に消費し、サーバー全体のパフォーマンスを低下させる可能性があります。

事実、free -hコマンドで、Swapの使用量が放置しているのに高まってきたという状況でした。

そこで、リクエストがあれば都度、プロセスを増やしてしまうpassengerの機能をチューニングで抑制します。

さっくりとした手順

  1. passengerの設定ファイルのバックアップを行います。
  2. ファイルの修正によるチューニングを行います。
  3. 設定を反映させ、状況を確認します。

Passengerの設定ファイルをのバックアップ

※この設定は、バーチャルサイト個別設定ではなく、一括で設定を行います。

なぜなら、Passenger関連のディレクティブが、<VirtualHost>ブロックの中に書くことができないからです。

  • ファイルのバックアップ
sudo cp -pi /etc/apache2/mods-available/passenger.conf /path/to/backup/direcotry/passenger.conf.$(date +%Y%m%d)

バックアップディレクトリは自分の環境に合わせます。(筆者環境:/etc/conf/backup)

  • ファイルのバックアップ確認
diff -u -pi /path/to/backup/direcotry/passenger.conf.$(date +%Y%m%d) /etc/apache2/mods-available/passenger.conf 

差分がなく、エラーがなければバックアップはできています。

メモリ管理ディレクティブを追記

上記passenger.confファイルを開き、<IfModule mod_passenger.c>...</IfModule>ブロックの中に、以下の設定を追記します。

<IfModule mod_passenger.c>
  PassengerRoot /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini
  PassengerDefaultRuby /usr/bin/ruby

  # --- ▼ここから追記▼ ---
  # Passengerが起動するRubyプロセスの最大数を4に制限
  PassengerMaxPoolSize 4
  # 各アプリケーションごとに起動する最大プロセス数を1に制限
  PassengerMaxInstancesPerApp 1
  # プロセスが300秒(5分)アイドルだったら停止させる
  PassengerPoolIdleTime 300
  # --- ▲ここまで追記▲ ---
</IfModule>

各ディレクティブの解説

ディレクティブ説明
PassengerMaxPoolSizePassengerがサーバー全体で起動するRubyプロセスの最大総数です。サーバーのメモリ上限を超えないよう、搭載メモリ量に応じて設定します。
PassengerMaxInstancesPerApp各アプリケーションごとに起動できるプロセスの最大数です。例えば1に設定すると、一つのRedmineサイトは同時に1つのプロセスしか使えなくなります。
PassengerPoolIdleTimeプロセスがここで指定した秒数以上アクセスがない場合、Passengerはそのプロセスを停止させ、メモリを解放します。

設定の反映

ファイルを保存した後、Apacheを再起動して設定を反映させます。

  • 構文確認
sudo apache2ctl configtest

Syntax Okを確認します。エラーが出たらその箇所を確認し、Syntax OKが出るまで修正します。

  • Apache再起動
sudo systemctl restart apache2.service
  • Apache再起動確認
systemctl status apache2.service

Running(Active)を確認します。

動作確認

この状態で一晩ほど様子を見ました。

free -h
               total        used        free      shared  buff/cache   available
Mem:           5.8Gi       3.3Gi       551Mi       141Mi       2.3Gi       2.4Gi
Swap:          2.0Gi       1.5Mi       2.0Gi

のまま。チューニング前はSwapを100~500MBも使用していたことから、動作は安定。設定は有効のようです。

PHP8.3をPHP-FPMで動かすための設定

概要

Nextcloud等のLAMP環境で利用するPHPを、PHP-FPM (FastCGI Process Manager) を使ってApacheと連携させる形でインストール・設定します。
php-fpmを利用すると、PHPの処理がApacheのプロセスから分離されるため、パフォーマンスの向上や、より柔軟なリソース管理が期待できます。

※筆者の好みでaptitudeを用いています。適宜、aptに読み替えてください。

実施した環境

  • Ubuntu 24.04
  • Apache 2.4
  • MySQL 8

さっくりとした手順

  1. レポジトリを追加します。
  2. PHPおよびPHP-FPMパッケージをインストールします。
  3. Apacheの連携用モジュールを設定します。
  4. PHPのパフォーマンス設定(OPcache等)を行います。
  5. 各Webサイト(バーチャルホスト)でPHP-FPMを有効化します。
  6. サービスを再起動し、動作を確認します。

手順詳細

レポジトリ追加とアップデート

最新のPHPバージョンを利用するため、ondrej/phpリポジトリを追加します。

  • PHPレポジトリの追加
sudo add-apt-repository ppa:ondrej/php
  • パッケージ全体のアップデート
sudo aptitude update

PHPパッケージのインストール

PHP本体と、php-fpm、そしてWebアプリケーションで一般的に必要とされる拡張機能をインストールします。

  • PHP本体とFPM
sudo aptitude install php8.3 php8.3-fpm
  • 周辺モジュール (APCuとMemcached)
sudo aptitude install memcached php8.3-apcu
  • Webアプリに必要な周辺モジュール (MySQLを使う場合)
sudo aptitude install php8.3-{opcache,pdo,bcmath,calendar,ctype,fileinfo,ftp,gd,intl,json,ldap,mbstring,mysql,posix,readline,sockets,bz2,tokenizer,zip,curl,iconv,phar,xml,imagick,gmp,redis-server}
  • インストール確認
php -v

PHP 8.3.xのように、インストールしたバージョンが表示されることを確認します。

Apache連携モジュールの設定

php-fpmでPHPを動かすため、従来のmod_phpを無効化し、代わりにphp-fpmと通信するためのproxy_fcgiモジュールを有効化します。

  • mod_phpを無効化
sudo a2dismod php8.3
  • 必要なモジュールを有効化
sudo a2enmod proxy_fcgi setenvif

OPcacheとAPCuの有効化

Nextcloudなどのアプリケーションでは、パフォーマンス向上のためこれらのキャッシュ設定が推奨(あるいは必須)となります。

  • 設定ファイル待避
sudo mv /etc/php/8.3/mods-available/opcache.ini /path/to/backup/directory/opcache.ini.$(date +%Y%m%d)
sudo mv /etc/php/8.3/mods-available/apcu.ini /path/to/backup/directory/apcu.ini.$(date +%Y%m%d)

任意のバックアップディレクトリを指定します。(筆者環境/etc/conf_backup)

  • 設定ファイル差し替え
cat <<- __EOF__ | sudo tee /etc/php/8.3/mods-available/opcache.ini > /dev/null
; configuration for php opcache module
; priority=10
zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.memory_consumption=256
opcache.save_comments=1
opcache.revalidate_freq=1
__EOF__
cat <<- __EOF__ | sudo tee /etc/php/8.3/mods-available/apcu.ini > /dev/null
extension=apcu.so
[apcu]
apc.enabled=1 apc.shm_size=32M apc.ttl=7200 apc.enable_cli=1 apc.serializer=php
__EOF__

※ メモリ量などは環境に合わせます。

各WebサイトでのPHP-FPM連携設定

各WebサイトのApache設定ファイル(.conf)で、PHPへのリクエストをphp-fpmに渡すように設定します。この設定がなければPHPスクリプトをサーバが解釈せず、動きません。

  • 設定例 (/etc/apache2/sites-available/hoge.confなど)
    <VirtualHost>ブロックの中に、以下の<FilesMatch>ブロックを追記します。
    <VirtualHost *:443>
        ServerName hoge.example.com
        # ...

        <FilesMatch \.php$>
            # SetHandlerで、phpファイルのリクエストをFastCGIプロキシに渡す
            SetHandler "proxy:unix:/var/run/php/php8.3-fpm.sock|fcgi://localhost/"
        </FilesMatch>

        # ...
    </VirtualHost>

このSetHandlerディレクティブが、Apacheに来たPHPへのリクエストを、バックグラウンドで動いているphp-fpmのプロセスに転送する役割を担います。

サービスの再起動と動作確認

設定を反映させるため、Apacheとphp-fpmの両方を再起動します。

  • サービス再起動
sudo systemctl restart php8.3-fpm.service
sudo systemctl restart apache2.service
  • 動作確認
    mod_phpは無効化されているため、a2queryではdisabledと表示されるのが正しい状態です。代わりに、php-fpmサービスが稼働していることを確認します。
systemctl status php8.3-fpm.service
`active (running)`と表示されていれば、PHP-FPMの導入と設定は完了です。

XServer VPS切り替え後のトラブル。(ログの肥大化によるサービス停止と対応・恒久的対応策)

概要

2025/08/12 朝、管理しているWebサーバーにSSHで接続できなくなり、Webサイトも全て閲覧できなくなる障害が発生しました。本記事は、その原因究明から、応急処置、そして恒久対策までの一連の流れを記録したものです。

障害発生時の状況

  • Webサイトにアクセスすると、タイムアウトエラーになる。
  • SSHでログインしようとしても、接続ができない。
  • VPSの管理コンソールから再起動をかけると、redis-serverの停止処理(DBの保存)でタイムアウトし、正常にシャットダウンできない。
  • 強制再起動後、一時的に復旧するものの、しばらくすると再び応答がなくなる。

止まっていたサービス

  • mongod
  • redis-server
  • elasticsearch
  • growi

原因の特定手順

切り分け中に原因判明

障害の切り分け中、Wasabiクラウドストレージをマウントしようとした際に、以下のエラーが発生しました。

  • クラウドストレージのマウントを実行
sudo mount -a
mount.s3fs: unable to access MOUNTPOINT /mnt/wasabi: No space left on device

No space left on device(デバイスに空き領域がありません)」というこのエラーメッセージ。150GBもディスク容量があるのになぜ……? 思いつつ調査を行います。

ディスク使用率の確認

上記のエラーを受け、ディスクの空き容量を確認しました。

df -h

実行結果:

Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       145G  145G     0 100% /

この結果から、**ルートディレクトリ(/)のディスク使用率が100%**になり、空き容量がゼロになっていることが、数値的にも裏付けられました。

容量を圧迫しているディレクトリの調査

次に、どのディレクトリが容量を使い果たしているのかを調査しました。

sudo du -h -d 1 /

実行結果(抜粋):

125G    /var

/varディレクトリが125GBを占めていることが分かりました。さらに詳細に調査を進めます。

sudo du -h -d 1 /var/log

実行結果(抜粋):

122G    /var/log/apache2

/var/log/apache2が122GBという、異常なサイズになっていることが特定できました。

肥大化したログファイルの特定

その後、/var/log/apache2ディレクトリの中身を確認しました。

ls -lh /var/log/apache2

実行結果(抜粋):

-rw-r-----  1 root adm   61G  8月 12 08:09 modsec_audit.log
-rw-r-----  1 root adm   59G  8月 11 00:15 modsec_audit.log.1

**ModSecurityの監査ログ(modsec_audit.log)**が、わずか2日間で120GB以上にまで肥大化していたことが、直接の原因であると確定しました。

ここまで分かれば対処はもうすぐです。

対処手順

ステップ1:応急処置(空き容量の確保)

まず、サーバーを正常に動作させるため、巨大化した監査ログを削除し、空き容量を確保します。

  • 巨大なログファイルを削除
sudo rm /var/log/apache2/modsec_audit.log*
  • Apacheを再起動して、ファイルハンドルを解放
sudo systemctl restart apache2.service

ステップ2:恒久対策(監査ログの無効化)

modsec_audit.logは、攻撃の詳細な分析やデバッグには役立ちますが、今回のようにログがディスクを圧迫し、サーバーダウンを引き起こしますため、この設定をオフにします。

  1. ModSecurityのメイン設定ファイルを開きます。 sudo nano /etc/modsecurity/modsecurity.conf
  2. SecAuditEngineの値をOffに変更します。 修正前: SecAuditEngine RelevantOnly 修正後: SecAuditEngine Off
  3. Apacheをリロードして設定を反映させます。 sudo systemctl reload apache2.service

その後、念のためサーバそのものを

```bash
sudo reboot
```

で、状況が元に戻っていることを確認しました。

まとめ

今回の障害は、ModSecurityの監査ログが有効になったまま放置され、そのログがログローテーションの対象になっていなかったために、ディスク容量を100%使い果たしたことが原因でした。

前述したとおり、よりよいスペックのサーバに引っ越した後の出来事だけに、かなりの冷や汗ものでした。

VPSをWebArena→XServerへと切り替えたときのメモ。

2024年9月から利用してきた WebARENA VPS を XServer VPS に切り替えました。
理由は大きく分けて以下の3点です。

1. 物理的リソースの逼迫

旧環境では、以下のアプリが同時稼働していました。

この構成ではメモリ使用量が常に高止まりし、スワップ発生も頻繁。
安定運用のためには、より余裕のある環境が必要でした。

2. キャンペーン終了によるコスト増

WebARENA VPS の 4GB プランはキャンペーン中であれば 月額約980円 と魅力的でしたが、
終了後は 月額約1,600円 に上昇。コスト面の優位性が失われました。

3. XServer VPS を選んだ理由

  • 既にこの WordPress をホストしている会社で信頼性が高い
  • 同価格帯でも メモリ6GB と余裕がある
  • NVMe SSD 搭載でストレージ速度が段違い
  • 年額契約でキャンペーン終了後も料金安定
  • 最終的にWebARENA 4GB プランより安くなる

サーバ移行のタイムライン

  • 8/03 : 契約、初期設定(UFW / fail2ban)
  • 8/04 : 負荷試験を兼ねて Growi 導入(Apache / MongoDB / Elasticsearch / Node)
  • 8/05 : MySQL 導入
  • 8/06 : ModSecurity 導入
  • 8/07 : PHP-FPM 導入、Wasabi 接続、Nextcloud・BookStack・Laravel アプリ移行
    • この時点で「Growiを削除しなくてもいける」と確信
  • 8/08 : Redmine 6.0 新規構築に挑戦 → DB移行でエラー
    • 方針転換し Redmine 5.1 を移植(公開用)
  • 8/09 : 残りの Redmine 2件も移行完了、DNS 切替、WebARENA 解約

結果: 7日間で全サービス移行完了。Growi 常駐のまま全サイトを載せられたのは想定外というか、嬉しすぎる誤算でした。

スペック比較

項目WebARENA(旧)XServer(新)変化
CPUXeon E3-12xx (2011年)AMD EPYC Milan (2021年)大幅性能向上
vCPUコア数4コア4コア同等
メモリ4GB6GB1.5倍
ストレージ80GB SSD150GB NVMe SSD約1.9倍+高速化
ネットワーク500Mbps100Mbps低下
月額料金約1,630円(終了後)1,439円(年額換算)安価

新環境のメモリ状況

free -h
               total        used        free      shared  buff/cache   available
Mem:           5.8Gi       2.1Gi       1.6Gi       121Mi       2.5Gi       3.6Gi
Swap:          2.0Gi       623Mi       1.4Gi

Growi(MongoDB + Elasticsearch + Node)常駐のままで 3.6GiB available
旧環境より明確に余裕があり、スワップも軽減。

感想と今後

  • 前払いの安心感:年額払いで価格変動リスクなし
  • サービス統合効果:VPSとレンタルサーバを同社で一元管理
  • 展望:Redmine 6.0 アップデート挑戦、または5.1での堅牢運用

個人的に、NWの転送速度をサーバのリソースで解決するパワープレイができたのが安心です。

Apache全般のログローテーション設定。

サーバの移設中なので、ログに関しての記録を残しています。

概要

Webサーバ全般のメンテナンスで特に必要なのは

「ログを適切に管理する」

です。

  • 不審なアクセスの兆候
  • 自分のやらかし

を目の当たりにできる手段だからです。(特に後者に助けられたことは幾度となくあります)

そのため、Webサーバ上のログのローテーションを行い、履歴を追いやすくし、更にサーバの容量も削減するログローテーションを行います。

ログローテーションとは

ログファイルが肥大化するのを防ぐために、古いログを一定のルールに基づいて新しいログに切り替えたり、削除したりする仕組みのことです。

hogeサイトのアクセス記録である/var/log/access.logというファイルは、何も設定していないとログが記録され続けます。これによって以下の問題が起きます。

  • ログの肥大化によるサーバ容量の圧迫
    • テキストファイルぐらいと思う方がいるかも知れませんが、昨今の不正アクセスやAIサイトのクローラーのアクセス頻度は異常です。個人サイト程度でも一日数十MBはよくあります。
    • RedmineのようなWebアプリの場合、そのログは数百MBになります。
    • → このため、指定した期間でログを新しいログに差し替え、古いものは削除していくという仕組みを取ります。
  • ログの追記によるサーバのリソース増加
    • 追記自体の負荷は小さいですが、そのログをgrepなどで分析・検索する際に、巨大な単一ファイルだと非常に多くのメモリとCPUを消費します。
    • → ログを適切に分割(ローテーション)しておくことで、日々のログ分析が高速かつ軽量に行えるようになります。

これを行うべきタイミング

「Webサーバに新たなWebサイトやサービスを立ち上げたとき」 です。
試験運用などでもログは問題が起きたときのヒントとなるからです。

環境

  • Ubuntu 24.04
  • Apache 2.4
    • apt(aptitude)によるパッケージ管理でインストールしているため、Apacheの実行ユーザはwww-dataです。

備考

ディストリビューションが違ってもこの手順は有効ですが、RockyやAlma等のRHEL系のApacheの実行ユーザは通常はapacheであることに注意しましょう。

さっくりとした手順

  1. (念のため)設定を行うサイトのログをwww-dataに変えます。
  2. ログローテーションのファイルを作成します。
  3. ログローテートの設定が有効かを確認します。

ここでは、hogeサイト(/var/log/hoge)のログをローテーションする方法です。

サイトのログの実行ユーザの変更(所有者変更)

これは、Redmineのlogプラグインのように、Webインタフェース上からログを閲覧できるプラグインがある場合、Apacheを実行しているサービス自体が参照できるようにするためです。

  • chownによる所有者変更
sudo chown -R www-data:www-data /var/log/hoge

ログ一式の所有者を変えます。ログファイルの形式は自分の環境に合わせます。

  • 所有者変更確認
ls -l /var/log/hoge

ログファイルの所有者とグループがwww-dataであることを確認します。

ログローテーションファイルの作成

ファイル名やログのパスは自分の環境に合わせます。 ここを間違えると元も子もありません。

cat <<- __EOF__ | sudo tee -a /etc/logrotate.d/hoge
/var/log/hoge/*.log {
    daily
    missingok
    rotate 10
    compress
    copytruncate
    notifempty 
    su www-data www-data
__EOF__

これは、以下を行います。

  • daiy
    • 1日ごとにローテーション
  • missingok
    • ログファイルが存在しなくてもエラーにしない
  • rotate 10
    • 10世代分の古いログファイルを保持する(それ以降は破棄)
  • compress
    • 古いログファイルをgzipで圧縮する
  • copytruncate
    • ログをコピーしてから元のファイルを空にする(サービス無停止でログ切替)
  • notifempty
    • ログファイルが空の場合はローテーションしない
  • su www-data www-data
    • ローテーション後のファイルの所有者を指定

他、週ごとやログのファイル形式(日付形式)などを設定できます。こちらは運用に合わせてください。

ログローテーションの確認

  • 設定確認
sudo logrotate -dv /etc/logrotate.d/hoge

※先ほど作成したファイル名に合わせます。

ここでエラーがなければログローテーションはできています。

  • ログローテーションの即時実行

すぐにローテーションを確かめる場合は以下を実行します。強制的にログローテーションを行うコマンドです。

sudo logrotate -f /etc/logrotate.d/hoge

このコマンドを実行後、/var/log/hoge/ディレクトリ内にaccess.log.1.gzのようなファイルが作成され、元のaccess.logが空になっていれば、設定は完璧です。

CPUとメモリ情報を表示するワンライナー。

概要

サーバスペックの要となる

  • CPU
  • コア数
  • メモリ量
  • スワップ量

を一度に表示するワンライナーです。

ワンライナー内容

awk 'BEGIN {FS=":"; OFS="\t"} /^model name/ && !cpu_model {cpu_model=$2; gsub(/^ */, "", cpu_model)} /^processor/ {cores++} /^MemTotal/ {mem_total=$2} /^MemAvailable/ {mem_avail=$2} /^SwapTotal/ {swap_total=$2} END {printf "CPUモデル\t: %s\n", cpu_model; printf "CPUコア数\t: %s\n", cores; printf "合計メモリ\t: %.2f GiB\n", mem_total/1024/1024; printf "利用可能メモリ\t: %.2f GiB\n", mem_avail/1024/1024; printf "合計スワップ\t: %.2f GiB\n", swap_total/1024/1024}' /proc/cpuinfo /proc/meminfo

実行結果

CPUモデル       : AMD EPYC-Milan Processor
CPUコア数       : 4
合計メモリ      : 5.78 GiB
利用可能メモリ  : 5.34 GiB
合計スワップ    : 2.00 GiB

と、分かりやすい表示で一気に表示してくれます。

出先での記録環境。

中古で購入したThinkPadがもたらしたのは、「出かけることのハードルの低さ」です。

常に行う

  • 日記
  • ブログの更新
  • 体調、入出金などライフログの記録

が、自宅で行うことなく可能になったからです。

喫茶店で食事を取りながらの入出金記録を行い(この食事代も記録しながら)

また、出先に椅子とテーブルがあると分かっていれば、このように、アナログな記録も同時に実施することができます。

記録するための道具がそろっていれば、記録する環境は問わない(というか状況に合わせて選べる)

というのが分かったのが、このThinkPadでの収穫です。

Page 1 of 56

Powered by WordPress & Theme by Anders Norén