🐈

2022年版 1から始めるTomcat-Apache構成~AWSを添えて~

2022/05/31に公開

はじめに

この記事は、アプリケーションエンジニアがJavaでWebアプリの環境を組んでいくにあたって、Googleさんと相談しながらどうにかこうにかサーバー周りの設定をやってみた内容を記事の形として残したものです。
そのため、専門的にやってきた経験者からは突っ込みどころのあるような内容となっている可能性が高いです。本腰を入れて各ミドルウェアに準じた本を読み、環境を作っていった方が最終的な時間対パフォーマンスは高くなると思われます。スーパーエンジニアからの知見というよりは、とりあえずどうにかこうにかやるしかない状況の中で最低限の品質ラインを担保するためのGoogle検索時間節約記事[1]のようにお考え下さい。
また、以上の経緯から、参考とした情報のURLも多分に含まれております。リンク切れしてたらごめんなさい。

頻出語:「どうにかこうにかやる」について

どうにかこうにかやる
この記事の頻出語。以下2つの意図のうちの1つ、またはその両方をミックスした意図で利用される。

  1. 業務遂行のスキルセット・体系的な技術がきちんと整っていないと自他ともに認めるが、それでも組織的な事情からやらなければならず、知識がない中で書籍やGoogleに頼って職務を遂行すること。
  2. 現代においては代替技術として他に選ぶべきものがあるのはわかっているが、色んな事情からそれが選べない中で、選択可能な範囲で最善を目指すこと。

この記事の状況が最も近い。

類義語:Twitterにおけるエンジニアの「にゃ~ん」という呟き。

想定読者

  • どうにかこうにかサーバー構築をやっていくしかない人。
  • どうにかこうにかサーバー構築をやってみたけど、抜け漏れや勘違いがないかを70%くらいの精度で点検したい人。
  • どうにかこうにかやらなくてもいいけれど、Tomcat-Apache構成で開発を行うにあたって初期にやるべきことを俯瞰して見たい人(Docker Imageをビルドするにあたって、とりあえずやっておく点を把握したいとかを想定)。
  • どうにかこうにかやってみた人を救うために修正・指摘をくれるインフラつよつよな聖人。

注意事項

  • AWS上での構成を行う想定のため、AWSのサービス及びサービスで扱われる用語を解説なく利用します。記事内では詳しく説明しないため、不明な単語が存在する場合は各自ググりをお願いします。
    • 出来る限りAWSを利用しない・他PaaS、IaaSを利用する際にもある程度は適用できる内容を心がけて書いたり、「こうしたよ」というさらっとした説明を流す程度に残すようにします。
    • また、AWSの機能ではなく、サーバー内(今回はEC2インスタンス)の構成に視点を向けるため、意図的にAWSのサービス・機能についての詳細な設定などは省いたり脚注に逃したりしてあります。
  • どうにかこうにかやっている部分や、著者の経験不足を踏まえ、現代のベストプラクティスではない目標設定となっていると思います。ただ、「こういう選択肢も考えていたけど、こういう理由から、この選択肢は消しました」というのは細かく明示していき、Tips的に応用できるような記事を心掛けます。
    • 現代のJavaでのWeb開発においてはほぼSpring bootのEmbedded Server(Tomcat, Jetty)やらPlayのNettyやらが基本だと思うんで、Tomcat+Apacheをわざわざインフラという節理面で区切り、アプリのポータビリティを捨てて使うメリットはあんまりないと思ってます。インフラ弄るスタック持ちとアプリのコード書けるスタック持ちが完全に分離されている、ApacheやNginxなどのWebサーバーとの連携体制を各自で用意できるような時間がない等々、様々などうにかこうにかポイントがあり、なおかつ長期で環境を整えるための第一歩目として特に役立つ記事だと思っているので、最初っからモダンにやったれな環境が用意できそうなら、特定フレームワークの設定項目などを書いた記事を見た方が手順書のたたき台としては役立ちポイントは高いです。特にPlay Framework。
  • Linuxの黒い画面は叩きますが、コマンドとしてはややこしいことしないので、不明な点は各自Googleをご活用ください。

書くこと

  • Webアプリのサーバーを建てる際には、デフォルトのままで置いておくとマズかったり、とりあえず弄っておいた方が良い部分は多数あります。が、そういった設定で検索エンジンのトップに引っかかるものは、セキュリティなどの特定カテゴリにのみに観点を当てたものや、特定のモジュールにのみ観点を当てたものが多い傾向にあるように見えます。そのため、汎用的に利用できる一連の設定項目を記述したもの[1:1]があれば便利そうだなーと考え、この文書を書くに至りました。
    • というわけで、一連の汎用的な設定項目について、調べた範囲で「とりあえずやっておいた方がいい」というものを、実際にやっていく順に書いていきます。
    • 設定項目やコンフィグのキーはライブラリのバージョンアップに伴って変わることがありえます。しかし、バージョンが上がったとしても「課題は同じだが解決方法は違う」というパターンもあるため、「何をやるのか」に付随して、「なぜやるのか」「どうしてこういう結論に至ったのか」についても詳細に書き、破壊的変更への対応・検索のヒントとできるような期待を込めます。

実施環境

「AWSで利用できる[2]」を主眼において構成しています。Linuxディストリビューションによって詳細な設定ファイルのディレクトリ等が異なると思うので、諸所ディストリビューションに合わせて読み替えて設定してください。

  • Amazon Linux 2022
    • EC2を想定しています。AMIは al2022-ami-2022.0.20220504.1-kernel-5.15-x86_64[3]
  • Tomcat 9.0.x系
  • Apache httpd 2.4.x系

ゴール

以下の図のような構成を最終地点としています。

  • 同一インスタンス内にApache, Tomcat同棲の構成
  • HTTPをTCP80番で待ち受け
  • ApacheでAJPコネクターにリバースプロキシ
  • TCP8009番でAJP待ち受け

SSL/TLS対応については、ApacheでHTTPをHTTPSにリダイレクトさせるなどの様々な方法がありますが、今回は前段にALBを置いて、ALB + ACMの機能で処理するようにしています。(いわゆるSSL TerminationとかSSL Offloadingとか呼ばれる手法)

他の選択肢と採用の理由について

