Tomcat の初期設定まとめ

## はじめに

最近は Jetty に押されている感もありますが、Apache Tomcat は今でも各ディストリビューションに rpm が用意されていたりと、よく使われる Java AP サーバです。しかし、そのわりには初期設定がイマイチなことが多く、インストール後いろいろと設定を変更しなければならなくなります。にも関わらず、ドキュメントを読むだけでは、どう設定すれば適切と言えるのかわかりずらいところがあります。
この文書は、いつも行う手順を自分なりにまとめたものです。こうした方が良いなどあれば追記しますのでコメントでご指摘頂けると幸いです。なお、本稿を書いている時点でのバージョンは、Tomcat 8.5 です[^note]。

[^note]: また、Tomcat 上には単独の webapp のみを配置することを想定しています。複数の webapp が必要となる場合には、必ずしも適切な設定ではないかもしれません。

2017/01/31 追記：フロントエンドサーバから HTTP で接続する場合の設定を追記しました。

## 不要な webapp の削除

tomcat.apache.org の配布物を展開した場合、ROOT, docs, examples, manager, host-manager が最初から含まれています。本番環境では、ROOT, docs, examples はそもそも不要なのでフォルダごと削除していいですし、これから書くように manager, host-manager も削除すべきです。なお、最近の Linux ディストリビューションの場合、rpm で入れると webapps は空の状態でインストールされるようです。

manager は以前のバージョンではデプロイに必要になる場合もありましたが、最近のバージョンでは、webapps 以下に war ファイルを置くだけでデプロイすることができますし、追加の設定も war ファイルの META-INF/context.xml に書くことが可能なので削除しても特に困ることはありません。Web サーバ側でアクセス可能なパスに制限をかけることが出来るとはいえ、設定に間違いがあれば侵入されてしまう可能性もあるわけですから、セキュリティを考えれば、管理機能が Web 側からアクセス出来る場所にあるというのは望ましくないと思います。

host-manager も同様に仮想ホスト機能の管理画面ですが、そもそも通常は必要にはならないし、conf/server.xml を直接修正すればいいだけです。

## 不要なポート使用の停止

conf/server.xml の Connector にアプリケーションへの接続用ポートの設定、Server にシャットダウンポートの設定があります。

デフォルトでは、HTTP, AJP の設定が有効になっています。通常、AP サーバを立てる場合は、フロントエンドサーバとして Web サーバを立て、その後ろで動作させるのが一般的だと思います。その場合には、HTTP で接続するか AJP で接続するかによって設定を変える必要があります。

### フロントエンドサーバと AP サーバを AJP で接続する場合

AJP は、Apache が策定した Web サーバと Java AP サーバ間専用の接続プロトコルです。パフォーマンスとしてはどちらでもさほど変わらないようなのですが、 AJP で接続した場合、Web サーバと AP サーバが一体であるかのように振る舞うため実務的なメリットが多くあります。例えば、HTTPS の処理を Web サーバ側で行う場合でも request.isSecure() が true を返しますし、request.getRemoteAddr() がきちんとクライアントのアドレスを返します。統合 Windows 認証の認証情報を受け取りたい場合にも AJP を使うことで実現できます[^tomcatAuth]。

