第14章 Apacheの基本設定とバーチャルホスト

LPIC-2 208.1 相当

世界のWebサーバーの多くを占めるApache HTTP Serverについて、基本的な設定の考え方を扱います。HTTPS化の詳細(証明書の作成・管理)は「HTTPS化とSSL/TLS証明書管理」、Nginx・Squidによるプロキシ構成は「プロキシサーバー(Squid/Nginxリバースプロキシ)」の各章で扱います。

Apache と Nginx の設計の違い

項目ApacheNginx
プロセスモデルリクエストごとにプロセス/スレッドを生成イベント駆動の非同期処理で、少ないプロセスで大量の接続を処理
設定ファイル.htaccessによるディレクトリ単位の上書きが可能.htaccess相当の仕組みはなく、中央設定に一元化
得意分野豊富なモジュール、動的コンテンツとの親和性静的コンテンツ配信、リバースプロキシとしての高性能
主なログの場所/var/log/apache2/(Debian系)/var/log/nginx/
設定の再読込apache2ctl graceful(既存接続を切らずに再読込)nginx -s reload

Apacheはさらに「MPM(Multi-Processing Module)」と呼ばれるプロセス処理方式を切り替えられます。伝統的なprefork(リクエストごとにプロセスを生成、モジュールの互換性が高い)、スレッドベースのworker、そしてNginxに近い非同期処理を取り入れたeventがあり、近年はeventが既定になっているディストリビューションが増えています。一方Nginxは、少数のワーカープロセスがイベント駆動で大量の接続を同時にさばく設計を最初から採用しており、これが「Nginxは大量の同時接続に強い」と言われる理由です。どちらが優れているというより、動的なコンテンツ処理はApacheのようなアプリケーションサーバーに任せ、Nginxを静的ファイル配信やリバースプロキシとして使う構成(次章以降で扱います)を採用することで、両者の強みを組み合わせるのが実務では一般的な設計です。

Apacheのディレクトリ構造とモジュール・サイトの有効化

Apacheの設定ファイルの構成は、ディストリビューション系統によって大きく異なります。

系統構成
Debian系sites-available/(サイト定義の置き場)とsites-enabled/(有効化されたサイトへのシンボリックリンク)、mods-available/とmods-enabled/(モジュールも同様の仕組み)に分離
RHEL系conf.d/(またはconf.modules.d/)以下に置いた*.confファイルを、httpd.confからIncludeで一括読み込みする方式

Debian系では、sites-available/にサイトの設定ファイルを置くだけでは有効になりません。a2ensiteコマンドでsites-enabled/へのシンボリックリンクを作成して初めて、そのサイトが有効になります。

$ sudo a2ensite example.com.conf     # sites-enabled/へシンボリックリンクを作成し有効化
Enabling site example.com.
To activate the new configuration, you need to run:
  systemctl reload apache2
$ sudo a2dissite example.com.conf    # シンボリックリンクを削除し無効化(ファイル自体は残る)
Site example.com disabled.
To activate the new configuration, you need to run:
  systemctl reload apache2
$ sudo a2enmod rewrite                # mod_rewriteを有効化
Enabling module rewrite.
To activate the new configuration, you need to run:
  systemctl restart apache2
$ sudo a2dismod status                # mod_statusを無効化
Module status disabled.
To activate the new configuration, you need to run:
  systemctl restart apache2
$ sudo systemctl reload apache2   # 正常時は無出力

この行のこの値に注目: a2ensite/a2enmod系のコマンドはいずれも、実行後に「反映するには reload/restart してください」という案内を表示するだけで、この時点ではまだ設定に反映されていません。モジュールの有効・無効化は多くの場合reloadではなくrestartが必要になる点が、サイトの有効化と異なります。

この仕組みの利点は、設定ファイル自体を削除せずに一時的な無効化・再有効化ができる点と、ls sites-enabled/だけで現在有効なサイトの一覧を素早く確認できる点です。RHEL系にはこのようなシンボリックリンクの仕組みはなく、conf.d/以下にファイルを置くかどうかがそのまま有効・無効に直結します(無効化したい場合はファイルの拡張子を.conf.disabledのように変えるなど、慣習的な回避策が使われます)。

Apacheのバーチャルホスト

1台のサーバーで複数のドメインを扱うための仕組みが「バーチャルホスト」です。

/etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com
    ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
</VirtualHost>
$ sudo a2ensite example.com.conf   # サイトを有効化(Debian系)
Enabling site example.com.
To activate the new configuration, you need to run:
  systemctl reload apache2
$ sudo apache2ctl configtest       # 設定ファイルの文法チェック
Syntax OK
$ sudo systemctl reload apache2   # 正常時は無出力

この行のこの値に注目: Syntax OKが表示されれば設定ファイルに文法エラーはありません。もしServerNameの記述漏れや閉じタグ忘れなどがあると、次のようなエラーになり、この状態でreloadしてもApacheは起動・反映されません。

$ sudo apache2ctl configtest
AH00526: Syntax error on line 3 of /etc/apache2/sites-enabled/example.com.conf:
DocumentRoot must be a directory
Action 'configtest' failed.

この行のこの値に注目: Syntax error on line 3 of ...という行が、どの設定ファイルの何行目に問題があるかを直接示しています。この例ではDocumentRootに指定したパスがディレクトリとして存在しないことが原因です。エラーを解消しSyntax OKになるまではreload・restartを実行しないのが安全です。