この構成において、変えられる点はいくつかあります。

  • Apache, Tomcatを同一サーバー内に置くか?それとも別サーバーに置くか?
    • 同一サーバーの場合、1接続当たりのハードウェア・ネットワークリソースの消費量が大きくなるが、一方でインスタンス総数を減らせてコスト減につながる。
      • 間違いなくC10K問題[4]、およびプロセスやスレッドにおける限界などの問題には引っ掛かります。
    • 別サーバーの場合はおおむねその逆。
  • 分離した場合、それぞれの台数は1台か?それとも複数か?
    • 1-1構成の場合では、Web, Appそれぞれに適したチューニングが可能だが、その分台数コストや分離による認知負荷コストがかかる。
    • 1-n, n-n構成の場合ではスレッド数のチューニングによって予期せぬ事故が起こりそう[5]
      • 特にEC2ではオートスケーリングによって動的スケールアウトが可能なため、Autoscaling GroupとAMI内部の設定値の乖離が現れると危険そう。

以上の特徴を踏まえたうえで、「EC2のオートスケーリングでスケールアップ(ダウン)・スケールアウト(イン)、およびダウンタイムゼロのアプリケーション更新作業をしやすいよう、AMI単位で完結できるよう管理したい」「そこまで接続数や性能が必要とされるようなシステムではない」といった理由から、「同一サーバー内、1-1構成」を選んでいます[6]

Tips: AWSでAppサーバーだけでなくWebサーバーをわざわざ用意する是非について

Classmethodさんの記事で触れられていますが、AWSにおいてはELB(今回のパターンだとALBが適切)がサービスとして提供されており、フロントに置いてあるWebサーバーが担う責務はほぼ委譲可能となっています。

  • CF+S3を利用することで、アプリケーションサーバの負荷を軽減するという役割は不要になった(静的コンテンツを配信するということに関して)
  • ALBの機能も充実してきた

個人的な経験で見ても、ALBの機能はほぼWebサーバーの機能を代替できるほどに充実してきていると思います。また、ALBで担保できない部分(HTTP Headerの追加など)についてはCloudfrontやLamda@Edgeを併用することでカバーするということも可能となっており、Webサーバーとして扱うのに十分すぎる機能です。

一方で、それでもWebサーバーを採用する理由として以下が挙げられています。

  • 複雑なルーティングをしたい場合、ALBだけでは辛い
  • Nginxでキャッシュコントロールをしたい(コンテンツを圧縮したい)
  • ALBのログで良いんじゃね?という意見に対して
    • ALBのアクセスログには5分程度のタイムラグがあり、リアルタイムでログを拾いたい場合はWebサーバーがあった方が良い
    • ALBのアクセスログでは、クッキーやヘッダ等回収できないログ項目がいくつか存在する
    • Appサーバー側でうまくログ出力できるようであれば、考慮不要
  • 望まないリクエストの門前払いや、メンテ時のリダイレクト、ゆるくBasic認証を導入など、あると便利なケースが多い

ただ、上記の予定もなく、全てバックエンドに流すだけであれば撤去できる可能性もあると思います。

今回のケースの場合は、

  • 構築初期における探索のため、できるだけ早く反映される詳細なログがあった方が初期のバグ対応・チューニングに有利。
  • もしALBをWebサーバー代わりにするよう構成を切り替える場合にも、Webサーバーでの構築を行った際に手に入れた視点がALBにも転用可能では?という技術者経験の積み立てを重視する。
  • そもそもALBをWebサーバーにとらえるような構成はサーバー内に設定項目が納まらないため、TerraformやCFn、ECSといったIaCを中心に構成しないと設定値の低凝集・散乱を招く。今回はその辺をどうにかこうにかやっていくにあたっての事情で捨てた。
    • というか、全部AWSフルマネージドはIaCガッツリやってたとしても職人芸になるような気がする。

といった各種積極的・消極的な動機をもとに、状況に最も適切だと思われる構成として選択しました。

構築手順一覧

  • EC2 Instanceを建てる
    • SSHユーザーの初期設定
  • Tomcat, Apacheのインストール
  • <Optional> デフォルトの設定ファイルのバックアップを取っておく
  • Apacheの設定
    • ApacheのWelcomeページを無効化する
    • ApacheのAuto Indexを無効化する
    • ApacheのIcons Directoryを無効化する
    • ApacheのUserdirを無効化する
    • ApacheのCGIを無効化する
    • 必要のないモジュールロードをなくす
    • Apacheのバージョンを隠蔽する
    • XSTを防ぐ
    • クリックジャッキング攻撃を防ぐ
    • Mime Sniffingを通じたXSSを防ぐ
    • プロキシキャッシュへのセキュアなコンテンツ残留を防ぐ
    • ProxyPassの設定
    • <Optional> ログの設定
    • <Optional> MPMのチューニング
    • <Optional> Timeout/Keepaliveのチューニング
  • Tomcatの設定
    • エラーページの設定
    • <Optional> Tomcatユーザーを作成する
    • 利用しないコネクターを閉じる
    • AJPのコネクターを有効化する
    • <Optional> スレッドプール・JVMのチューニング
    • <Optional> 必要のないログを切る
  • サービスの起動

EC2 Instanceを建てる

今回はサーバーの中身の方についての記述を優先したいので、この点は詳しく記述しません。
ただし、IMDSv2の有効化とIMDSv1の無効化だけはしておいた方がいいかもしれません。
https://dev.classmethod.jp/articles/ec2-imdsv2-release/

注意点としては2つあります。
1つはパッケージマネージャー経由でAWSのリポジトリに触る必要があるので、なんらかの方法でVPC内からインターネットへのアウトバウンドを許可する必要がある点です[7]
もう1つは、SSH接続での作業を想定しているため、なんらかの方法で仮想マシンにSSH接続できるような環境を建てる必要がある点です[8]
次項からは、この2点が守られているという前提で進めていきます。

SSHユーザーの初期設定

EC2を立ち上げた際のデフォルトのSSHユーザーは、root権限の利用にパスワードが必要なく、ユーザー名がec2-userと決め打ちになっているため、セキュリティ観点から見てよろしくありません。なので、最低限の処置として、以下の記事を参考にデフォルトユーザーを閉じつつ、root権限を持つユーザーを作ります[9]
https://avinton.com/academy/amazonec2-initial-setting/

# --- SSH login as ec2-user ---
# Create user
sudo useradd <username>
sudo passwd <username> # input <password>

# Copy SSH Key from ec2-user
sudo cp -R ~/.ssh/ /home/<username>/.ssh

