nginxでvirtualhostを設定する方法について複数のアプローチで紹介します。先日技育アカデミアでお話させていただいた「マルチテナントwebサーバの作り方」を再構成しました。
目標
ubuntu22.04の環境を前提として、以下のようなweb環境を構築します。
各ユーザは自身のhomeディレクトリにコンテンツを設置できる各ユーザはそれぞれ複数のドメインを所有できる各ドメインはそれぞれ異なるPHPバージョンを利用できる
よくあるレンタルサーバのような環境ですね。
では早速構築していきましょう。
事前準備
先に動作確認のためのユーザ、ドメイン、コンテンツを作成しておきます。ドメイン名、ユーザ名は適宜読み替えてください。
# useradd -m -s /bin/bash user1
# useradd -m -s /bin/bash user2
# mkdir -p /home/user1/public_html/userdomain1.com
# mkdir -p /home/user1/public_html/userdomain2.net
# mkdir -p /home/user2/public_html/userdomain3.org
動作確認用コンテンツとしては、index.htmlとphpのバージョンを表示するだけのversion.phpを用意します。
<html>userdomain1.com works!</html>
<html>userdomain2.net works!</html>
<html>userdomain3.org works!</html>
<?php echo 'PHP version: ' . phpversion() . "\n"; ?>
<?php echo 'PHP version: ' . phpversion() . "\n"; ?>
<?php echo 'PHP version: ' . phpversion() . "\n"; ?>
nginxのインストールとデフォルトページ表示
ubuntu22.04のリポジトリに含まれるnginxはバージョン1.18ですので、今回はnginxの公式リポジトリから最新版をインストールします。
まず以下のようにaptソースを追加します。
deb https://nginx.org/packages/ubuntu/ jammy nginx
deb-src https://nginx.org/packages/ubuntu/ jammy nginx
保存したら以下のようにパッケージをインストールしてください。
# apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ABF5BD827BD9BF62
# apt update
# apt install nginx
またこれは必須ではありませんが、後々サーバに他のユーザをログインさせることを考えnginxの設定内容を秘匿したい場合は、設定ディレクトリのパーミッションを変更しておきましょう。
# chown -R nginx:nginx /etc/nginx
# chmod -R o-rx /etc/nginx
また、firewallのポートを開けておきます。
# ufw allow 80/tcp
# ufw allow 443/tcp
# ufw reload
ここまで出来たらnginxを起動しましょう。
# systemctl enable nginx
# systemctl start nginx
アクセスできるか確認してみましょう。
$ curl http://userdomain1.com
無事に表示できましたか?
この段階でnginxが起動しない場合、元からapacheが動いていたりh2loadコマンドのために入れたnghttp2が既に80番ポートを使用している可能性があります。それらが動いていたら止めて、nginxを再起動してみましょう。
では次の作業に移ります。
virtualhostの設定
ubuntu標準パッケージでは、ドメイン毎のvirtualhost設定ファイルをsites-availableに設置し、有効にするドメインをsites-enabled内にsymlinkで定義するスタイルですので、それを踏襲していきます。
まずはディレクトリを作成。
# mkdir /etc/nginx/sites-available
# mkdir /etc/nginx/sites-enabled
次にsites-available内に設定ファイルを設置します。
server {
listen 80;
server_name userdomain1.com www.userdomain1.com;
root /home/user1/public_html/userdomain1.com;
access_log /var/log/nginx/userdomain1.com_access.log main;
location / {
index index.html index.htm;
}
}
server {
listen 80;
server_name userdomain2.net www.userdomain2.net;
root /home/user1/public_html/userdomain2.net;
access_log /var/log/nginx/userdomain2.net_access.log main;
location / {
index index.html index.htm;
}
}
server {
listen 80;
server_name userdomain3.org www.userdomain3.org;
root /home/user2/public_html/userdomain3.org;
access_log /var/log/nginx/userdomain3.org_access.log main;
location / {
index index.html index.htm;
}
}
続いてsites-enabled内にsymlinkを設置します。
# cd /etc/nginx/sites-enabled
# ln -s ../sites-available/userdomain1.com.conf ./
# ln -s ../sites-available/userdomain2.net.conf ./
# ln -s ../sites-available/userdomain3.org.conf ./
sites-enabled内の設定ファイルを読み込むように、nginx.confに記述を追加します。
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf; ★この行を追加
}
user1,user2のホームディレクトリは、作成したままの状態だとパーミッション700になっていると思います。これではnginxユーザがファイルを読みにいけません。しかし他のユーザには見られたくない。こんな時にはACLを使うと良いです。nginxユーザにのみディレクトリへのアクセス権限を与えます。
# apt install acl
# setfacl -m u:nginx:x /home/user1
# setfacl -m u:nginx:x /home/user2
ではnginxを再起動してアクセスを確認してみましょう。
# systemctl restart nginx
# curl http://userdomain1.com
# curl http://userdomain2.net
# curl http://userdomain3.org
それぞれのサイトのコンテンツが見えましたか?
まだnginxのデフォルトページが表示されてしまう場合、sites-enabled/*.confを読み込む設定が正しい位置に入っていない、sites-availableにファイルは作ったがsites-enabledにシンボリックリンクを作成していない、これらの修正をしたあとnginxを再起動していない可能性があります。
404や403になってしまう場合、設定ファイルのタイプミス、作成したファイルやディレクトリ名のタイプミス、権限設定の不備により、nginxユーザが各ユーザのhomeディレクトリ内のファイルを見れていない可能性があります。以下のようにして問題なく表示できたらnginxの設定ファイルの方を疑ってみてください。
# sudo -u nginx cat /home/user1/public_html/userdomain1.com/index.html
以後は設定ファイルも大きくなってくるので、具体例はuserdomain1.comのみ示します。
userdomain2, userdomain3についても読み替えて追加してください。
SSLの設定
ここではlets encryptのcertbotを利用して、http認証で<ドメイン名>、www.<ドメイン名>の2way証明書をhttp認証で取得します。このステップでは実在のドメインが必要になります。
# apt install certbot
# certbot certonly --manual --server https://acme-v02.api.letsencrypt.org/directory --preferred-challenges http-01 -d userdomain1.com -d www.userdomain1.com -m [email protected] --agree-tos --manual-public-ip-logging-ok
certbotの指示に従って、letsencryptが認証のためにアクセスしてくるファイルを作成します。
# vim /home/user1/public_html/userdomain1.com/.well-known/acme-challenge/RANDOMSTRING
先ほどvirtualhostが正しく設定されていれば、以下のように作成したファイルにHTTPからアクセスできるはずです。ここまでできたらcertbotの処理を進めて、lets encryptに認証してもらいましょう。
$ curl http://userdomain1.com/.well-known/acme-challenge/RANDOMSTRING
認証に成功すると証明書は/etc/letsencrypt/以下に作成されます。
archiveが実ファイル、live以下はarchive内の最新版を指すsymlinkです。
このlive以下のPATHをそのまま利用しても良いのですが、後々他の手段で取得した証明書も混在させることを想定してnginx設定ディレクトリの中にsymlinkを作成しておきましょう。
# mkdir /etc/nginx/certs/
# cd /etc/nginx/certs/
# ln -s /etc/letsencrypt/live/userdomain1.com/fullchain.pem ./userdomain1.com.crt
# ln -s /etc/letsencrypt/live/userdomain1.com/privkey.pem ./userdomain1.com.key
ではバーチャルホスト設定にSSL用ブロックを追加していきます。
server {
listen 80;
server_name userdomain1.com www.userdomain1.com;
root /home/user1/public_html/userdomain1.com;
access_log /var/log/nginx/userdomain1.com_access.log main;
location / {
index index.html index.htm;
}
}
server {
listen 443;
server_name userdomain1.com www.userdomain1.com;
root /home/user1/public_html/userdomain1.com;
access_log /var/log/nginx/userdomain1.com_access.log main;
ssl on;
ssl_certificate /etc/nginx/certs/userdomain1.com.crt;
ssl_certificate_key /etc/nginx/certs/userdomain1.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE+AESGCM:DHE+aRSA+AESGCM:ECDHE+AESCCM:DHE+aRSA+AESCCM:ECDHE+CHACHA20:DHE+aRSA+CHACHA20:+AES128:+DHE;
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256;
location / {
index index.html index.htm;
}
}
cipher関連はデフォルトでもいいですが、IPAがガイドラインを公開していますのでそちらに従っておくのが無難です。ここでは1.1.1節の「高セキュリティ型の設定例」を参考にしました。
https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/tls_cipher_suite_config_20200707.pdf
userdomain2,userdomain3も同様に設定したらnginxを再起動してアクセスを確認してみましょう。
# systemctl restart nginx
# curl https://userdomain1.com
httpsでのアクセスも警告無しに表示できたでしょうか?
再起動に失敗する場合は、nginxのエラーログを確認してみてください。
# tail /var/log/nginx/error.log
PHP-FPMのインストールと設定
ubuntu22.04標準リポジトリのphpは8.1ですが、今回は複数バージョンのPHPを入れたいのでondrejリポジトリを利用します。
# add-apt-repository ppa:ondrej/php
# apt install php8.1 php8.1-fpm
# apt install php8.2 php8.2-fpm
php-fpmの設定ファイルは/etc/php/X.X/fpm/pool.dに設置します。ユーザ毎ドメイン毎のPHP設定はいくつかのパターンが考えられますが、「どのバージョンが」「誰の権限で」動いているのか分かりやすい命名規則にすることをお勧めします。下記の例を参考にしてみてください。
[user1]
user = user1
group = user1
listen = /run/php/php8.1-fpm-user1.sock
listen.owner = nginx
listen.group = nginx
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 1000
[user1]
user = user1
group = user1
listen = /run/php/php8.2-fpm-user1.sock
listen.owner = nginx
listen.group = nginx
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 1000
user2についても同様に作成してください。
ここではunixソケットを利用する形にしましたので、phpを実行する権限であるuser,groupは各ユーザ、ソケットはnginxによってアクセスされるのでlisten.owner,listen.groupはnginxとしています。
pmについてはこの程度のプロセス数であればstaticとdynamicの差はほぼ無いと言えるでしょう。サーバのリソースと収容するユーザ数によって選択してください。数百数千ドメインを収容するような環境でphp-fpmによるリソース消費を抑え、必要な時だけプロセスを起動したい場合はondemandを利用します。
ではphp-fpmを起動しておきましょう。
# systemctl enable php8.1-fpm php8.2-fpm
# systemctl start php8.1-fpm php8.2-fpm
ソケットが作られていることを確認します。作られていないときはもう一度php-fpmを再起動してください。
# ls -l /run/php/php8.?-fpm-user*
srw-rw---- 1 nginx nginx 0 Nov 27 13:00 /run/php/php8.1-fpm-user1.sock
srw-rw---- 1 nginx nginx 0 Nov 27 13:00 /run/php/php8.1-fpm-user2.sock
srw-rw---- 1 nginx nginx 0 Nov 27 13:00 /run/php/php8.2-fpm-user1.sock
srw-rw---- 1 nginx nginx 0 Nov 27 13:00 /run/php/php8.2-fpm-user2.sock
virtualhostにphpの設定を追加します。
server {
listen 80;
server_name userdomain1.com www.userdomain1.com;
root /home/user1/public_html/userdomain1.com;
access_log /var/log/nginx/userdomain1.com_access.log main;
location / {
index index.html index.htm;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm-user1.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
}
server {
listen 443;
server_name userdomain1.com www.userdomain1.com;
root /home/user1/public_html/userdomain1.com;
access_log /var/log/nginx/userdomain1.com_access.log main;
ssl on;
ssl_certificate /etc/nginx/certs/userdomain1.com.crt;
ssl_certificate_key /etc/nginx/certs/userdomain1.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE+AESGCM:DHE+aRSA+AESGCM:ECDHE+AESCCM:DHE+aRSA+AESCCM:ECDHE+CHACHA20:DHE+aRSA+CHACHA20:+AES128:+DHE;
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256;
location / {
index index.html index.htm;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm-user1.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
}
user1の権限でphp8.2を使いたいドメインでは"fastcgi_pass unix:/run/php/php8.2-fpm-user1.sock;"、user2の権限でphp8.1を使いたいドメインでは"fastcgi_pass unix:/run/php/php8.1-fpm-user2.sock;"となります。
ではnginxを再起動してphpにアクセスしてみましょう。
# systemctl restart nginx
# curl https://userdomain1.com/version.php
想定したバージョンが表示されたでしょうか?
PHPのソースがそのまま表示されてしまったときは、"location ~ .php$"のブロックが対象バーチャルホストのlisten 80, listen 443それぞれに追加されているか確認してください。
nginxでVirtualDocumentRoot
ここからはvirtualhost設定のバリエーションを紹介していきます。一つ目はapacheのVirtualDocumentRootに相当することをやってみます。
VirtualDocumentRootとは、ディレクトリを一定の法則に従って配置することで、アクセス時のホスト名(ドメイン名)を元にしてDocumentRootを自動的に割り当ててくれる機能です。
ドメイン追加の際はディレクトリを追加するだけでよく、apacheの再起動をせずにドメイン追加・削除を行えるメリットがあります。しかしドメイン毎のSSL証明書設定やPHPバージョン設定を行うことはできず、利用する場面は限定的になります。
一方nginxでは設定ファイルに組み込み変数を用いることができるため、SSLやPHPバージョンの問題点も克服したVirtualDocumentRoot相当の設定ができます。
では早速設定を見ていきましょう。
# mkdir -p /var/nginx/vdr-sites
# mkdir /etc/nginx/vdr-php
一つ目のディレクトリは/var/nginx以下に作成している点に注意してください。DocumentRootはuser1,user2など一般ユーザ権限で閲覧できる必要があります。/etc/nginxは一般ユーザが参照できないようにパーミッションを設定しているので、別途/var以下にDocumentRoot用のディレクトリを作成します。
次にDocumentRoot用のsymlinkを作成します。
# ln -s /home/user1/public_html/userdomain1.com /var/nginx/vdr-sites/userdomain1.com
# ln -s /home/user1/public_html/userdomain1.com /var/nginx/vdr-sites/www.uesrdomain1.com
userdomain1.comとwww.userdomain1.comの二つのsymlinkを作成していますね。virtualhostではserver_nameとしてuserdomain1.comとwww.userdomain1.comを併記していました。しかしこのVirtualDocumentRoot風の設定では、それぞれ別々に行う必要があります。
php用のsymlinkを作成します。
# ln -s /run/php/php8.1-fpm-user1.sock /etc/nginx/vdr-php/userdomain1.com
# ln -s /run/php/php8.1-fpm-user1.sock /etc/nginx/vdr-php/www.userdomain1.com
SSL証明書用のsymlinkも作成します。上から順に進めてきた方は、www無しの方のsymlinkは作成済みかもしれません。
# ln –s /etc/letsencrypt/live/userdomain1.com/fullchain.pem /etc/nginx/certs/userdomain1.com.crt
# ln –s /etc/letsencrypt/live/userdomain1.com/privkey.pem /etc/nginx/certs/userdomain1.com.key
# ln –s /etc/letsencrypt/live/userdomain1.com/fullchain.pem /etc/nginx/certs/www.userdomain1.com.crt
# ln –s /etc/letsencrypt/live/userdomain1.com/privkey.pem /etc/nginx/certs/www.userdomain1.com.key
nginxユーザが鍵ファイルにアクセスできるように権限を追加します。
# setfacl -m u:nginx:x /etc/letsencrypt/live
# setfacl -m u:nginx:x /etc/letsencrypt/archive
# setfacl -m u:nginx:r /etc/letsencrypt/archive/*/privkey1.pem
なぜこのような作業が必要なのでしょうか。
nginxはroot権限で起動するmasterプロセスとnginxユーザ権限で起動するworkerプロセスが協調して動作します。virtualhost設定の際にはmasterプロセスがroot権限で全ての証明書と鍵を読み込んで、その情報をworkerプロセスに渡していました。しかし今回の設定ではworkerプロセスがアクセスを受けるたびに証明書と鍵を読みに行きます。その処理はnginxユーザの権限で行われるため、nginxユーザが証明書と秘密鍵を読み取れるよう権限の追加が必要になります。
ではnginxの設定を進めていきます。先にvirtualhostの設定を進めていた方は、nginx.confでsites-enabled以下のconfを読み込む設定をコメントアウトしておいてください。
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
#include /etc/nginx/sites-enabled/*.conf; ★この行をコメントアウト
}
conf.dディレクトリにあるdefault.confを無効化します。
# mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.disabled
新たにvirtualdocumentroot.confを作成します。
log_format vcombined '$host $remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
server {
listen 80 default_server;
server_name _;
root /var/nginx/vdr-sites/$http_host;
access_log /var/log/nginx/access.log vcombined;
location / {
index index.html index.htm;
}
location ~ \.php$ {
fastcgi_pass unix:/etc/nginx/vdr-php/$http_host;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
}
server {
listen 443 default_server;
server_name _;
root /var/nginx/vdr-sites/$http_host;
access_log /var/log/nginx/access.log vcombined;
ssl on;
ssl_certificate /etc/nginx/certs/$ssl_server_name.crt;
ssl_certificate_key /etc/nginx/certs/$ssl_server_name.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE+AESGCM:DHE+aRSA+AESGCM:ECDHE+AESCCM:DHE+aRSA+AESCCM:ECDHE+CHACHA20:DHE+aRSA+CHACHA20:+AES128:+DHE;
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256;
location / {
index index.html index.htm;
}
location ~ \.php$ {
fastcgi_pass unix:/etc/nginx/vdr-php/$http_host;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
}
DocumentRootを指定するrootやPHPの指定は$http_hostになっていますが、SSL証明書の指定は$ssl_server_nameが使われています。これは何故でしょうか。
$http_hostはその名の通りHTTPプロトコルの中でHostヘッダにより渡される値が入ります。しかしSSL/TLS接続確立の段階ではまだHTTPプロトコルは開始していないのでHostで何も渡されていません。その代わりTLSハンドシェイクの中でServerNameが渡されますので、そちらを用いて必要な証明書を特定します。
ではその$ssl_server_nameをそのままDocumentRootに利用すればいいじゃないかと思われるかもしれません。そもそもTLSハンドシェイクのServerNameとHTTP Hostで別のホスト名を渡すことが可能という時点で、それを受け取ったサーバ側がどう振る舞うべきかについては議論のあるところだと思います。ここでは返すコンテンツはHTTPプロトコルで渡されたHostに従うこととして$http_hostでDocumentRootを決定しています。$ssl_server_nameと$http_hostの一致を確認する方法については次の章で取り入れてみます。
またアクセスログもこれまでドメイン毎に別ファイルにしていました。今回の方法でもaccess_log /var/log/nginx/$http_host.access.logのようにすれば別ファイルにできますが、HTTPのHostで渡されたどんな文字列でもファイルを作成してしまうのはよろしくないので、Hostをアクセスログ内に記載するlog_formatを定義して一つのログファイルに入れています。
ではnginxを再起動して動作を確認してみましょう。
# systemctl restart nginx
# curl https://userdomain1.com/version.php
symlinkの削除や追加、phpのリンク先変更によるphpバージョン変更も試してみてください。
njsによるvirtualhostの実現
nginxには、njsと呼ばれるjavascriptで設定を動的に行う仕組みが用意されています。これを用いてvirtualhostを実現してみましょう。
njsでは、まだDBアクセスを行うモジュールがサポートされていないため、以下のようなCSVでvirtualhostを管理する形にします。
userdomain1.com,/home/user1/public_html/userdomain1.com,8.1,user1
userdomain2.net,/home/user1/public_html/userdomain2.net,8.2,user1
userdomain3.org,/home/user2/public_html/userdomain3.org,8.2,user2
まずはnjsをインストールします。nginxの公式リポジトリを有効にしていればaptで簡単に入ります。
# apt install nginx-module-njs
次にnginx.confでnjsのモジュールを読み込みます。
さらにjavascriptのfunctionを用いて変数を定義する記述を追加します。
ここではDocumentRootのPATH、PHPバックエンドのソケットのPATH、SSL証明書と秘密鍵ファイルをjavascriptで動的にセットすることとします。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
load_module modules/ngx_http_js_module.so; ★njsモジュール読み込み
load_module modules/ngx_stream_js_module.so; ★njsモジュール読み込み
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
js_path "/etc/nginx/njs"; ★njsのスクリプトを設置するPATH
js_import main from "virtualhost.js"; ★njsのスクリプトファイル名
js_set $docroot main.get_docroot; ★変数$docrootにvirtualhost.jsで定義した関数get_docrootの実行結果を格納
js_set $ssl_cert_file main.get_ssl_cert_file; ★$ssl_cert_fileにget_ssl_cert_fileの実行結果を格納
js_set $ssl_cert_key_file main.get_ssl_cert_key_file; ★$ssl_cert_key_fileにget_ssl_cert_key_fileの実行結果を格納
js_set $accesslog main.get_accesslog; ★$accesslogにget_accesslogの実行結果を格納
js_set $backend main.get_backend; ★$backendにget_backendの実行結果を格納
include /etc/nginx/conf.d/*.conf;
#include /etc/nginx/sites-enabled/*.conf;
}
これらの変数を利用したバーチャルホスト設定ファイルを設置します。先ほどのvirtualdocumentroot.confが残っていたら退避しておいてください。
server {
listen 80 default_server;
server_name _;
root $docroot;
access_log /var/log/nginx/$accesslog main;
location / {
index index.html index.htm;
}
location ~ \.php$ {
fastcgi_pass $backend;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
}
server {
listen 443 default_server;
server_name _;
root $docroot;
access_log /var/log/nginx/$accesslog main;
ssl on;
ssl_certificate $ssl_cert_file;
ssl_certificate_key $ssl_cert_key_file;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE+AESGCM:DHE+aRSA+AESGCM:ECDHE+AESCCM:DHE+aRSA+AESCCM:ECDHE+CHACHA20:DHE+aRSA+CHACHA20:+AES128:+DHE;
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256;
location / {
index index.html index.htm;
}
location ~ \.php$ {
fastcgi_pass $backend;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
}
njsで実行されるjavascriptを設置します。
var fs = require('fs');
function _read_csv() {
var VHOSTS = '/etc/nginx/njs/sites-enabled.csv';
var data = '';
try {
data = fs.readFileSync(VHOSTS,'utf-8');
} catch (e) {
}
var lines = data.split('\n');
return data;
}
function get_ssl_cert_file(r) {
var ssl_server_name = r.variables['ssl_server_name'];
ssl_server_name = ssl_server_name.replace(/^www\./,'');
if (ssl_server_name){
return '/etc/nginx/certs/' + ssl_server_name + '.crt';
} else {
return '';
}
}
function get_ssl_cert_key_file(r) {
var ssl_server_name = r.variables['ssl_server_name'];
ssl_server_name = ssl_server_name.replace(/^www\./,'');
if (ssl_server_name){
return '/etc/nginx/certs/' + ssl_server_name + '.key';
} else {
return '';
}
}
function get_docroot(r) {
var host = r.headersIn.Host;
host = host.replace(/^www\./,'');
var ssl_server_name = r.variables['ssl_server_name'];
if (ssl_server_name){
ssl_server_name = ssl_server_name.replace(/^www\./,'');
if (ssl_server_name != host){
return '';
}
}
var data = _read_csv();
var lines = data.split('\n');
var results = lines.filter(line => line.startsWith(host + ','));
var splitted = results[0].split(',');
return splitted[1];
}
function get_accesslog(r) {
var host = r.headersIn.Host;
host = host.replace(/^www\./,'');
var data = _read_csv();
var lines = data.split('\n');
var results = lines.filter(line => line.startsWith(host + ','));
var splitted = results[0].split(',');
var enabled_host = splitted[0];
if (enabled_host){
return enabled_host + '.access.log';
} else {
return 'access.log';
}
}
function get_backend(r) {
var host = r.headersIn.Host;
host = host.replace(/^www\./,'');
var data = _read_csv();
var lines = data.split('\n');
var results = lines.filter(line => line.startsWith(host + ','));
var splitted = results[0].split(',');
var php_version = splitted[2];
var user = splitted[3];
var backend = 'unix:/run/php/php' + php_version + '-fpm-' + user + '.sock';
return backend;
}
export default {get_ssl_cert_file, get_ssl_cert_key_file, get_docroot, get_accesslog, get_backend}
get_docrootの中で、ssl_server_nameとHTTP Hostが不一致だった場合にはDocumentRootのPATHを返さない(結果としてクライアントに403 forbiddenが返る)処理を入れています。
アクセスログもsites-enabled.csvに無いドメインでのアクセスを受けたときは個別のファイルではなくaccess_logに格納するようにしています。
ではnginxを再起動して動作を確認してみましょう。
# systemctl restart nginx
# curl https://userdomain1.com/version.php
うまく表示されたでしょうか。csvを編集すればドメインの追加もphpバージョン変更もnginxの再起動無しに反映されます。
まとめ
いかがでしたか?一口にvirtualhostの設定と言っても様々なアプローチがあることを見ていただけたかと思います。
実際の運用においては数十ドメイン程度のサーバであればvirtualhostがお勧めです。
VirtualDocumentRoot風の設定はdocument_rootが/var/vdr-sites/DOMAIN、CWDが/home/user1/public_html/DOMAINのように食い違いが発生するデメリットがあります。
njsについては現状データベース接続モジュールなどは利用できず、多数のvirtualhostの管理をCSVファイルで行うというのは実運用環境では厳しいかもしれません。
それぞれの目的に合わせて最適な方法を選んでください。