1台のサーバーで名前ベースの複数バーチャルホストを運用する場合、Apacheはリクエストに含まれるHostヘッダー(HTTP/1.1で必須)を見て、どのServerName/ServerAliasに一致するかでマッチさせます。一致するものがなければ、設定ファイルの中で最初に定義されたバーチャルホスト(既定のバーチャルホスト)が使われる点に注意が必要です。

ディレクトリ単位で細かい制御を行いたい場合は、バーチャルホスト定義の中に<Directory>ディレクティブを追加します。

<Directory /var/www/example.com>
    Options -Indexes +FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

AllowOverride All を指定すると、そのディレクトリ以下で .htaccess ファイルによる設定の上書きが有効になります。共用レンタルサーバーのように利用者ごとに設定を触らせたい場合に便利ですが、リクエストのたびに各ディレクトリの.htaccessを探しに行くためパフォーマンスが低下します。可能であれば中央の設定ファイルに直接書き、AllowOverride Noneにするのが推奨されます。

ディレクティブのコンテキストと評価順序

Apacheの各ディレクティブは、どこに書けるか(コンテキスト)があらかじめ決まっています。ドキュメントで「Context: server config, virtual host, directory」のように明記されている情報で、ディレクティブによって書ける場所が異なります。

コンテキスト意味
サーバー設定(server config)httpd.conf/apache2.confの最上位。サーバー全体に影響する
<VirtualHost>特定のバーチャルホストにのみ影響する
<Directory>指定したファイルシステム上のディレクトリパスに対して影響する
<Location>指定したURLパス(ファイルシステムのパスとは無関係)に対して影響する
<Files>指定したファイル名(パターン)に対して影響する
.htaccessそのディレクトリ以下のリクエストに、リクエストのたびに動的に適用される(AllowOverrideで許可された範囲のみ)

この行のこの値に注目: <Directory>はサーバー上の実ファイルパス(ディスク上の場所)を指定するのに対し、<Location>はクライアントがアクセスするURLパスを指定します。Aliasやmod_rewriteでURLとファイルパスの対応がずれている場合、この違いを混同すると意図した箇所に設定が適用されません。

複数のコンテキストが重なって同じリソースに適用される場合、Apacheは次の順序でディレクティブを評価し、後から評価されたものが基本的に優先されます。

Directory(アルファベット順、より深いパスが後) → DirectoryMatch
  → Files / FilesMatch
  → .htaccess(AllowOverrideで許可された範囲)

この評価順序を理解していないと、「Directoryディレクティブで許可したはずなのに、.htaccessの内容で上書きされて意図通りにならない」といったトラブルの原因が分からなくなります。

アクセス制御(Require系ディレクティブ)

Apache 2.4以降のアクセス制御は、mod_authz_coreが提供するRequire系のディレクティブに統一されています。

ディレクティブ意味
Require all grantedすべてのアクセスを許可する
Require all deniedすべてのアクセスを拒否する
Require ip アドレス指定したIPアドレス・ネットワークからのアクセスのみ許可する
Require host ホスト名指定したホスト名(逆引き結果)からのアクセスのみ許可する
Require valid-user認証(AuthUserFileなど)に成功した、任意の登録済みユーザーのみ許可する
Require user ユーザー名指定した特定のユーザーのみ許可する
Require group グループ名指定したグループに属するユーザーのみ許可する

複数のRequireディレクティブを組み合わせたい場合、RequireAll・RequireAny・RequireNoneブロックでまとめます。

<RequireAll>
    Require ip 192.168.1.0/24
    Require valid-user
</RequireAll>
# RequireAllはブロック内の条件をすべて満たした場合のみ許可(AND条件)

<RequireAny>
    Require ip 192.168.1.0/24
    Require ip 10.0.0.0/8
</RequireAny>
# RequireAnyはブロック内のいずれか1つでも満たせば許可(OR条件)

Apache 2.2以前はOrder・Allow・Denyという別の書式が使われていました。2.4系でもmod_access_compatを有効にすれば旧書式との互換性を保てますが、新規の設定では原則としてRequire系を使うべきとされています。

2.2以前の書式2.4のRequire系での対応
Order allow,deny
Allow from all
Require all granted
Order deny,allow
Deny from all
Require all denied
Order allow,deny
Allow from 192.168.1.0/24
Require ip 192.168.1.0/24

書き方が変わった主な理由は、Order・Allow・Denyの組み合わせが直感的でなく、Orderの引数の順序(allow,denyかdeny,allowか)によって同じAllow/Denyの行でも評価結果が変わってしまうという分かりにくさがあったためです。Require系は、認証条件(valid-userなど)とアクセス制御条件(ipなど)を同じ構文で統一的に、かつAND/ORの組み合わせも明示的に表現できるように設計し直されています。

htpasswdによるユーザー認証

特定のディレクトリへのアクセスに、Apache自身の機能でユーザー名・パスワードによる認証(Basic認証)をかけたい場合、htpasswdコマンドでパスワードファイルを作成し、そのディレクトリの設定で参照します。

$ sudo htpasswd -c /etc/apache2/.htpasswd alice   # -cは新規作成(既存ファイルに追加する場合は付けない)
New password:
Re-type new password:
Adding password for user alice