# Setup user permission
sudo usermod -aG wheel <username>
sudo chown -R <username>:<username> ~<username>/.ssh
sudo chmod -R go-rwx ~<username>/.ssh

# --- reboot or reload sshd ---
# --- Logout ---
# --- SSH login as <username> ---

# Deny ec2-user
# <!> If you want to delete ec2-user, you can use it.
#     => sudo userdel -r ec2-user
sudo mv /home/ec2-user/.ssh/authorized_keys /home/ec2-user/.ssh/authorized_keys.bk
sudo systemctl restart sshd

# Clear command history
sudo history -c

参考にさせていただいた記事内ではec2-userの公開鍵をそのまま使っていますが、もし他の公開鍵を利用したいという場合[10][11]は、手元で作成した鍵を再配置してください。

Tomcat, Apacheのインストール

特に気を付けることはないです[12]
Amazon Linux 2022ではパッケージマネージャーにdnfを採用していたので、dnfでコマンドを打っていますが、yumでも通ります。お好きな方をどうぞ。

sudo dnf install tomcat -y
sudo dnf install httpd -y

<Optional> デフォルトの設定ファイルのバックアップを取っておく

場合によっては飛ばしていいし、設定ファイル消してもいいです。
今回変えるものについてバックアップを取っておきます。
拡張子は.orgとしましたが、.bkでも.bakでも.originでもなんでも構いません。

sudo cp /etc/httpd/conf.d/welcome.conf /etc/httpd/conf.d/welcome.conf.org
sudo cp /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
sudo cp /etc/tomcat/conf/server.xml /etc/tomcat/conf/server.xml.org
sudo cp /etc/tomcat/conf/web.xml /etc/tomcat/conf/web.xml.org
Tips: ApacheとTomcatの設定ファイルのディレクトリ構成

Apache2.4, Tomcat9では、どちらも基本の設定ファイルはファイル名固定、オプションのモジュールロード等を書く設定はconfconf.dディレクトリ内に.conf拡張子で設定を書くようになっています。

/etc
  - /tomcat
    - server.xml
    - web.xml
    - /conf # ここ
    - ...

  - /httpd
    - /conf
      - httpd.conf # conf.dの中にあるconfファイルをインポート
    - /conf.d # ここ
    - /conf.modules.d # httpd.confで読み込まれるライブラリの記述(モジュールバンドラみたいなイメージ)
    - /modules # conf.modules.d内でロードするモジュールの実態
    - ...

逆に言えば、設定集約ディレクトリ(今回の場合は/etcの各ライブラリのディレクトリ)や、conf.d内に元のファイルが残っていたとしても、拡張子さえ変えれば使われないファイルとして保持できます。これを利用して初期設定時のバックアップや、rpmnewとの比較などに利用しています。
今後、「conf.d内の拡張子縛りをなくしました」というようなアップデートが来たら変更の必要性は出てくるでしょうが、ほぼ可能性としてゼロと言っていいと思います。

Apacheの設定

ほぼほぼこちらの記事のような、「やっておいた方がいい初期設定」系の記事の丸パクリです。
https://www.rem-system.com/apache-security01/

とはいえそれを書き連ねていくだけだと粗製乱造された検索ノイズと変わらないので、各手順にやってみて生まれた疑問点や、個人的な視点も付与していこうと思います。

ApacheのWelcomeページを無効化する

Welcomeページは、Apacheのルートにindex.htmlが存在しない場合に表示されるテスト用のページです。
初期起動の確認ならともかく、アプリケーションやコンテンツを配置する際には邪魔なので消してしまいます。

ただし、単に設定ファイルを消すだけではupgrade時に復帰してしまう[13]ので、ファイルの中身を消して空のファイルにするか、全行コメントアウトしてしまいましょう。

# Remove or comment out all the lines.
sudo vi /etc/httpd/conf.d/welcome.conf

ApacheのAuto Indexを無効化する

Auto Indexは、サーバ内の特定ディレクトリの中身をブラウザ上で閲覧できる機能です。
これもWebアプリの文脈においては見えるとダサい[14]ので、無効にしてしまいます。
conf.d内にある設定ファイルを消すか拡張子を変えてしまうかすればOKです。こちらにはリストアされない模様。

sudo mv /etc/httpd/conf.d/autoindex.conf /etc/httpd/conf.d/autoindex.conf.org

また、httpd.conf内でも、ディレクトリごとにindexを有効化している部分があります。
ここも消してしまいましょう。

# /etc/httpd/conf/httpd.conf

# <Directory "/var/www/html"> scope
-   Options Indexes FollowSymLinks
+   Options FollowSymLinks

ApacheのIcons Directoryを無効化する

上記のAuto Indexの画面上で利用されているアイコンや、他デフォルトページで利用されている画像ファイルの利用の可否を決める設定項目です。
autoindex.conf内に存在する項目をコメントアウトで無効化できるので、autoindex.confへの処置をしているのなら何もしなくても問題ありません。
開発用サーバーでAuto Index利用したいけど、できるだけ絞りたい、という場合は、autoindex.confの該当部分を消すかコメントアウトしてしまってください。

# /etc/httpd/conf.d/autoindex.conf

- Alias /icons/ "/usr/share/httpd/icons/"
- 
- <Directory "/usr/share/httpd/icons">
-     Options Indexes MultiViews FollowSymlinks
-     AllowOverride None
-     Require all granted
- </Directory>
+ # Alias /icons/ "/usr/share/httpd/icons/"
+ #
+ # <Directory "/usr/share/httpd/icons">
+ #     Options Indexes MultiViews FollowSymlinks
+ #     AllowOverride None
+ #     Require all granted
+ # </Directory>

ApacheのUserdirを無効化する

Userdirは、LinuxのユーザーアカウントごとにWebページを持ち、公開することができる機能です。
アプリケーションサーバーの前段のサーバーとしては不要なので、これも消してしまいます。

conf.d内にある設定ファイルを消すか拡張子を変えてしまうかすればOKです。

sudo mv /etc/httpd/conf.d/userdir.conf /etc/httpd/conf.d/userdir.conf.org

ApacheのCGIを無効化する

今回の構成の場合はCGIを利用しないため、CGI周りで利用するディレクトリは封じてしまいます。
内部でPerlのCGI呼び出してるよー、みたいなアプリ構成の場合は有効にする必要があるかもしれませんが、経験がないのでそのパターンについては無視して書いています[15]

こちらも同様に、httpd.confから該当ディレクトリの設定を消すかコメントアウトしてしまいます。

