CentOS Stream 9でNginxをソースからビルドしてdnf版からダウンタイムなしで切り替える

本番環境でNginxをソースからビルドしたときの覚書。
Gemini 2.5 Proと対話。

環境: CentOS Stream 9


1. 事前準備

dnf版Nginxのビルドオプションを確認。
改行して見やすくする。
# nginx -V 2>&1 | sed 's/ --/\n--/g'

nginx version: nginx/1.20.1
built by gcc 11.5.0 20240719 (Red Hat 11.5.0-7) (GCC)
built with OpenSSL 3.5.0 8 Apr 2025 (running with OpenSSL 3.5.1 1 Jul 2025)
TLS SNI support enabled
configure arguments:
--prefix=/usr/share/nginx
--sbin-path=/usr/sbin/nginx
--modules-path=/usr/lib64/nginx/modules
--conf-path=/etc/nginx/nginx.conf
--error-log-path=/var/log/nginx/error.log
--http-log-path=/var/log/nginx/access.log
--http-client-body-temp-path=/var/lib/nginx/tmp/client_body
--http-proxy-temp-path=/var/lib/nginx/tmp/proxy
--http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi
--http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi
--http-scgi-temp-path=/var/lib/nginx/tmp/scgi
--pid-path=/run/nginx.pid
--lock-path=/run/lock/subsys/nginx
--user=nginx
--group=nginx
--with-compat
--with-debug
--with-file-aio
--with-http_addition_module
--with-http_auth_request_module
--with-http_dav_module
--with-http_degradation_module
--with-http_flv_module
--with-http_gunzip_module
--with-http_gzip_static_module
--with-http_image_filter_module=dynamic
--with-http_mp4_module
--with-http_perl_module=dynamic
--with-http_random_index_module
--with-http_realip_module
--with-http_secure_link_module
--with-http_slice_module
--with-http_ssl_module
--with-http_stub_status_module
--with-http_sub_module
--with-http_v2_module
--with-http_xslt_module=dynamic
--with-mail=dynamic
--with-mail_ssl_module
--with-pcre
--with-pcre-jit
--with-stream=dynamic
--with-stream_ssl_module
--with-stream_ssl_preread_module
--with-threads
--with-cc-opt='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection'
--with-ld-opt='-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -Wl,-E'

必要なライブラリをインストール。
参考: nginx/nginx: The official NGINX Open Source repository.
# dnf install gcc make zlib-devel pcre2-devel openssl-devel libmaxminddb-devel redhat-rpm-config

redhat-rpm-configは--with-cc-optと--with-ld-optで指定されているspecファイルをインストールするため。


2. Nginxをソースからビルド

前に開発環境で行った記事を参考に。
参考: CentOS Stream 10でNginxをソースからビルド

dnfでインストールしたときのビルドオプションにある「--with-cc-opt」と「--with-ld-opt」は積極的に使うべきとGemini先生が言うので、同じ値を設定してビルドした。

./configure \
--prefix=/etc/nginx \
--conf-path=/etc/nginx/nginx.conf \
--sbin-path=/usr/sbin/nginx \
--pid-path=/run/nginx.pid \
--lock-path=/run/lock/subsys/nginx \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--user=nginx \
--group=www \
--with-compat \
--with-pcre \
--with-file-aio \
--http-client-body-temp-path=/var/lib/nginx/tmp/client_body_temp \
--http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi_temp \
--http-proxy-temp-path=/var/lib/nginx/tmp/proxy_temp \
--http-scgi-temp-path=/var/lib/nginx/tmp/scgi_temp \
--http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi_temp \
--with-http_v2_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_realip_module \
--with-http_slice_module \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_sub_module \
--without-mail_imap_module \
--without-mail_pop3_module \
--without-mail_smtp_module \
--with-threads \
--add-module=./ngx_http_geoip2_module \
--add-module=./nginx-module-vts \
--add-module=./ngx_cache_purge \
--with-cc-opt='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' \
--with-ld-opt='-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -Wl,-E'

ビルドして確認する。
# make
# objs/nginx -V

nginx version: nginx/1.28.0
built by gcc 11.5.0 20240719 (Red Hat 11.5.0-11) (GCC)
built with OpenSSL 3.5.1 1 Jul 2025
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --conf-path=/etc/nginx/nginx.conf --sbin-path=/usr/sbin/nginx --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --user=nginx --group=www --with-compat --with-pcre --with-file-aio --http-client-body-temp-path=/var/lib/nginx/tmp/client_body_temp --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi_temp --http-proxy-temp-path=/var/lib/nginx/tmp/proxy_temp --http-scgi-temp-path=/var/lib/nginx/tmp/scgi_temp --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi_temp --with-http_v2_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_realip_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --without-mail_imap_module --without-mail_pop3_module --without-mail_smtp_module --with-threads --add-module=./ngx_http_geoip2_module --add-module=./nginx-module-vts --add-module=./ngx_cache_purge --with-cc-opt='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' --with-ld-opt='-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -Wl,-E'


ちなみにFreeBSDの場合は、Ports Collectionが自動設定してくれるので./configureを実行する必要がない。
Ports Collectionがどのようなオプションを設定しているかを確認するコマンド。
# cd /usr/ports/www/nginx
# make -V CFLAGS

-O2 -pipe  -I/usr/local/include -fstack-protector-strong -fno-strict-aliasing

