nginxのvirtualhost設定まとめ

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ファイルで行うというのは実運用環境では厳しいかもしれません。

それぞれの目的に合わせて最適な方法を選んでください。

ブログの著者欄

小島 慶一

GMOインターネットグループ株式会社

デベロッパーエキスパート 2002年GMOインターネットグループ株式会社入社。 interQ、bekkoame等のホスティング商材の運用から、お名前.comレンタルサーバSDの開発、z.com webhostingを開発を担当。 最近はConoHa WING,お名前RSの開発運用の傍ら、グループのホスティング商材全般と関わりを深めたいと思っている。

採用情報

関連記事

KEYWORD

採用情報

SNS FOLLOW

GMOインターネットグループのSNSをフォローして最新情報をチェック