# /etc/httpd/conf/httpd.conf

# --- Before ---
- <Directory "/var/www/cgi-bin">
-     AllowOverride None
-     Options None
-     Require all granted
- </Directory>
+ # <Directory "/var/www/cgi-bin">
+ #     AllowOverride None
+ #     Options None
+ #     Require all granted
+ # </Directory>

# ...

# <IfModule alias_module> scope
-      ScriptAlias /cgi-bin/ "/var/www/cgi-bin"

必要のないモジュールロードをなくす

Apacheはデフォルトでモジュールをロードするような設定となっていますが、ここまでで無効化してきたり、そもそも使わないような機能のためのモジュールも一緒にロードしてしまっています。
なので、このあたりのモジュールをロードする設定も消してしまいます。

モジュールロードの設定に関しては、/etc/httpd/conf.modules.dに書いてあるので、この中から今回の構成で必要ないものを消したりアーカイブしてしまいます。
以下を対象とします。

  • 00-dav.conf : WebDav[16]に必要なモジュール
  • 01-cgi.conf : 前項で無効化したCGIの呼び出しに必要なモジュール
sudo cp /etc/httpd/conf.modules.d/00-dav.conf /etc/httpd/conf.modules.d/00-dav.conf.org
sudo cp /etc/httpd/conf.modules.d/01-cgi.conf /etc/httpd/conf.modules.d/01-cgi.conf.org

また、もともとの設定ファイルに書いてあるが、一部のモジュールは必要で、一部のモジュールはいらない、というパターンもあります。
そちらはファイルもろともではなく、ファイルの中身を見て一つずつ消してしまいましょう。

有効化・無効化すべき機能はアプリの要件や特性によって変わってきますが、少なくとも無効化したCGI関連や、Autoindex、Userdirについては必要ないので、こちらはモジュールロード対象から外してしまいます。
/etc/httpd/conf.modules.d/00-base.confを編集し、消すかコメントアウトしてしまいましょう。

# CGI Helper modules
- LoadModule actions_module modules/mod_actions.so
+ # LoadModule actions_module modules/mod_actions.so
- LoadModule env_module modules/mod_env.so
+ # LoadModule env_module modules/mod_env.so # ただし、PassEnvディレクティブなどを使って環境変数でパラメータ切り替えなどを行っているときは除く
- LoadModule suexec_module modules/mod_suexec.so
+ # LoadModule suexec_module modules/mod_suexec.so

# auto index
- LoadModule autoindex_module modules/mod_autoindex.so
+ # LoadModule autoindex_module modules/mod_autoindex.so

# userdir
- LoadModule userdir_module modules/mod_userdir.so
+ # LoadModule userdir_module modules/mod_userdir.so
Tips: 他のモジュールロード抑制候補

標準モジュールロード対象(/etc/httpd/conf.modules.d/00-base.conf)や、その他のモジュールロードのコンフィグには、多数のモジュールが登録されているのですが、「とりあえずコンフィグだけ書けば動く」を想定しているのか、実際に使うかどうかわからないものもロードされています。
今回のケースで「アプリケーションごとに要件が違うと言っても、明らかに使わんだろうなあ」と思ったのがいくつかあるので、候補として上げておきます。

以下記事を参考にさせていただきました。

  • MPM(Multi Processing Module)のモジュール : mpm_xxx_module
    • Apacheは並行リクエスト処理のためにMPMと呼ばれる仕組みを持っています。MPMにはいくつかモードがあり、これらの中から一つのモードを選んで起動させるのですが、モードとして使わないモジュールは消しても問題ありません。
    • Apache2.4ではeventモードがデフォルトかつ、00-mpm.conf内のデフォルト設定では、event以外のモジュールがコメントアウトされています。デフォルトから変える必要が無ければそのままでも問題ありません。
  • Basic, Digest認証のデータストアモジュール : mod_authn_xxx
    • デフォルトで使われるのはmod_authn_fileのようなので、コア機能であるmod_authn_coreと、場合によってはAnnonymous認証に必要なmod_authn_anonと一緒に残し、特に変える必要がないなら他は消します。
  • 認可のデータストアモジュール : mod_authz_xxx
    • 認証と同様。
  • Server Side Includes[17]モジュール : mod_include
  • サーバーの情報を提供するページを表示するモジュール : mod_info, mod_status
    • 開発中に使うのはいいかもしれませんが、実環境としては……。一応、デフォルトではページが提供されないよう設定されているようです。

他にも認証・認可・キャッシュ・ログ等の状況に合わせて、一つずつ見て消しましょう。

セキュリティコンフィグを設定する

前述したように、Apacheはconf.d内部にある.conf拡張子のファイルを読み込んで初期設定が行われます。
これは既存のwelcome.confなどに限らず、拡張子さえ合っていればロードされるものとなっています。
今回はこの仕様を利用して、security.confというファイルを作成し、その中にセキュリティ上必要となってくる要素を書き込んでいくこととします。

Tips: POODLEを防ぐ

SSLにはPOODLEと呼ばれる脆弱性が存在します。

これはSSLv3までが抱える脆弱性で、ほとんどのOS・ブラウザはもうSSLv3をサポートしていないのですが、どうしても保守しなければいけない場合は対策が必要となります。
また、TLSであっても、TLS1.0, 1.1といったバージョンIPAで非推奨とされているなどもあり、セキュリティ向上のためにこれらを無効化することも必要となるかもしれません。

このような脆弱性を抱えるSSL/TLSプロトコルを弾くために、以下の設定が利用できます。(Apache2.4以降はSSL2.0をサポートしていないため無視してOK)

# Anti POODLE
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1

今回のケースでは、ALBでSSL/TLSの制御を行っているため、Apacheではなくロードバランサ―のセキュリティポリシーにて制御を行います。

Apacheのバージョンを隠蔽する

デフォルト設定のApacheは、いくつかの場所でバージョンを出力します。
この情報は、ユーザーにとっては特に有用な情報ではないうえ、攻撃者にとっては「バージョンが上がっていないので、この脆弱性は通る可能性がある」といったような情報を渡してしまうきっかけとなってしまいます。
これを防ぐためにの設定をsecurity.confに記述していきます。

sudo touch /etc/httpd/conf.d/security.conf
  • ServerTokensの設定を変更し、HTTPレスポンスヘッダーに出力されるサーバーのバージョンを隠蔽する
    • HeaderのキーはServer
    • ServerTokensの値はProd, OS, Majorなど、いくつか設定値があるのですが、今回は最も出力内容が少ないProdを設定します。