この行のこの値に注目: htpasswdもパスワード入力の際は画面に文字を表示せず、2回入力させて一致を確認します。最後のAdding password for user aliceで追加が完了したことが分かります。

VirtualHost内、または.htaccessでの設定例
<Directory /var/www/example.com/admin>
    AuthType Basic
    AuthName "Restricted Area"
    AuthUserFile /etc/apache2/.htpasswd
    Require valid-user
</Directory>

AuthUserFileで参照するパスワードファイルの場所を指定し、Require valid-userで「そのファイルに登録されている、どのユーザーでもよいのでログインが必要」であることを示します。この仕組みはmod_auth_basicとmod_authz_host(またはmod_access_compat)というモジュールによって提供されており、Apacheでは既定で有効になっていることがほとんどです。

$ sudo htpasswd /etc/apache2/.htpasswd bob     # 2人目以降は-cを付けない(既存ファイルへ追記)
New password:
Re-type new password:
Adding password for user bob
$ sudo htpasswd -B /etc/apache2/.htpasswd alice # -Bでbcryptハッシュを使用(より安全)
New password:
Re-type new password:
Updating password for user alice
$ sudo htpasswd -D /etc/apache2/.htpasswd bob   # -Dで指定したユーザーを削除
Deleting password for user bob

この行のこの値に注目: 新規追加時はAdding password for user ...、既存ユーザーの上書きはUpdating password for user ...と表示が分かれるため、意図せず新規ユーザーを追加してしまっていないかをこのメッセージで確認できます。-D削除時のDeleting password for user ...も同様に、対象ユーザー名を確認する材料になります。

htpasswdの-cオプションの罠: -c(create)は新規ファイルを作成するオプションで、初回のみ付けます。すでにユーザーが登録済みのファイルに対して誤って-cを付けて実行すると、既存のファイルが空の新規ファイルで上書きされ、それまで登録していた全ユーザーの情報が消えてしまいます。2人目以降のユーザーを追加する際は、-cを付けないよう注意してください。

グループ単位でアクセスを許可したい場合は、AuthGroupFileでグループ定義ファイルを指定し、Require group グループ名と組み合わせます。

/etc/apache2/.htgroup(AuthGroupFileで指定するファイルの書式)
admins: alice bob
editors: carol
<Directory /var/www/example.com/admin>
    AuthType Basic
    AuthName "Restricted Area"
    AuthUserFile /etc/apache2/.htpasswd
    AuthGroupFile /etc/apache2/.htgroup
    Require group admins
</Directory>

Basic認証は、ユーザー名とパスワードをBase64エンコードしただけの状態でリクエストヘッダーに含めて送信するため、暗号化されていないHTTP通信の上では盗聴によって容易に読み取られてしまいます。より安全なDigest認証(mod_auth_digest)は、パスワードそのものではなくハッシュ化されたダイジェストを送信する方式ですが、実装の複雑さや対応クライアントの少なさから現在ではあまり使われていません。実務での現実的な選択は、Basic認証を使う場合は必ずHTTPS(TLS)と組み合わせ、通信経路自体を暗号化することで盗聴のリスクを解消する、という方針です。

.htaccessとAllowOverrideの値

すでに触れたとおりAllowOverrideは、.htaccessファイルでどこまでの設定上書きを許可するかを制御します。All・Noneという両極端な値だけでなく、許可する範囲を細かく指定するキーワードも用意されています。

値許可される内容
None.htaccessファイル自体を読み込まない(最も高速で安全)
Allすべてのディレクティブの上書きを許可する
AuthConfigAuthType・AuthName・AuthUserFile・Requireなど認証関連のディレクティブのみ許可
FileInfomod_rewriteやリダイレクト、ドキュメントタイプ関連のディレクティブを許可
Indexesディレクトリの一覧表示(Options Indexesなど)関連のディレクティブを許可
LimitRequire等のアクセス制御ディレクティブを許可
.htaccessを使うべきでない理由: AllowOverrideがNone以外に設定されていると、Apacheはリクエストのたびに、対象ファイルが属するディレクトリから、ドキュメントルート(またはファイルシステムのルート)に向かって、各階層に.htaccessファイルが存在しないかを毎回探索します。この探索処理はディスクI/Oを伴うため、アクセスのたびに繰り返されることで無視できないパフォーマンス低下を招きます。

実務では、共用レンタルサーバーのように利用者に直接サーバーの中央設定ファイルを触らせられない環境を除き、本番環境ではAllowOverride Noneとしたうえで、必要な設定はすべて<Directory>や<VirtualHost>など中央の設定ファイルに直接記述するのが基本方針です。中央設定であれば、変更の反映に.htaccessのような即時反映は得られない代わりに(reloadが必要)、探索コストがかからず、設定内容もバージョン管理やレビューの対象にしやすくなります。

MPM(Multi-Processing Module)のチューニング

すでに紹介したprefork・worker・eventの3つのMPMは、リクエストの処理単位が異なります。

prefork:  [プロセス1(1接続)] [プロセス2(1接続)] [プロセス3(1接続)] ...
          (プロセスごとに1接続。シンプルだがメモリ消費が大きい)

worker:   [プロセス1─スレッドA(1接続)─スレッドB(1接続)─...]
          [プロセス2─スレッドA(1接続)─スレッドB(1接続)─...]
          (プロセス内の複数スレッドが複数接続を処理)