# make -V LDFLAGS

 -Wl,-rpath,/usr/local/lib

FreeBSDではOS全体として統合・設計されているので、サードパーティ製ソフトはPortsで管理するのが流儀。
(逆に言うとPortsで管理できないものはインストールすべきではない)


3. dnfでインストールしたNginxとダウンタイムなしで切り替える

別コンソールを開いてhtopを起動して「nginx」でフィルタ表示する。
# htop

現在稼働中のPIDと一致するか確認。
# cat /run/nginx.pid

設定ファイルとNginxバイナリのバックアップ。
aオプション(archiveモード)は権限とディレクトリ構成をできる限り維持する。
# cp -a /etc/nginx /etc/nginx.20250907
# mv /usr/sbin/nginx /usr/sbin/nginx.old

新しいNginxバイナリの配置。
# make install

設定ファイルの構文チェック。
# nginx -t

新しいNginxマスタープロセスを起動 (USR2シグナル)。
参考: Controlling nginx
# kill -USR2 $(cat /run/nginx.pid)

htopでマスタープロセスが増えているのを確認。

nginx.pid.oldbinがあるか確認して、古いプロセスを段階的に終了させる。
(WINCH = Window Change)
# ls /run/
# kill -WINCH $(cat /run/nginx.pid.oldbin)

古いワーカープロセスはすべて終了したのをhtopで確認。
最後に古いマスタープロセス自体を終了させる。
# kill -QUIT $(cat /run/nginx.pid.oldbin)

/run/には新しいPIDのnginx.pidしか残っていないはず。
# ls /run/
# cat /run/nginx.pid

ブラウザでサイトが表示できるか確認する。

wp-login.phpにアクセスすると不安定になっていて再起動したら改善されたので、結局アクセスがあまりない時間帯に実行するのが良さそう。
# reboot


4. dnfでインストールNginxをアンインストール

DNFパッケージの削除する前にサービスファイルの内容をコピーしておく。
# systemctl status nginx
# less /usr/lib/systemd/system/nginx.service

dnf版Nginxをアンインストール。
# dnf remove nginx

/usr/sbin/nginxが削除された
(Gemini先生は削除されないと言ったのに…)。

慌てて再度make installを実行してNginx起動。
# cd /usr/local/src/
# make install
# cd /etc/nginx
# cp ../nginx.20250907/nginx.conf ./
# nginx -t

# vi /etc/systemd/system/nginx.service
# systemctl daemon-reload
# systemctl enable nginx
# systemctl start nginx
# systemctl status nginx

次回dnfから切り替えるときには気を付ける。

後片付け。
# rm /usr/sbin/nginx.old
# rm -rf /etc/nginx.20250907/

.rpmsaveはNginxパッケージをアンインストールした際に生成されるバックアップファイルなので削除する。
# ls /etc/nginx/*.rpmsave
# rm -f /etc/nginx/*.rpmsave

.rpmnewもdnf経由でインストールしたときに生成されるファイルなので削除する。
一応差分を表示して確認する。
# diff /etc/nginx/nginx.conf.default /etc/nginx/nginx.conf.rpmnew
# rm /etc/nginx/nginx.conf.rpmnew


5. ログローテーション設定

dnfアンインストールする前にログローテーション設定をメモるのを忘れていた。
Gemini先生に設定ファイルを生成してもらう。
# vi /etc/logrotate.d/nginx

# Nginxのログファイル(/var/log/nginx/ 内の .log で終わるファイル)を対象とする
/var/log/nginx/*.log {
    # -----------------------------
    # ▼ ローテーションの頻度と世代管理
    # -----------------------------
    # 週に一度、ログファイルをローテーションする
    weekly

    # 104世代分の古いログファイルを保持する (週次 x 104世代 ≒ 2年分)
    rotate 104

    # -----------------------------
    # ▼ ファイルの圧縮と作成
    # -----------------------------
    # 古いログファイルをgzipで圧縮する
    compress

    # 直近の1世代だけは圧縮せず、次回のローテーション時に圧縮する
    # (例: access.log.1 は圧縮せず、access.log.2.gz から圧縮される)
    delaycompress
    
    # ローテーション後、新しい空のログファイルを作成する
    # パーミッション: 640 (-rw-r-----)
    # 所有ユーザー: nginx
    # 所有グループ: www
    create 0640 nginx www

    # -----------------------------
    # ▼ 実行条件と後処理
    # -----------------------------
    # ログファイルが空の場合は、ローテーションしない
    notifempty
    
    # ログファイルが見つからなくてもエラーにしない
    missingok

    # postrotateスクリプトを、対象ファイル(access.log, error.logなど)ごとに毎回実行せず、一度だけ実行する
    sharedscripts

    # ローテーション処理が完了した後に、以下のスクリプトを実行する
    postrotate
        # NginxのプロセスIDファイルが存在する場合のみ、中のコマンドを実行
        if [ -f /run/nginx.pid ]; then
            # 稼働中のNginxプロセスにUSR1シグナルを送り、ログファイルを再オープンさせる
            # これをしないと、Nginxは名前が変わった古いファイルにログを書き込み続けてしまう
            /bin/kill -USR1 `cat /run/nginx.pid`
        fi
    endscript
}

動作テスト。
# logrotate --debug /etc/logrotate.d/nginx


▼ 関連記事