# Hide Apache Version
ServerTokens Prod
  • HTTPレスポンスヘッダー内のX-Powered-Byの値を削除する
    • X-Powered-ByServerと同じように、内部で動いているサーバー情報を出力します。ただし、こちらはアプリケーションサーバーの情報を出力するものとなります。
# Hide Header X-Powered-By
Header always unset X-Powered-By
  • エラーページ等のバナーに出るApacheのバージョン値を抑制する
    • Apacheが生成するエラーページには、バナーと呼ばれる付随情報が存在します。ここにもApacheのバージョンが乗ってしまうので抑制します。
    • ServerSignatureという設定値を変更します。
# Hide bunner in Error Page
ServerSignature off

XSTを防ぐ

  • XST(Cross-Site Tracing)と呼ばれる攻撃を防ぐために、TRACEメソッドによるリクエストを拒否します。
    • 現代において、実際にXSTが重い脆弱性になるのはXSS(Cross-Site Scripting)脆弱性が含まれる場合に限られるようなので、実際の効力としてはそこまで高いもの[18]ではないですが、大した労力なく少しは安心度が上がる、ということで、今回は設定として書いておきます。
# Deny HTTP TRACE Method access for protection of Cross-Site Tracing attack
TraceEnable off

クリックジャッキング攻撃を防ぐ

  • Webサイトのボタンの上などに、見えない要素を置いてクリックさせることで、悪意ある別サイトへ移動させる・スクリプトを実行させるような攻撃。
    • iframeをXSSを通じて展開する方法がクリックジャッキング展開の基本なので、開けるiframe要素を同一オリジンに絞ることで防御策となります。
    • iframeは使わないよ、という場合は値がDENYでも良いかも。
    • どうしても外部のiframeを開く必要がある場合は、X-Frame-Optionsではなく、Content-Security-Policyおよびその値であるframe-srcを利用して開けるリソースをコントロールすると安全です[19]
# Deny open outer web resources for protection of Click Jacking attack
Header append X-Frame-Options SAMEORIGIN

XSSを防ぐ

  • 悪意あるユーザーによって格納されたサイトの入力値や、悪意あるサイトへアクセスしてしまったユーザーのクライアントなどから、外部からスクリプトが注入・実行できてしまう脆弱性です。
    • 一番有名で一番危険で一番塞ぎにくい脆弱性。
    • ある程度はサーバーの設定で抑制できますが、それでも下手な実装をすると入り込んでしまう脆弱性なので、気休め程度にお考え下さい。
    • X-Content-Type-Optionsの値を設定することで、MIME Sniffingと呼ばれる攻撃を防ぎ、そこを経由したXSS攻撃の防御を行います[20]
    • X-XSS-Protectionの値を設定することで、XSSの自動検知とブロックをブラウザに明示します。
# Protection for MIME Sniffing attack
Header set X-Content-Type-Options nosniff 
Header set X-XSS-Protection "1; mode=block"
Tips: X-XSS-Protectionに設定する値について

X-XSS-Protectionはほとんどのモダンブラウザでサポートされていないことが明示されており、この値の利用によって逆にXSSの危険性が高まってしまうことが警告されています。

Warning: Even though this feature can protect users of older web browsers that don't yet support CSP, in some cases, XSS protection can create XSS vulnerabilities in otherwise safe websites.
(警告:この機能は、まだCSPをサポートしていない古いWebブラウザーのユーザーを保護できますが、場合によっては、XSS保護によって安全なWebサイトにXSSの脆弱性が生じる可能性があります)

詳細はリンク先にて確認していただきたいのですが、X-XSS-Protection以外で取れるXSS対策としては、Content-Security-Policy: reflected-xss blockを設定する、nonce方式のスクリプト実行制御を行うなどの多角的な制御が必要となってきます。

今回はiOSのSafariをサポート対象として見ていることから入れましたが、この辺は知識が足りてないので、何がベストか把握しきれていません。あくまで暫定案としてとらえてください。

プロキシキャッシュへのセキュアなコンテンツ残留を防ぐ

  • アプリケーションによっては必要ないかもしれませんが、今回のようなアーキテクチャを取る場合はほとんど必要だろう、と判断して載せます。
    • 詳しい説明・プロキシキャッシュへのコンテンツ残留によるリスクなどは以下リンク参照。

これらのキャッシュメカニズムは、ブラウザからのリクエストによって得られたコンテンツをキャッシュに保持しておき、同じURLのリクエストが生じたとき、本来のWebサーバにコンテンツを取りに行かず、キャッシュの内容をブラウザに渡すものである。
このようにキャッシュは、円滑なインターネットの利用に寄与してくれる。
しかし、コンテンツによっては、ただひとりのユーザのみが呼び出せるものでなければならない場合がある。
もしも誰かのプライベートなコンテンツがキャッシュに記録されていて、それを他人が呼び出すことができれば情報漏えい問題になる。

  • Cache-Controlヘッダーを設定することでコンテンツ残留を防ぎます[21][22]
# Prevent proxy cache
Header set Cache-Control "private, no-store, no-cache, must-revalidate"
# Prevent proxy cache for HTTP 1.0
Header set Pragma no-cache

ProxyPassの設定

Apacheが受け取ったリクエストは、最終的にTomcatのAJPの待ち受けポートに流してやる必要があるので、あらかじめここで設定をしておきます。
httpd.confに追記してもいいのですが、今回はetc/httpd/conf.d/httpd-proxy.confを新たに作成して記述します[23]

以下のように設定を行います。

<Location /<your-app-path>/ >
        ProxyPass ajp://localhost:8009/<your-app>/ secret=<your-secret>
        ProxyPassReverse ajp://localhost:8009/<your-app>/ secret=<your-secret>
</Location>

サイトの構成によってはVirtualHostを構成して複数のアプリを同居させたり、rootパスからアプリにアクセスできるようにするなどの処理が必要になる[24]ので、上記は一例として参考にしてください。
また、secretの値についてはTomcatのAJPコネクターの設定の際に説明しますが、今回の構成の場合は必要ありません。が、手順として必要そうなものを全部残すという記事のため、説明用にひとまず書いておきます。この値の要不要は、後に出てくるAJPの項目、およびGhostcat脆弱性についてを読んでから判断してください。