event:    [プロセス1─スレッドA(複数接続を非同期処理)─...]
          (keep-alive中の待機接続を専用スレッドで効率的に処理)

近年主流のevent MPMには、次のようなチューニング用ディレクティブがあります。

ディレクティブ意味
StartServers起動時に生成する子プロセス数
MinSpareThreads維持しておく待機スレッドの最小数
MaxSpareThreads維持しておく待機スレッドの最大数
ThreadsPerChild子プロセス1つあたりのスレッド数
MaxRequestWorkers同時に処理できるリクエスト数の上限(子プロセス数×ThreadsPerChildが目安の上限)
MaxConnectionsPerChild子プロセスが処理するリクエスト数の上限。超えると子プロセスを再起動し、メモリリークの蓄積を防ぐ

preforkを使う場合、MaxRequestWorkers(子プロセス数の上限、1プロセス=1リクエストなので同時接続数の上限に等しい)の適切な値は、次のような計算で見積もれます。

MaxRequestWorkers = 利用可能なメモリ量 ÷ 1プロセスあたりのメモリ使用量

例: 利用可能メモリが4GB、1プロセスあたり約40MBを消費する場合
     4096MB ÷ 40MB ≈ 102 が現実的な上限の目安

この行のこの値に注目: MaxRequestWorkersには、それとは別にServerLimitという上限値も存在し、MaxRequestWorkersはServerLimitの値を超えて設定することはできません(超えて指定すると、起動時にServerLimitの値まで自動的に切り下げられます)。ServerLimit自体をデフォルトより大きくしたい場合は、明示的にServerLimitディレクティブで引き上げてから、その範囲内でMaxRequestWorkersを設定する必要があります。

接続の持続に関するディレクティブも、性能に直結する重要な設定です。

ディレクティブ意味・トレードオフ
KeepAlive同一クライアントとのTCP接続を再利用するかどうか(On推奨。接続確立のオーバーヘッドを削減)
KeepAliveTimeout次のリクエストを待つ最大秒数。長すぎると接続を保持するプロセス/スレッドが増え続けてリソースを圧迫し、短すぎるとKeepAliveの恩恵が薄れる
MaxKeepAliveRequests1つの接続で処理できるリクエスト数の上限
Timeoutリクエスト処理全体のタイムアウト秒数
LimitRequestBodyリクエストボディ(アップロードされるデータなど)の最大サイズ

動的コンテンツの処理(mod_perl・PHP)

Apacheは、静的なHTMLファイルを返すだけでなく、モジュールを通じてPerlやPHPのようなスクリプト言語を実行し、動的にコンテンツを生成することもできます。

$ sudo apt install libapache2-mod-php   # PHPをApacheのモジュールとして組み込む
Setting up libapache2-mod-php8.3 (8.3.6-0ubuntu0.24.04.1) ...
apache2_invoke: Enable module php8.3
$ sudo a2enmod php8.1                    # モジュールを有効化
ERROR: Module php8.1 does not exist!

この行のこの値に注目: パッケージ導入時のapache2_invoke: Enable module ...行は、実はlibapache2-mod-phpのインストール時点で自動的にモジュールが有効化されていることを示しています。この例のUbuntu 24.04 LTSではPHP 8.3が同梱されているため、a2enmod php8.1のようにバージョンが一致しないモジュール名を指定するとdoes not existエラーになります。実際のバージョンはphp -vやapache2ctl -M | grep phpで確認してから指定します。

mod_perlはApacheプロセスにPerlインタプリタを常駐させることで、CGIとして毎回新しいプロセスを起動するより高速にPerlスクリプトを実行できる仕組みです。PHPも同様にmod_phpとしてモジュール組み込み型で動かす方法と、独立したプロセスとして動かすPHP-FPM(FastCGI経由)を使う方法があり、近年はプロセスを分離できる柔軟性からPHP-FPMを使う構成が主流になりつつあります。

近年の主流であるPHP-FPMは、Apacheプロセス自体にPHPを組み込むのではなく、独立したFastCGIプロセスとしてPHPを稼働させ、Apache側からはプロキシ経由でリクエストを転送する構成です。

VirtualHost内での設定例(PHP-FPMへのプロキシ)
<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
</FilesMatch>

この行のこの値に注目: SetHandlerの値にあるunix:/run/php/php8.2-fpm.sockは、PHP-FPMがUnixドメインソケットで待ち受けていることを示し、|fcgi://localhostの部分はFastCGIプロトコルでそのソケットへリクエストを転送することを示します。この構成はmod_phpと異なりPHPのプロセスがApache本体から完全に分離されているため、PHPプロセスをApacheとは別のユーザー権限で動かせる、PHPだけを個別に再起動できる、Apache側はpreforkに縛られずevent MPMを使って多数の同時接続をさばきながら、実際のPHP処理は別プロセス群で並列に行えるといった利点があります。

PHPやPerlのようなモジュール組み込み方式とは別に、外部プログラムをリクエストのたびに新しいプロセスとして起動する伝統的なCGI(Common Gateway Interface)という仕組みも、mod_cgi(またはスレッドセーフ版のmod_cgid)によって提供されています。

CGIスクリプトを実行可能にする設定例
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory /usr/lib/cgi-bin>
    Options +ExecCGI
    AddHandler cgi-script .cgi .pl