[^tomcatAuth]: 認証を通過させる場合には、tomcatAuthentication="false" を設定する必要があります。【参考】[Windows Authentication How-To](https://tomcat.apache.org/tomcat-8.5-doc/windows-auth-howto.html)

AJP で接続する場合には、（特に本番環境では）HTTP ポートは閉じてしまっていいですし、逆に AJP を使用しないのであれば、AJP ポートを閉じてしまって構いません。ポートを閉じたい場合は、該当の Connector タグをコメントアウトして Tomcat を再起動してください。

### フロントエンドサーバと AP サーバを HTTP で接続する場合

フロントエンドサーバと AP サーバの接続に HTTP を使う場合、特に何も設定を行わないとアクセスログには Web サーバの IP アドレスが記録されてしまいます。このような問題に対応するため、最近のフロントエンドサーバは、特別な HTTP ヘッダ（プロキシプロトコル[^proxyprotocol]）をバックエンドサーバに受け渡します。

[^proxyprotocol]: [RFC7239](https://tools.ietf.org/html/rfc7239) で標準化されたようですが、多くのフロントエンドサーバではまだ対応していない場合が多いようです。

|HTTPヘッダ       |説明                                           |
|-----------------|----------------------------------------------|
|X-Forwarded-For  |カンマで区切られた経由したプロキシサーバ名のリスト|
|X-Forwarded-Proto|リクエストされたプロトコル（例：https）          |

Tomcat では、RemoteIpValve[^remoteipvalve] を設定することでフロントエンドサーバを経由したアクセスでも直接アクセスしたかのように情報を取得することができます。

```xml:conf/server.xml
<Valve className="org.apache.catalina.valves.RemoteIpValve"
       protocolHeader="X-Forwarded-Proto" />
```

[^remoteipvalve]: [RemoteIpValve Javadoc](https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html)

### シャットダウンポートの取り扱い

初期状態ではシャットダウンポートを使って、ポート経由でシャットダウンするようになっています。localhost からしか応答を受け付けないので、さほど危険なわけではありませんが、Apache Commons Daemon を使って起動されている場合はシャットダウンポートは無効にして構わない旨記載があります。ですので、Windows サービスとして使う場合には無効にしても構いません。

Redhat linux など Linux ディストリビューションを使っている場合には catalina.sh を使って起動が行われているため、有効のままにしてください。無効にしても停止はしてくれますが、Gracefull なシャットダウンができなくなります。

シャットダウンポートを停止する場合はポート番号に -1 を設定し Tomcat を再起動してください。

```xml:
<Server port="-1" shutdown="SHUTDOWN">
```

## ログの整理

Tomcat はデフォルトで多くのログファイルを出力するため、いったいどのログに何が出力されるのか大変わかりずらいです。環境によって多少の違いがあるようなのですが、デフォルトでは次のようになっています。

|ログファイル                        |出力内容                                 |設定箇所           |
|-----------------------------------|----------------------------------------|------------------|
|catalina.out[^catalina_out]        |標準出力(catalina.logの内容を含む)＋標準エラー出力 |catalina.sh       |
|catalina.YYYY-MM-DD.log            |Tomcat自体のログ                         |logging.properties|
|localhost.YYYY-MM-DD.log           |ホスト共通の ServletContext#log          |logging.properties|
|manager.YYYY-MM-DD.log             |manager webapp の ServletContext#log    |logging.properties|
|host-manager.YYYY-MM-DD.log        |host-manager webapp の ServletContext#log|logging.properties|
|localhost_access_log.YYYY-MM-DD.txt|ホスト共通の アクセスログ                 |server.xml        |

[^catalina_out]: Windows の場合には、catalina.out は出力されません。サービスとして起動した場合には代わりに、サービス・プログラムのログ（commons-daemon.YYYY-MM-DD.log）、標準出力のログ（tomcat8-stdout.YYYY-MM-DD.log）、標準エラー出力のログ（tomcat8-stderr.YYYY-MM-DD.log）の３つが出力されます。

まず、このうち manager.YYYY-MM-DD.log と host-manager.YYYY-MM-DD.log は webapp 自体を削除してしまっているので不要です。また、catalina.YYYY-MM-DD.log は起動したコンソールにログを出力するように設定されていますが、サービスとして使う場合には単にふたつのログファイルに同じ内容が出力されてしまっているので、これも抑止してしまって構いません。

```properties:conf/logging.properties
handlers = 1catalina.org.apache.juli.AsyncFileHandler, 2localhost.org.apache.juli.AsyncFileHandler
#   , 3manager.org.apache.juli.AsyncFileHandler, 4host-manager.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler

.handlers = 1catalina.org.apache.juli.AsyncFileHandler
#   , java.util.logging.ConsoleHandler

1catalina.org.apache.juli.AsyncFileHandler.level = FINE
1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.

#2localhost.org.apache.juli.AsyncFileHandler.level = FINE
#2localhost.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
#2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost.

#3manager.org.apache.juli.AsyncFileHandler.level = FINE
#3manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
#3manager.org.apache.juli.AsyncFileHandler.prefix = manager.

#4host-manager.org.apache.juli.AsyncFileHandler.level = FINE
#4host-manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
#4host-manager.org.apache.juli.AsyncFileHandler.prefix = host-manager.

#java.util.logging.ConsoleHandler.level = FINE
#java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.AsyncFileHandler

#org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO
#org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = 3manager.org.apache.juli.AsyncFileHandler

#org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/test].level = INFO
#org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/test].handlers = 4host-manager.org.apache.juli.AsyncFileHandler
```

これで、ログファイルは、catalina.out、catalina.YYYY-MM-DD.log、localhost.YYYY-MM-DD.log、localhost_access_log.YYYY-MM-DD.txt だけになりました。ここからはプロジェクトによりますが、通常、アプリケーションのログは ServletContext#log メソッドを使わずに logback などのアプリケーション固有のログ出力機能を使うことが多いのではないでしょうか。多くの場合、ServletContext#log が使われるのは、設定ミスなどによるアプリケーションの起動が失敗した場合のみでしょう。そう考えると catalina.YYYY-MM-DD.log に統合した方が良さそうです。

```properties:conf/logging.properties
handlers = 1catalina.org.apache.juli.AsyncFileHandler
.handlers = 1catalina.org.apache.juli.AsyncFileHandler

1catalina.org.apache.juli.AsyncFileHandler.level = FINE
1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 1catalina.org.apache.juli.AsyncFileHandler
```

これで、catalina.out、catalina.YYYY-MM-DD.log、localhost_access_log.YYYY-MM-DD.txt のみとなりました。catalina.out は標準（エラー）出力だけが記録されるので、ほぼ用済みなのですが、何らかの理由でシェルからの起動時にエラーが発生した場合には原因究明に必要となるので、そのままにしておきます。もし、抑止したいという場合には、bin/setenv.sh にて CATALINA_OUT に /dev/null を指定することが可能です。

最後にアクセスログです。Tomcat では、アクセスログは拡張機能のひとつとして実装されているため、logging.properties ではなく server.xml に設定があります。

```xml:conf/server.xml
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
       prefix="localhost_access_log" suffix=".txt"
       pattern="%h %l %u %t &quot;%r&quot; %s %b" />
```

Web サーバと併用する場合には、Web サーバ側で同様のログが取れるため、コメントアウトしてもかまわないとは思います。Web サーバと AP サーバの接続に失敗するような場合だけ戻せばよいのではないでしょうか。

Tomcat 単独で動作させる場合には、むしろ Apache と同じログ形式で出力した方が便利です。まず、ファイル名ですが prefix、suffix を省略すると　Apache のデフォルト・ログ・ファイル名と同じ access_log になります。pattern に ついても simple を指定すると Common Log Format で出力され、combined を指定すると Combined Log Format で出力されます[^rotation]。

[^rotation]: ローテーションはデフォルトで true です。logrotate を使うなどの理由でローテーションをやめたい場合は rotatable="false" を付けます。

```xml:conf/server.xml
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
       pattern="combined" />
```

なお、RemoteIpValve と併用する場合は、requestAttributesEnabled="true" を指定する必要があります。

## JavaVM オプションの設定

本番環境で AP サーバを使用する場合には、メモリの上限を上げるために JavaVM の起動オプションを設定することが多いと思います。通常の Java アプリであれば引数を設定するだけですが、Tomcat の場合は、起動用のシェルスクリプトが用意されているため、別の方法で設定する必要があります。

設定方法は、bin/catalina.sh（Windows であれば bin/catalina.bat）の中に書かれていますが、bin/setenv.sh（Windows の場合は bin/setenv.bat）を作成しそこに変数を定義すると反映されます[^envconfig]。

[^envconfig]: Windows サービスの場合には bin/setenv.bat の内容は反映されないことに注意してください。同フォルダにある Tomcat8w.exe を実行するかタスクトレイの Tomcat アイコンを右クリックし「Configure...」をクリックすると設定画面が立ち上がりますので、そこで設定します。設定内容はレジストリに格納されます。

設定できる変数のうち、主なものを列挙します。

|変数名          |説明    |
|----------------|--------|
|CATALINA_OPTS   |Tomcat 起動時の JavaVM オプションを指定します。JAVA_OPT でも指定できますが、こちらに設定すると Tomcat のすべてのコマンド（例えば stop など）にも反映されてしまうため、CATALINA_OPTS に設定するほうが適切です。|
|JAVA_HOME       |Tomcat を起動する JavaVM の場所を指定します。|
|CLASSPATH       |Tomcat に追加するクラスパスを指定します。グローバルな CLASSPATH 環境変数は無視されるため、こちらに指定する必要があります。|