<Optional> ログの設定

Apacheのログはフォーマット・ローテーション・ログレベル等様々な設定が可能となっています。
ここは要求やログの集約設計・担当範囲によって正解が変わる部分なので、デフォルトでも問題ないならそのままで良いですし、触る必要があれば変更します。
とはいえ、何ができるかの指針がなければ厳しいので、いくつか参考とさせていただいたサイトをリンクします。

https://httpd.apache.org/docs/2.4/ja/logs.html
https://qiita.com/colorrabbit/items/a9189fd1f27c49613577
https://qiita.com/na0AaooQ/items/ff2dad51b2b70d69ce87

今回のケースでは、インスタンスにログファイルを残しておくとフェイルオーバー時のネックになるかつ、せっかくAWSを利用するということで、ローテーション早めに設定し、以下を参考としてCloudwatch Logsに流してやるという構成になります。ログストリームの設計などは割愛。

https://blog.serverworks.co.jp/cw-apache-access_log

<Optional> MPMのチューニング

MPMは並行リクエスト処理に利用されるモジュールであると前述しましたが、このチューニングにより同時にさばけるリクエストを増加させられるということで、この辺も弄れるならば弄っておきます。
/etc/httpd/conf/httpd.confに直書きでもいいのですが、mpm関連はまとめておきたいと考え、/etc/httpd/conf.d/mpm-event.confを作成し、そこに置くことにしました。
この値も、マシンのメモリや他プロセス、求められるパフォーマンス目標値などで様々な点が変わってくるため、指針として参考にした記事のリンクを貼っておきます。

特に初期の設定の考慮に使えそうな点では、以下の記事を参考にしました。
https://medium.com/@sbuckpesch/apache2-and-php-fpm-performance-optimization-step-by-step-guide-1bfecf161534

ServerLimit = (Total RAM - Memory used for Linux, DB, etc.) / process size
MaxRequestWorkers = (Total RAM - Memory used for Linux, DB, etc.) / process size
StartServers (Number of Cores)

また、チューニングはアクセス数などによって最適とされる値が変わるため、一度設定したら終わりではなく、継続的に監視・チューニングを行うべき領域です。初期設定の他に計画的なチューニングも計画するのが良いかと思います。
チューニングに関連するものは以下の記事を参考にしました。
https://qiita.com/rryu/items/5e02ea60e36d7fd956b8
http://blog.matsumoto-r.jp/?p=2996
https://ftp-admin.blogspot.com/2009/09/worker-mpm_15.html

MPMのモードは、Apache2.4系だとeventがデフォルトなので、eventモードのチューニングを参考にしましたが、これがpreforkになると大きく変わってくるので、preforkを利用する場合はまた別のチューニングを参考にしてください。

<Optional> Timeout/Keepaliveのチューニング

Apacheはクライアントからの接続をどこまで保持するか・バックエンドとの接続をどれだけ保持するかの値を持っています。
接続の保持をいつまでも持ち続けられるならもちろん早く処理を行い続けられるのですが、マシンの資源は限られているため、適切な値にチューニングしていく必要があります。
ここもマシンの性能や最大リクエスト数などに依存するので一概にこれで、というのは言えません。そのため、例によって参考URLを貼ります。
https://qiita.com/kazukikudo/items/96f77f1648b8ce47dc06

また、今回のようにALBをフロントに置いている場合、KeepAliveTimeoutなどの設定値には注意点が存在します。
以下を参考にしてください。

https://qiita.com/hareku/items/7880eeac1e96c2f897e1
https://aws.amazon.com/jp/premiumsupport/knowledge-center/apache-backend-elb/

キープアライブタイムアウト (Apache の KeepAliveTimeout、NGINX の keepalive_timeout)
キープアライブオプションが有効になっている場合は、ロードバランサーのアイドルタイムアウトよりも長いキープアライブタイムアウトを選択します。

ここもアクセス数などによって最適とされる値が変わるため、一度設定したら終わりではなく、継続的に監視・チューニングを行うべき領域です。

Tomcatの設定

Apacheの設定が終わったら、次はTomcatの設定を行います。
Apache同様、セキュリティ周り、AJPによる連携の設定、チューニングといった項目に注目していきます。

エラーページの設定

Tomcatはデフォルトのエラーページ内でエラーのステータスや詳細、Tomcatのバージョンなどを出力してしまいます。
Apacheのバージョン出力同様、バージョンに起因する脆弱性の情報を攻撃者に与えてしまうことになってしまうため、デフォルトのエラーページ出力を塞いでしまいます。
/etc/tomcat/web.xmlに記述するか、アプリケーションのWar, Jarに含まれるweb.xmlや、Spring Java Configのような設定値に対応するものを編集して、独自のエラーページに差し替えます。
今回は/etc/tomcat/web.xmlの例を記載します。

<web-app>
  <!-- ... -->
  </welcome-file-list>
  <error-page>
    <error-code>500</error-code>
    <location>/path/to/errorpage.html</location>
  </error-page>
  <!-- ... -->
</web-app>

エラーページとなるHTMLファイルのlocationは、/etc/tomcat/web.xmlに記載した場合は、/var/lib/tomcat/webappsをルートパスとして配置しますが、War,Jarに含まれる設定はアプリごとに異なります。それぞれフレームワークのドキュメント等を見ながら配置してください。

また、他の方法として、ErrorReportValueを編集するという方法もあります。
https://mr-star.hatenablog.com/entry/tomcat_errorReport_valve

<Optional> Tomcatユーザーを作成する

Tomcatは実行ユーザーをroot権限を持つユーザーで動かすと、Tomcatに侵入された際にすべてのシステムにアクセスを許すようになってしまいます。
そのため、ログイン不可のTomcatユーザーを作成し、そのユーザーで動かすようにします。

ただ、Amazon Linux 2022でdnf install tomcat経由でTomcatをインストールし、systemctl enableをかけるとよしなにやってくれていそうなので、今回の手順としてはスルーしました。
必要な方は、下記リンクを参考にユーザーの作成や、実行ユーザーのファイルパーミッションの設定、systemdのユニットファイルの設定などを行ってください。
https://qiita.com/nkojima/items/cb9e2056ae532c8dd0dc

利用しないコネクターを閉じる

TomcatはデフォルトでTCP8080番をHTTPのポートとして待ち受けています。今回のケースでは、こちらは利用しないため、アクセスを防ぐために閉じてしまいます。
/etc/tomcat/server.xmlを開き、以下のConnectorの項目をコメントアウトしてしまいます。邪魔であればコメントアウトではなく、消してしまって構いません。