</Directory>

ScriptAliasは、指定したURLパス配下へのリクエストを、指定したディレクトリ内のプログラムとして実行するよう指示するディレクティブです。+ExecCGIオプションと合わせることで、そのディレクトリ内のファイルをCGIプログラムとして実行できるようになります。CGIはリクエストのたびに新規プロセスを起動するオーバーヘッドが大きく、mod_perlやPHP-FPMのような常駐型の仕組みと比べて性能面で劣るため、現在の実務で新規に採用されることは少なくなっていますが、既存の古いシステムや管理系スクリプトでは依然として使われることがあります。

リダイレクトと書き換え(mod_alias・mod_rewrite)

URLの転送やパスの付け替えは、目的に応じて複数のディレクティブが用意されています。

ディレクティブ用途
Redirect指定したパスへのリクエストを、別のURLへリダイレクトする(既定では302)
RedirectMatch正規表現でパスをマッチさせてリダイレクトする
RedirectPermanent常に301(恒久的な移動)としてリダイレクトする
AliasURLパスを、実ファイルシステム上の別の場所に対応付ける(DocumentRoot外のディレクトリを公開する際などに使用)
ScriptAliasAliasと同様だが、対応付けた先をCGIプログラムとして扱う

301(Moved Permanently)と302(Found、一時的な移動)の違いは、検索エンジンのインデックスやブラウザのキャッシュに影響します。ドメイン移転やHTTPからHTTPSへの恒久的な切り替えのように「今後もずっとこの新しいURLを使う」場合は301を、一時的なメンテナンスページへの切り替えなど「元のURLに戻る予定がある」場合は302を使うのが適切な使い分けです。

Redirect 301 /old-page.html https://example.com/new-page.html

より複雑なパターンでのURL書き換えにはmod_rewriteを使います。

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]

この行のこの値に注目: RewriteCondは、後続のRewriteRuleを適用する条件を指定します(この例では「HTTPSが有効でない場合」)。RewriteRuleの末尾にある[R=301,L]のような部分はフラグで、R=301は301リダイレクトとして応答することを、L(Last)はこのルールが適用されたら以降のRewriteRuleの評価を打ち切ることを意味します。他にも、内部的なプロキシ転送として扱うP(Proxy)フラグや、書き換え後のURLに元のクエリ文字列を引き継ぐQSA(Query String Append)フラグがよく使われます。

ログとアクセス解析の基礎

Apache/Nginxはいずれもアクセスログとエラーログを分けて記録します。アクセスログには、標準的な「Common Log Format」または拡張版の「Combined Log Format」がよく使われ、アクセス元IP・日時・リクエスト内容・ステータスコード・レスポンスサイズなどが1行ずつ記録されます。

Apache/Nginxのアクセスログの例(Combined Log Format)
192.0.2.20 - - [15/Aug/2026:10:00:00 +0900] "GET /index.html HTTP/1.1" 200 512 "-" "Mozilla/5.0"

末尾のステータスコードは、リクエストの処理結果を示します。2xxは成功、3xxはリダイレクト、4xxはクライアント側の問題(404 Not Foundなど)、5xxはサーバー側の問題(500 Internal Server Errorなど)を表します。障害調査の際は、まずエラーログで詳細なエラーメッセージを確認し、次にアクセスログで発生頻度やパターンを確認するのが基本的な流れです。

$ tail -f /var/log/nginx/access.log
203.0.113.10 - - [21/Aug/2026:09:15:03 +0900] "GET /index.html HTTP/1.1" 200 1024 "-" "Mozilla/5.0"
198.51.100.7 - - [21/Aug/2026:09:15:05 +0900] "POST /api/order HTTP/1.1" 500 512 "-" "curl/8.5.0"
(新着ログが追記されるたびに表示され続ける。Ctrl+Cで終了)
$ grep " 500 " /var/log/nginx/access.log | wc -l   # 500エラーの発生件数を数える
23

この行のこの値に注目: Nginxのアクセスログは各フィールドがスペース区切りで並ぶ形式で、7列目付近のステータスコード(この例では200や500)を見れば正常応答かエラーかがすぐに判別できます。grep " 500 "のようにスペースで囲んで検索することで、URLパスの一部に偶然「500」という文字列が含まれるケースを除外し、ステータスコード欄だけを正確に拾えます。

LogFormatの書式指定子とログの管理

アクセスログの出力形式はLogFormatディレクティブで定義され、CustomLogでその形式を使ってログファイルへ出力します。書式指定子を組み合わせることで、記録する項目を自由にカスタマイズできます。

指定子意味
%h接続元のホスト名(またはIPアドレス)
%lidentdによるクライアントの識別名(通常は取得できず"-")
%uBasic認証などで認証されたユーザー名
%tリクエストを受信した日時
%rリクエストの1行目(メソッド・パス・プロトコルバージョン)
%>s最終的なHTTPステータスコード
%bレスポンスボディのサイズ(バイト)
%{Referer}iRefererリクエストヘッダーの値
%{User-Agent}iUser-Agentリクエストヘッダーの値
%Dリクエストの処理にかかった時間(マイクロ秒)
%Tリクエストの処理にかかった時間(秒)
LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i" %D" combined
CustomLog ${APACHE_LOG_DIR}/access.log combined

