先日、Apacheを使ってTLS・SSLに対応したバーチャルホストを構築していてど嵌まりしたことについて書いてみました。
そもそもの発端はApacheでバーチャルホストを構築する際の設定は何処に書くべきなのか?なのだけど、古い(例えばApache 2.2系列時代の)記事では/etc/httpd/conf/httpd.conf
やTLS・SSL通信に対応するなら/etc/httpd/conf.d/ssl.conf
を修正するなり、設定を追加するなりして自分のホスト環境に合わせての設定を行っている記事が多かったように思います。
しかし最近のApache 2.4系列のApacheでは、直接/etc/httpd/conf/httpd.conf
や/etc/httpd/conf.d/ssl.conf
を修正するするのではなく、/etc/httpd/conf.d/
の下に新しく自分のホスト(WEBサイト)向けの設定ファイルを新たに作りここに設定を書き込むことで設定を行うことが主流になっているようです。
で、今回も新しいWEBサイトを設定するにあたり/etc/httpd/conf/httpd.conf
の下に[ドメイン名].conf
ファイルを作り、そこで以下のような設定を行いApacheを起動してWEBブラウザからサイトにアクセスしてみましたが、TLS・SSL接続を使わずにhttp://[ドメイン名]
でアクセスするとちゃんと想定通りの内容が表示されるのだけど、何故か設定がTLS・SSL接続(https://[ドメイン名]
)でアクセスするとデフォルトのApacheテストページが表示されてしまうし、TLS・SSL証明書も/etc/httpd/conf.d/ssl.conf
で指定されている自動生成されるダミーの自己証明書が使われてしまうと言う謎の現象に遭遇しました。
/etc/httpd/conf.d/
の下に追加して作った設定ファイルの内容
<VirtualHost *:80>
ServerName webserver.example.com
DocumentRoot /var/www/webserver.example.com/public_html
ErrorLog /var/log/httpd/webserver.example.com-error.log
CustomLog /var/log/httpd/webserver.example.com.log combined
</VirtualHost>
<VirtualHost *:443>
ServerName webserver.example.com
DocumentRoot /var/www/webserver.example.com/public_html
ErrorLog /var/log/httpd/webserver.example.com-ssl_error.log
CustomLog /var/log/httpd/webserver.example.com.ssl_log combined
SSLCertificateFile /etc/letsencrypt/live/webserver.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/webserver.example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
「あれ?変だな〜」と思い、まず試してみたのは自分が運用している他のバーチャルホストをサポートする環境に同じ設定を作り、そちらにDNSの向き先を変えて動かしてみた。
でも特に同じような問題は発生せずにhttp://webserver.example.com/
でもリアルなホスト名でもアクセスができる。
次にチェックしたのがmod_sslをインストールしたときに同時にインストールされた/etc/httpd/conf.d/ssl.conf
の設定。
/etc/httpd/conf.d/ssl.conf
の中には以下のようにすでにデフォルトのバーチャルホストの設定が書かれている。
/etc/httpd/conf.d/ssl.conf
の40行目あたり
##
## SSL Virtual Host Context
##
<VirtualHost _default_:443>
# General setup for the virtual host, inherited from global configuration
#DocumentRoot "/var/www/html"
#ServerName www.example.com:443
あ〜、そうか!ここでデフォルト(_defallt_
)のバーチャルホストの設定がされているから、別の設定ファイルでバーチャルホストの設定をしても、そのホスト名(ServerName
やServerAlias
)が実ホスト名にマッチしちゃうとこちらが優先されちゃうのか!
と気がついて、Apacheを稼働させているホストの名前をwebserver.example.com
から、vhost.example.com
とか別の名前に変えてみた。
しかし、それでも結果は変わらない。
そこで思いついたのは、もしかしてTLS・SSL証明書のコモンネーム(CN
)がマッチする証明書を使う設定があるとそれが優先されるのかな?と…
で、openssl
コマンドで証明書の内容をチェックしてみた。
openssl x509 -text -in /etc/pki/tls/certs/localhost.crt
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 475065158036033417 (0x697c53e85e70f89)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=Unspecified, OU=ca-2753923304029444439, CN=webserver.example.com, emailAddress=root@webserver.example.com
Validity
Not Before: Feb 21 06:28:31 2025 GMT
Not After : Feb 21 06:28:31 2026 GMT
Subject: C=US, O=Unspecified, CN=webserver.example.com, emailAddress=root@webserver.example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:bf:28:8b:f3:c1:eb:02:38:4f:1c:05:7f:a0:4f:
あれ?証明書のコモンネーム(CN
)hが変わっていない?
ちょっと調べてみるとこのダミーの証明書はApache起動時に/usr/libexec/httpd-ssl-gencerts
スクリプトで自動生成され、ダミーの証明書がすでに生成済みな場合には再生成はされずに、過去に生成されたものをそのまま使うらしいので、まずは過去に生成されたダミーの証明書を削除してApacheを再起動してみた。
ダミーのTLS・SSL証明書の削除と再生成
sudo rm /etc/pki/tls/certs/localhost.crt
sudo rm /etc/pki/tls/private/localhost.key
sudo systemctl restart httpd
もう一度openssl
コマンドで証明書の内容をチェックしてみた。
ダミーの証明書のコモンネーム(CN
)は設定しなおしたホスト名('vhost.example.com')に変わっていたので、WEBブラウザでhttps://webserver.example.com/
へとアクセスしてみる。
やっぱり表示されるのは変わらずにApacheのテストページ
opensslでリモートからhttps://webserver.example.com/
へとアクセスして証明書をチェックしてみるとやはりダミーの証明書(コモンネームは'vhost.example.com'に変わっている)が使われているので、Apacheの設定ファイルとしては/etc/httpd/conf.d/ssl.conf
が使われているらしい。
openssl s_client -connect webserver.example.com:443 -servername webserver.example.com
Connecting to nnn.nnn.nnn.nnn
CONNECTED(00000005)
depth=1 C=US, O=Unspecified, OU=ca-5015121004957820975, CN=vhost.example.com, emailAddress=root@vhost.example.com
verify error:num=19:self-signed certificate in certificate chain
verify return:1
depth=1 C=US, O=Unspecified, OU=ca-5015121004957820975, CN=vhost.example.com, emailAddress=root@vhost.example.com
verify return:1
depth=0 C=US, O=Unspecified, CN=vhost.example.com, emailAddress=root@vhost.example.com
verify return:1
---
Certificate chain
0 s:C=US, O=Unspecified, CN=vhost.example.com, emailAddress=root@vhost.example.com
i:C=US, O=Unspecified, OU=ca-5015121004957820975, CN=vhost.example.com, emailAddress=root@vhost.example.com
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Feb 25 05:47:07 2025 GMT; NotAfter: Feb 25 05:47:07 2026 GMT
1 s:C=US, O=Unspecified, OU=ca-5015121004957820975, CN=vhost.example.com, emailAddress=root@vhost.example.com
i:C=US, O=Unspecified, OU=ca-5015121004957820975, CN=vhost.example.com, emailAddress=root@vhost.example.com
a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256
v:NotBefore: Feb 25 05:47:07 2025 GMT; NotAfter: Feb 25 05:47:07 2026 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIE5jCCAs6gAwIBAgIIPZ3D9IPUdvQwDQYJKoZIhvcNAQELBQAwgYkxCzAJBgNV
BAYTAlVTMRQwEgYDVQQKDAtVbnNwZWNpZmllZDEfMB0GA1UECwwWY2EtNTAxNTEy
クライアントからアクセスするのに使ったホスト名(-servername
)はwebserver.example.com
で、証明書のコモンネーム(CN
)はvhost.example.com
で、ましてや自己証明書なので、当然検証の結果はエラー(Verify return code: 19 (self-signed certificate in certificate chain)
)
00d0 - 88 55 85 af 9a 0e 37 4f-43 9e f4 3b 6f 33 82 39 .U....7OC..;o3.9
00e0 - ef db 42 4e 9b 55 00 b1-90 8f 1b bc 9a 2b 95 5e ..BN.U.......+.^
Start Time: 1740462620
Timeout : 7200 (sec)
Verify return code: 19 (self-signed certificate in certificate chain)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
closed
ちなみに、hostname
コマンドで問い合わせても、念の為に/etc/hostname
ファイルを確認してみてもApacheを動作させているホストのホスト名はvhost.example.com
となっているし、ダミーのTLS・SSL証明書のコモンネーム(CN
)はvhost.example.com
だし、ではApacheは何処で_default_
にマッチするホスト名としてwebserver.example.com
を設定してしまったのだろう?ということで、次の手段としてhttpd
を-S
オプションを付けて起動して、設定ファイルの読み込みと設定の経過を観察してみた。
/usr/sbin/httpd -S
VirtualHost configuration:
*:80 webserver.example.com (/etc/httpd/conf.d/webserver.example.com.conf:1)
*:443 is a NameVirtualHost
default server webserver.example.com (/etc/httpd/conf.d/ssl.conf:40)
port 443 namevhost webserver.example.com (/etc/httpd/conf.d/ssl.conf:40)
port 443 namevhost webserver.example.com (/etc/httpd/conf.d/webserver.example.com.conf:8)
ServerRoot: "/etc/httpd"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/etc/httpd/logs/error_log"
すると何故か?/etc/httpd/conf.d/ssl.conf
がデフォルトのホスト名(_default_
)として認識しているホスホネームがwebserver.example.com
となっている。
grep
を使いgrep -lr "webserver.example.com" /etc
とかやって文字列webserver.example.com
を探しても何処にも無いし、このホスト名は一体どこから持ってきた?と思ったら、思わぬところに伏兵が!
今回のWEBサーバー構築にはさくらインターネットの「さくらのVPS」を使ったので、「さくらのVPS」ではちゃんとVPSホストに割り当てられたIPアドレスに対してDNSの逆引きのホスト名を設定できるので、これをwebserver.example.com
に設定していたのだが、どうやらApacheは設定ファイルを読み込んでのデフォルトのバーチャルホストのホスト名(_default_
)には、ホストのIPアドレスから逆引きして得られたホスト名を使うらしい。
と言うことは!
もしかして、DNSの逆引きホスト名をwebserver.example.com
じゃないホスト名に変えてしまうか、さくらインターネットが決めた元のホスト名に戻してしまえば上手く動くんじゃないの?
と思ってやってみました。
dig -x nnn.nnn.nnn.nnn
; <<>> DiG 9.16.23-RH <<>> -x nnn.nnn.nnn.nnn
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 46072
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;nnn.nnn.nnn.nnn.in-addr.arpa. IN PTR
;; ANSWER SECTION:
nnn.nnn.nnn.nnn.in-addr.arpa. 3600 IN PTR wwwXXXXue.sakura.ne.jp.
;; Query time: 511 msec
;; SERVER: 210.224.163.3#53(210.224.163.3)
;; WHEN: Wed Feb 26 16:53:40 JST 2025
;; MSG SIZE rcvd: 91
案の定!、DNSの逆引きホスト名を元に戻したらhttpd -S
で確認したデフォルトサーバー(default server
)のホスト名がさくらインターネットで定義されたホスト名に変わっています。
※この時、WEBサーバーのホストのhostname
コマンドや/etc/hostname
の設定はwebserver.example.com
のままです。
/usr/sbin/httpd -S
VirtualHost configuration:
*:80 webserver.example.com (/etc/httpd/conf.d/webserver.example.com.conf:1)
*:443 is a NameVirtualHost
default server wwwXXXXue.sakura.ne.jp (/etc/httpd/conf.d/ssl.conf:40)
port 443 namevhost wwwXXXXue.sakura.ne.jp (/etc/httpd/conf.d/ssl.conf:40)
port 443 namevhost webserver.example.com (/etc/httpd/conf.d/webserver.example.com.conf:8)
ServerRoot: "/etc/httpd"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/etc/httpd/logs/error_log"
この状態でWEBブラウザなどのクライアントアプリからhttps://webserver.example.com/
へとアクセスすれば期待通りのバーチャルホスト設定/etc/httpd/conf.d/webserver.example.com.conf
が使われるはずです。
試しにopenssl
を使ってWEBサーバーへと接続して想定どおりの証明書が使われているかを確認してみます。
openssl s_client -connect webserver.example.com443 -servername webserver.example.com
Connecting to nnn.nnn.nnn.nnn
CONNECTED(00000005)
depth=2 C=US, O=Internet Security Research Group, CN=ISRG Root X1
verify return:1
depth=1 C=US, O=Let's Encrypt, CN=R11
verify return:1
depth=0 CN=example.com
verify return:1
---
Certificate chain
0 s:CN=example.com
i:C=US, O=Let's Encrypt, CN=R11
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Feb 15 11:46:07 2025 GMT; NotAfter: May 16 11:46:06 2025 GMT
1 s:C=US, O=Let's Encrypt, CN=R11
i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFMzCCBBugAwIBAgISA5P3+oblpLleVpcbszj2CTdmMA0GCSqGSIb3DQEBCwUA
MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD
この証明書ではCN=
がexample.com
となっていますが、これはここで使った証明書がワイルドカード証明書で、マッチするドメイン名は証明書のSAN(Subject Alternative Name)で指定されています。
例えば、この証明書が置かれたホスト上で以下のように証明書の内容を確認することで、SANでどのようなホスト名をマッチさせているかを確認することができます。
openssl x509 -text -in /etc/letsencrypt/live/example.com/cert.pem
X509v3 Subject Alternative Name:
DNS:*.example.com, DNS:example.com
ちゃんと最後のベリファイ結果もOKとなっているようです。
00d0 - 79 f3 2a bf 8f 9f 06 2c-23 a8 9f 92 b2 67 ee be y.*....,#....g..
00e0 - 6a fd 60 ab 2b 90 ab b2-b5 52 33 4f 1f e8 61 a7 j.`.+....R3O..a.
Start Time: 1740557291
Timeout : 7200 (sec)
Verify return code: 0 (ok)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
closed
ここまで散々と弄って何なのだけど、さらに色々と試しているうちにもっと単純なルールを発見!
実は!(も何も、当たり前と言われれば当たり前なのだけど…)諸々の設定は早い者勝ち的に先に定義されたものが有効になるようなので、/etc/httpd/conf.d/
の中から読み込まれる設定ファイルの順番をファイル名を工夫することで調整してssl.confよりも前に追加したバーチャルホストの設定ファイル(今回の例では/etc/httpd/conf.d/webserver.example.com.conf
)が読み込まれるようにすればこれが有効になるので全て解決です。
この/etc/httpd/conf.d/
ディレクトリの中から設定ファイルが読み込まれる順番はディレクトリをls
コマンドでリストした際の表示順(アルファベット順)で読み込まれます。
例えば今回の例では/etc/httpd/conf.d/
ディレクトリをls
コマンドでリストすると
(-1
オプションはファイル名を1行に1件表示するオプションです)
ls -1 /etc/httpd/conf.d/*.conf
/etc/httpd/conf.d/autoindex.conf
/etc/httpd/conf.d/ssl.conf
/etc/httpd/conf.d/userdir.conf
/etc/httpd/conf.d/webserver.example.com.conf
/etc/httpd/conf.d/welcome.conf
のようにssl.conf
がwebserver.example.com.conf
より先に読み込まれることが判ります。
これを例えば以下のようにファイルwebserver.example.com.conf
の前に文字列00_
を付けることで00_webserver.example.com.conf
をssl.conf
より先に読み込ませることができます。
ls -1 /etc/httpd/conf.d/*.conf
/etc/httpd/conf.d/00_webserver.example.com.conf
/etc/httpd/conf.d/autoindex.conf
/etc/httpd/conf.d/ssl.conf
/etc/httpd/conf.d/userdir.conf
/etc/httpd/conf.d/welcome.conf
その様子をhttpd -S
で確認しても先にバーチャルホストwebserver.example.com
の設定として/etc/httpd/conf.d/00_webserver.example.com.conf
が使われているのが判ります。
/usr/sbin/httpd -S
VirtualHost configuration:
*:80 webserver.example.com (/etc/httpd/conf.d/00_webserver.example.com.conf:1)
*:443 is a NameVirtualHost
default server webserver.example.com (/etc/httpd/conf.d/00_webserver.example.com.conf:8)
port 443 namevhost webserver.example.com (/etc/httpd/conf.d/00_webserver.example.com.conf:8)
port 443 namevhost webserver.example.com (/etc/httpd/conf.d/ssl.conf:40)
ServerRoot: "/etc/httpd"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/etc/httpd/logs/error_log"
でも、このディレクトリの中の設定ファイルの読み込み順で動作が変わっちゃうやつに関しては、大抵の場合はここでの対処のように最初から(パッケージ化する時点で!)nn_ファイル名
みたいになっていて、それを見てみんな「ああそうか!そんな感じでファイル名には注意が必要なのか!」と気がつくんだと思うんで「先にそれを言ってよ〜」な感じはする…