+ <!--
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
+ -->

AJPのコネクターを有効化する

TomcatではAJPの待ち受けを行いたいので、HTTPと違い、こちらはコメントアウトを外してしまいます。
同じように、以下部分のコメントアウトを外してしまいます。

-    <!--
    <Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />
-    -->

また、ApacheのProxyPassで設定したSecretの値もこちらに入れます。

    <Connector protocol="AJP/1.3"
               address="::1"
+              secret="<your-secret>"
               port="8009"
               redirectPort="8443" />
Tips: Ghostcat脆弱性について

Tomcatには、Ghostcatと呼ばれる脆弱性があります。

この脆弱性の対策のために、Tomcat9.0.31以降ではAJPの接続にsecret属性でシークレットキーを指定を必須にする属性secretRequiredがデフォルトでtrueとなる変更が行われました。

トレンドマイクロによると、Ghostcatが成立するためには、以下の条件を満たす必要があります。

APP機能を利用してファイルをアップロードすること
ファイルはDocumentRoot内に保存されていること
AJPポートに直接到達すること

これらの条件は、今回の構成では「AJPポートに直接到達すること」の条件を満たしません(address="::1"でlocalhost以外の接続を封じてある)。そのため、secretRequired="false"を指定して、ApacheのProxyPassからもsecretを外してしまって構いません。ただし、AJPコネクターであるTCP8009番への直接アクセスの恐れがある環境の場合は、address="::1"の指定を外さないようにしましょう。

<!-- server.xml -->
    <Connector protocol="AJP/1.3"
               address="::1"
+              secretRequired="false"
               port="8009"
               redirectPort="8443" />
<Location /<your-app-path>/ >
        ProxyPass ajp://localhost:8009/<your-app>/
        ProxyPassReverse ajp://localhost:8009/<your-app>/
</Location>

<Optional> スレッドプール・JVMのチューニング

付け焼刃かつ初期設定では無理です。(放棄)
この記事一番の強いエンジニア助けてポイントになります。

本頁にある各属性の設定値のチューニングは、サーバー及びネットワークの物理構成と同時接続数、
データーベースのデータ量及び最大同時接続数に対するサーバーの運用実績(経験値)から調整するものが多いです。
または、多くの負荷試験からシュミレーションを行い導き出されるものです。

本書の読者がTomcatを上手くチューニングできるようになる事へのアドバイスとしては、
貴方がどのような立場でTomcatに接していても、本番機の機器構成(サーバー、ネットワーク)、
同時接続数(平均、最高)、データベースのデータ量、データベース側の最大同時接続などを
情報共有して頂き、どのような設定で運用しているか興味を持つ事です。

https://gakumon.tech/tomcat/server_xml/http.html

引用先に完全同意です。Javaパフォーマンス読んで測定の体制整えたりGCやヒープ領域と格闘しましょう。ここはどうにかこうにかしようがありません。
https://www.oreilly.co.jp/books/9784873117188/

とはいえ、JVMチューニングのお約束として、-Xmx-Xmsを合わせてオーバーヘッドを防ぐ-XX:MaxMetaspaceSizeを大体256mくらい割り当ててやる、-XX:+UseConcMarkSweepGC-XX:+UseParNewGCを乗っけてCMS GCを有効化する(Concurrent Nark Sweep GCはJava9以降非推奨らしいです……[25]。取れるヒープ領域にもよりますが、今のところとりあえずでやるならデフォルト(G1GC)か-XX:+UseShenandoahGCでShenandoah GC[26]を有効化するかの二択あたりから始めるのが良いかも)、なんかはとりあえずでやってもいいかな、と思います。その場合は、/etc/sysconfig/tomcatCATALINA_OPTSとして書き込んでしまいましょう。

<Optional> 必要のないログを切る

manager, host-managerは今回の構成では必要ないので、これらのログ設定は邪魔になります。消してしまいましょう。
ここに関しては、リンク先のログ設定のまんまで問題ないと判断しています。

https://qiita.com/hidekatsu-izuno/items/ab604b6c764b5b5a86ed#ログの整理

サービスの起動

ここまでで設定は終了です。あとはサービス自動起動を有効化して起動[27]し、実際にアクセスするなりサービスのプロセスを確認するなりアプリをデプロイするなりして、テストを行って完了です。お疲れ様でした。

sudo systemctl enbale httpd
sudo systemctl start httpd
sudo systemctl enbale tomcat
sudo systemctl start tomcat

おわりに

改めて「とりあえずやってみよう」と「なんでそうなるか」と「他の選択肢になりそうなところ」を並べてみると異常な長さになりました。これは項目分けされて話されるのもやむなし。
こんな読みづらい投稿ですが、細かく指摘・マサカリ大歓迎ですので、つよつよエンジニアの方は突っ込み入れていただけると喜びます。