Apacheには、上記の指定子を組み合わせたいくつかの既定の書式が名前付きで用意されています。common(基本的な項目のみ)、combined(commonにReferer・User-Agentを追加)、vhost_combined(combinedにバーチャルホスト名を追加、複数サイトのログを1ファイルに集約する際に有用)がよく使われます。

特定の条件でログの出力を除外したい場合(例:ヘルスチェックや静的ファイルへのアクセスをログから除外してノイズを減らす)は、環境変数とSetEnvIfを組み合わせます。

SetEnvIf Request_URI "\.(gif|jpg|png|css|js)$" dontlog
CustomLog ${APACHE_LOG_DIR}/access.log combined env=!dontlog

env=!dontlogは、「dontlogという環境変数がセットされていない場合にのみ」ログに出力するという条件です。静的ファイル(画像・CSS・JSなど)へのリクエストに対してdontlog変数をセットしておくことで、それらのアクセスをログから除外できます。

エラーログの詳細度はLogLevelで制御します。emerg(緊急)からdebug(詳細なデバッグ情報)まで段階があり、値が詳細になるほどログの量も増えます。通常運用ではwarn程度が一般的で、トラブルシューティング時に一時的にdebugへ引き上げて詳細を確認する、という使い方をします。

ログファイルは放置すると際限なく肥大化するため、logrotateによる定期的なローテーションが必須です。logrotateとは別に、Apache自身が生成するログをパイプで直接別プログラムへ渡すrotatelogsという方法もあります。

CustomLog "|/usr/bin/rotatelogs /var/log/apache2/access.%Y%m%d.log 86400" combined
# 86400秒(1日)ごとに新しいログファイルへ切り替える

rotatelogs方式は、Apacheプロセス自体がログの切り替えを担うため、外部のlogrotateによるシグナル送信(プロセスへの再オープン指示)を待たずに確実なタイミングでローテーションできる利点があります。

apachectlによる運用管理

Apacheの起動・停止・設定確認を行うapachectl(Debian系ではapache2ctl)には、日常運用で頻繁に使うサブコマンドが多数あります。

サブコマンド効果
configtest / -t設定ファイルの文法チェックのみを行い、実際には起動・反映しない
-S設定されているすべてのVirtualHostの解決結果(どのIP・ポートにどのServerNameが割り当てられているか)を表示する
-M現在読み込まれているモジュールの一覧を表示する
-Vコンパイル時の設定(バージョン、デフォルトの設定ファイルパスなど)を表示する
graceful既存の接続を切断せずに、新しい設定で穏やかに再起動する(現在の接続が完了してから順次新しい設定を適用)
graceful-stop既存の接続の完了を待ってから、穏やかに停止する
$ sudo apache2ctl -S
VirtualHost configuration:
*:443                  example.com (/etc/apache2/sites-enabled/example.com-ssl.conf:1)
*:80                   is a NameVirtualHost
         default server example.com (/etc/apache2/sites-enabled/example.com.conf:1)
         port 80 namevhost example.com (/etc/apache2/sites-enabled/example.com.conf:1)

この行のこの値に注目: apache2ctl -Sの出力は、複数のバーチャルホスト設定が意図通りに読み込まれているかを確認する際に非常に有用です。特に「アクセスしたつもりのドメインとは違う既定のバーチャルホストに繋がってしまう」ようなトラブルでは、この出力を見ることで、どのバーチャルホストがdefault server(Hostヘッダーが一致しなかった場合に使われる既定のバーチャルホスト)として扱われているかをすぐに特定できます。

実務のヒント: 設定変更後にいきなりsystemctl restartするのではなく、必ずapache2ctl configtestで文法エラーがないことを確認してからgraceful(またはsystemctlのreload)を実行する習慣をつけましょう。設定ミスに気づかないままrestartしてしまうと、サービスがそのまま停止した状態になってしまいます。

server-status(mod_status)による稼働状況の可視化

mod_statusを有効にすると、現在の接続数・処理中のリクエスト・各ワーカーの状態をブラウザやcurlから確認できる/server-statusというエンドポイントが提供されます。

/etc/apache2/mods-enabled/status.conf の例
<Location /server-status>
    SetHandler server-status
    Require ip 127.0.0.1
</Location>
$ curl http://localhost/server-status?auto
Total Accesses: 128340
Total kBytes: 512044
BusyWorkers: 4
IdleWorkers: 16

?autoを付けると、人間向けのHTML表示ではなく、スクリプトで扱いやすいプレーンテキスト形式の出力が得られ、監視ツール(Zabbix、Prometheusのapache_exporterなど)から定期的に取得してグラフ化するのに向いています。

注意: server-statusには、稼働中の各ワーカーが処理しているリクエストの内容(クライアントIP、リクエストされたURLなど)まで表示されます。Require ipなどでアクセスを内部ネットワークや特定のIPアドレスのみに厳しく制限せず、誰でもアクセスできる状態で公開してしまうと、サーバーの内部状況やアクセスパターンを外部に漏らしてしまう情報漏洩のリスクがあります。必ずアクセス制限をかけたうえで有効化しましょう。

Nginxのプロセスモデルと設定の勘所

Apacheと対比しながら軽く触れてきたNginxですが、実際の設定でも独自の考え方が多く登場します。まずプロセスモデルに関わる基本設定から見ていきましょう。

/etc/nginx/nginx.conf の一部
worker_processes auto;              # ワーカープロセス数。autoでCPUコア数に自動設定
events {
    worker_connections 1024;        # 1ワーカープロセスが同時に処理できる接続数
}

この行のこの値に注目: Nginxが理論上さばける最大同時接続数は、おおまかにworker_processes × worker_connectionsで見積もれます。worker_processes autoは論理CPUコア数と同じ数のワーカープロセスを起動する設定で、CPUバウンドな処理を各コアに分散させつつ、Nginx自体のイベント駆動処理と組み合わせることで高い同時接続処理能力を実現しています。

Nginxのリクエスト振り分けの中心となるlocationブロックは、複数のブロックが同時にマッチしうる場合、次の優先順位で評価されます。

記法意味優先順位
location = /path完全一致最優先
location ^~ /path前方一致(この後の正規表現マッチをスキップする)2番目
location ~ /pattern正規表現マッチ(大文字小文字を区別)3番目(記述順で最初に一致したもの)
location ~* /pattern正規表現マッチ(大文字小文字を区別しない)3番目(同上)
location /path前方一致(通常の)最後(最長一致が優先)

静的ファイルの配信では、複数の候補パスを順に試し、最初に見つかったものを返すtry_filesがよく使われます。

location / {
    try_files $uri $uri/ /index.html;
}
# リクエストされたパスのファイルを探し、なければディレクトリとして探し、
# それもなければ/index.htmlを返す(SPA向けのフォールバック構成でよく使われる)

リバースプロキシとして動作させる際は、バックエンド側にクライアントの本来の情報を伝えるため、プロキシ用のヘッダーを明示的に設定します。