脚注
  1. GoFのFacadeパターンっぽいので、こういうタイプの記事を勝手にFacade記事と呼んでいます。作りが雑な場合は雑NA〇ERまとめと呼ばれます。いかがでしたか? ↩︎ ↩︎

  2. 「EPELなどの特定パッケージリポジトリの利用などを出来る限り行わず、公式AMIの初期段階で実行可能」「仮想環境を何らかの形でローカルに引っ張ってこれる(もしくは類似環境を作成できる)」を中心に考えて組んでいます。 ↩︎

  3. どうにかこうにかポイント。Amazon Linux 2のサポート期間が来年6月までのため、評価用AMIであってもこっちの方が将来的なドキュメント価値は高いだろう、と想定してAmazon Linux 2022で試しています。ただし、Amazon Linux 2022のGithub Projects見てるとCorreto使えるようにするぜ、というチケットがあるので、OpenJDKでの設定やらチューニングやらは正式リリース後は使えないかも。ECS使ってるならECRからDockerの公式イメージとか引っ張ってきてどうにかこうにかしない方がCI/CDやIaCの観点から良いんじゃないかな。 ↩︎ ↩︎

  4. 参考: https://udon-yuya.hatenablog.com/entry/2020/09/03/233227 ↩︎

  5. 参考: https://atmarkit.itmedia.co.jp/ait/articles/0708/27/news098_2.html ↩︎

  6. Web3層アーキテクチャにおけるメリット・デメリットや、求められるパフォーマンスとコストの対比、組織内の技術体系等、決定の指標となる要素は多くありますが、今回は分離によるキャッシュコスト・運用面での認知コスト増加デメリットと、オートスケーリングの容易性にかなりの重みを置いた判断です。このあたりはコンテキストによって最適解は違うので、よしなに選ぶといいと思います。例えば、Docker Imageの管理がちゃんと組織内で回せるようなら、同一の採用基準であっても、ECS-EC2構成でWeb-Appサーバーの分離体制を実質仮想サーバー1台から開始する選択などがとりやすいと思います。 ↩︎

  7. IGWを使う、Private VPCからNAT Gatewayを通すなどやり方は色々ありますが、「Session Managerを使いたい」「InspectorやAWS Config、およびSecurity Hubの監査内容を追うと、近年ではEIPをインスタンスに紐づけるのは推奨されていない」「最終的にインターネットからのインバウンドをなんらかの形で確保する必要がある」という点から、記事作成時点では参考URLの2番のやり方がベターだと思います。 参考: https://dev.classmethod.jp/articles/session-manager-pattern/ ↩︎

  8. Packer + CodeBuildなどの構成でAMI構築を手オペ以外でやるなら必要ないかも。 ↩︎

  9. 他にもSession Managerでの使用を強制してSSHポートをセキュリティグループで封じ、そもそもユーザーの設定が必要ない状態にしたり、SSHの待ち受けポートを変える、ポートノッキングを有効化する等様々な措置を組み合わせたりするなどの発展形もありますが、今回は割愛。 ↩︎

  10. 「AWS側で自動生成したKeyって本当にセキュアなんけ?」「パスフレーズもないキーを使うなんて……」という理由から自分で作った鍵を使うみたいな記事を昔見た気がするが見つからない。 ↩︎

  11. 「デフォルトのキーでは暗号強度に不安がある」という記事もどこかで見た覚えがあるのですが見つかりませんでした。記事作成時点だと2048-bit SSH-2 RSA以外にもED25519が選択できるので、個人的にはED25519で生成してもらったキーをそのまま使って初期設定し、そのあとInstance Connectやらなんやらで制御するようにする、で問題ないと思います。また、複数のSSH Keyを発行して、個人個人のユーザー権限絞って配る、というパターンもありますが、AWSにおいてはグループ権限による境界防御をゴリゴリと推し進めるよりも、Session Managerでセッションログを取りながら「信頼はするが監視はする」「誰かがミスって壊すかもしれないが、壊すことを前提にケアできる体制を整える」のスタンスでやる方が安全性・ロールバック健全性なんかを担保できるんじゃないかなあ、と思います。 ↩︎

  12. WSL2・Ubuntuでaptでインストールするとリポジトリに古いバージョンしかない、みたいな、標準で設定されているリポジトリの中身が更新されていないパターンは対応しなきゃいけないかも。(最近触ってないから今のWSL2・Ubuntuでもそうかはわかりません。現状だとどうなんですかね?) ↩︎

  13. welcome.conf内に「NOTE: if this file is removed, it will be restored on upgrades.」という記述もある。 ↩︎

  14. 今回の構成だと「セキュリティ的にすごい危ないファイルを置く」というのはほぼ無いケースだと思うので、危険だから無効にしましょう、というよりは、むしろ「こんな簡単に塞げるところも手をつけていないなんて……」という、見る人が見ればわかる技術レベルへの不信感を持たせないようにする、という面が大きいのではないかと思います。 ↩︎

  15. というか、現代においてそんなパターンあるんですかね……? ↩︎

  16. 知らなかったので調べてみたんですが、「SCP, SFTP等を使わずに、HTTPでブラウザを使ってファイル共有サーバ使えますよ機能」だと解釈しました。現代では使わなさそうな印象。 参考: https://e-words.jp/w/WebDAV.html ↩︎

  17. これも知らなかったので調べてみました。「Webサーバー上で完結するテンプレートエンジン」みたいな感じ。どうも現代ではセキュリティに難ありのため利用されなくなっているようです。 詳しくは参考URL先:https://wa3.i-3-i.info/word11962.html ↩︎

  18. 参考: https://blog.tokumaru.org/2013/01/TRACE-method-is-not-so-dangerous-in-fact.html ↩︎

  19. 参考: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy ↩︎

  20. 参考: https://boscono.hatenablog.com/entry/20070805/p1 参考URLにも書いてある通り、もともとはIEの専用ヘッダーだったのですが、Chrome, Firefox等他のブラウザにも適用可能です。=> https://blog.ohgaki.net/x-content-type-options-nosniff-is-required-by-other-than-ie ↩︎

  21. 設定する値についてはほぼお決まりなのですが、各値についての詳細は参考にした記事がざっくりまとめていてわかりやすかったです。=> https://kiririmode.hatenablog.jp/entry/20170625/1498389317 徳丸本にも同じように書いてあった記憶があります。 ↩︎

  22. Pragmaも足したけどもうさすがにいらないかも。 ↩︎

  23. Apache httpd 2.2系あたりで/etc/httpd/conf/extrahttpd-proxy.confを突っ込んでいた覚えがあったので、それを模倣した。が、この記事を書いている途中に「コレはホストとかポートとか含めた設定なんだから、拡張設定的に書くよりも、httpd.confに直書きするか、VirtualHostごとの設定ファイルでまとめた方が凝集度高くないか?」と思い直している。ProxyPass周りの記述をどこに書くべきかは色々探ってはみているものの、新しいのも古いのも含めて様々なパターンが見つかり、今のところどのパターンがどういう時に最適かいうのはしっかりわかっていない。 ↩︎

  24. https://qiita.com/taconana/items/36855776d4ed6a0d5ba5 が連携の構成として参考になる。また、VirtualHost込みのリダイレクトを考慮した構成は https://qiita.com/tmiki/items/4383f66fa521da8aec03 を参照。 ↩︎

  25. 参考: https://jikkenjo.net/2244.html ↩︎

  26. 参考: https://blog.cybozu.io/entry/2018/05/29/080000 https://rheb.hatenablog.com/entry/shenandoah_wiki_1 ↩︎

  27. Tomcat-Apache間だと起動順序が大切だというのをどこかで効いた覚えがあったので調べてみましたが、どうも古代の話のようで、今はあんまり考える必要はないみたいです。参考: https://neilling.hateblo.jp/entry/2015/01/29/125159 ↩︎

Discussion