location / {
    proxy_pass http://backend;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

X-Forwarded-ForとX-Real-IPを設定しないと、バックエンドのアプリケーションから見えるアクセス元IPアドレスは、実際のクライアントIPではなく、常にNginx自身のIPアドレス(プロキシ元)になってしまいます。アクセスログの分析やIPベースのアクセス制限をバックエンド側で行いたい場合、これらのヘッダーで元のクライアントIPを伝搬させることが不可欠です。

複数台のバックエンドサーバーへ負荷分散したい場合はupstreamブロックを使います。

upstream backend {
    least_conn;                  # 接続数が最も少ないサーバーへ優先的に振り分ける
    server 10.0.0.11:8080;
    server 10.0.0.12:8080;
    keepalive 32;                # バックエンドとの間で再利用する接続プールのサイズ
}
負荷分散方式動作
(既定・無指定)round-robin。リクエストを順番に各サーバーへ振り分ける
least_connその時点で接続数が最も少ないサーバーへ振り分ける
ip_hashクライアントIPアドレスのハッシュ値に基づき、同じクライアントは常に同じサーバーへ振り分ける(セッション維持に有用)

転送データの圧縮(gzip on;)や、レスポンスをキャッシュして同じリクエストへの応答を高速化するproxy_cache_pathの設定も、Nginxをリバースプロキシとして使う際の代表的なパフォーマンスチューニング項目です。

gzip on;
gzip_types text/css application/javascript application/json;

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m max_size=1g;
location / {
    proxy_cache mycache;
    proxy_pass http://backend;
}

proxy_cache_pathのkeys_zone=mycache:10mは、キャッシュキーの情報を保持する共有メモリ領域にmycacheという名前を付け、10MiBを割り当てることを意味し、max_size=1gはディスク上のキャッシュデータの上限サイズです。同じリクエストに対する応答をキャッシュから返せるようになると、バックエンドの負荷を大幅に削減できます。

Webサーバーのセキュリティヘッダー

HTTPSの導入に加えて、レスポンスヘッダーによってブラウザ側の防御を強化する設定も、実務では標準的に行われます。代表的なものに、コンテンツの読み込み元を制限してXSS攻撃のリスクを下げるContent-Security-Policy、MIMEタイプの誤判定によるスニッフィング攻撃を防ぐX-Content-Type-Options: nosniff、他サイトへの埋め込み(クリックジャッキング)を防ぐX-Frame-Optionsなどがあります。これらは本来アプリケーション側で実装すべき対策を補完するものであり、Webサーバーの設定だけで完全に安全になるわけではありませんが、比較的少ない手間で一定の防御層を追加できるため、公開サーバーの基本的な設定項目として押さえておく価値があります。

確認クイズ

Q1. 多数の同時接続をイベント駆動の非同期処理で効率よく処理することを得意とするWebサーバーはどれですか?

解説: Nginxはイベント駆動の非同期処理を採用しており、少ないプロセス数で大量の同時接続を効率よく処理できるのが特徴です。

Q2. 1台のサーバーで複数のドメインのWebサイトを提供する仕組みを何と呼びますか?

解説: Apacheでは「バーチャルホスト」、Nginxでは「サーバーブロック」と呼ばれる仕組みで、1台のサーバーが複数のドメインを扱えます。

Q3. Apacheの設定ファイルの文法エラーがないかを確認するコマンドはどれですか?

解説: apache2ctl configtestは、Apacheの設定ファイルの文法をチェックするコマンドです。Nginxの場合はnginx -tを使います。

Q4. ディレクトリ単位で.htaccessによる設定の上書きを有効にするApacheのディレクティブはどれですか?

解説: AllowOverride Allを指定すると、そのディレクトリ以下で.htaccessファイルによる設定の上書きが有効になります。ただしリクエストのたびに探索が発生するためパフォーマンスは低下します。

Q5. htpasswdで作成したパスワードファイルの場所を指定するApacheのディレクティブはどれですか?

解説: AuthUserFileで、htpasswdコマンドで作成したパスワードファイルの場所を指定し、Require valid-userと組み合わせてBasic認証を実現します。

Q6. Debian系でsites-available/に置いたサイト定義ファイルを有効化するために実行するコマンドはどれですか?

解説: a2ensiteは、sites-available/の設定ファイルへsites-enabled/からのシンボリックリンクを作成し、そのサイトを有効化します。

Q7. <Directory>ディレクティブと<Location>ディレクティブの違いとして正しいものはどれですか?

解説: Directoryはディスク上の実パスを、Locationはリクエストされるurlパスを指定するディレクティブです。AliasやRewriteでパスがずれている場合は特に注意が必要です。

Q8. RequireAllブロックとRequireAnyブロックの違いとして正しいものはどれですか?

解説: RequireAllはブロック内の条件をすべて満たした場合のみ許可(AND)、RequireAnyはいずれか1つでも満たせば許可(OR)します。

Q9. Apache 2.2以前のOrder/Allow/Denyから2.4のRequire系へ書き方が変わった主な理由はどれですか?

解説: Order/Allow/Denyは引数の順序次第で評価結果が変わる分かりにくさがあり、Require系はAND/OR条件を明示的に表現できるよう再設計されました。

Q10. すでにユーザーが登録済みのhtpasswdファイルに対して誤って-cオプションを付けて実行した場合の結果はどれですか?

解説: -c(create)は新規ファイル作成用のオプションで、既存ファイルに対して使うと上書きされ登録済みユーザー情報が失われます。

Q11. Basic認証を安全に運用するための実務的な方針として適切なものはどれですか?

解説: Basic認証はBase64エンコードのみで平文同然のため、HTTPSと組み合わせて通信経路を暗号化することが安全な運用の前提になります。

Q12. 本番環境でAllowOverride Noneが推奨される主な理由はどれですか?

解説: AllowOverrideがNone以外だと、Apacheはリクエストのたびに各階層の.htaccessを探索するため、パフォーマンスが低下します。

Q13. prefork MPMでMaxRequestWorkersの妥当な値を見積もる計算式はどれですか?

解説: preforkは1プロセス=1接続のため、利用可能なメモリ量を1プロセスあたりの消費メモリで割ることで、現実的な上限の目安を見積もれます。

Q14. MaxRequestWorkersの値について正しいものはどれですか?

解説: MaxRequestWorkersはServerLimitの範囲内でのみ設定可能で、超えて指定するとServerLimitの値まで自動的に切り下げられます。

Q15. SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost" の設定が意味することはどれですか?

解説: この設定は、独立したFastCGIプロセスであるPHP-FPMへ、Unixドメインソケット経由でリクエストをプロキシ転送することを意味します。

Q16. Redirect 301とRedirect(既定の302)の使い分けとして適切なものはどれですか?

解説: 301(恒久的な移動)と302(一時的な移動)は検索エンジンのインデックスやブラウザキャッシュへの影響が異なるため、状況に応じて使い分けます。

Q17. RewriteRuleの末尾に付けるフラグ[R=301,L]のLフラグの意味はどれですか?

解説: L(Last)フラグは、そのルールが適用された時点で、以降のRewriteRuleの評価を打ち切ることを意味します。

Q18. Apacheのアクセスログの書式指定子%Dが示す内容はどれですか?

解説: %Dはリクエストの処理にかかった時間をマイクロ秒単位で記録します。秒単位で記録したい場合は%Tを使います。

Q19. CustomLog ... combined env=!dontlog の env=!dontlog が意味することはどれですか?

解説: env=!dontlogは、dontlog変数が設定されていない場合にのみログ出力する条件で、SetEnvIfで静的ファイルなどにdontlogをセットして除外する運用に使われます。

Q20. apache2ctl -Sの出力から確認できる内容はどれですか?

解説: apache2ctl -Sは、VirtualHostの設定がどのように解決されているかを表示し、意図しない既定バーチャルホストへの誤接続などのトラブル切り分けに有用です。

Q21. mod_statusのserver-statusエンドポイントに、Require ipなどのアクセス制限を必ずかけるべき理由はどれですか?

解説: server-statusにはクライアントIPやリクエストURLなどの内部状況が表示されるため、無制限に公開すると情報漏洩のリスクがあります。

Q22. Nginxのlocationブロックのうち、他の記法よりも優先して評価される完全一致の記法はどれですか?

解説: location = /pathは完全一致を意味し、Nginxのlocationマッチングの中で最優先で評価されます。

Q23. Nginxのリバースプロキシ設定でproxy_set_header X-Forwarded-Forを省略した場合に起こることはどれですか?

解説: X-Forwarded-ForやX-Real-IPを設定しないと、バックエンド側からは元のクライアントIPが分からず、常にプロキシ元(Nginx)のIPとして記録されてしまいます。

Q24. Nginxのupstreamブロックでip_hashを指定する目的はどれですか?

解説: ip_hashは、クライアントIPのハッシュ値に基づいて振り分け先を固定し、セッション維持が必要な構成で使われます。