ubuntuにインストールしたnginxでCGIを動かしてみた。

ubuntuにインストールしたnginxでCGIを動かしてみました。もともとApacheで動いていたCGIスクリプトをnginxで動くようにしています。環境は以下です。作業はrootユーザーで行いました。

【環境】
ubuntu 18.04 LTS
nginx 1.16.1

まず、nginxでCGIを動かすためにはfcgiwrapが必要なのでインストールします。

fcgiwrapのインストール
# apt install fcgiwrap

パッケージのインストールができたらfcgiwrapを確認してみます。

インストールされたパッケージの確認
# dpkg -l | grep cgi

僕の環境では赤枠で囲ったパッケージがインストールされたようです。

[広告]

fcgiwrapの設定を確認します。fcgiwrapでは設定ファイルのようなものはないらしく、/etc/init.d配下にある起動スクリプトを確認します。

fcgiwrapの起動スクリプトの場所
/etc/init.d/fcgiwrap

この起動スクリプトに記載されている設定の中で確認する項目の抜粋(以下はデフォルト値)です。


 :
NAME="fcgiwrap"
 :
# FCGI_APP Variables
FCGI_CHILDREN="1"
FCGI_SOCKET="/var/run/$NAME.socket"
FCGI_USER="www-data"
FCGI_GROUP="www-data"

# Socket owner/group (will default to FCGI_USER/FCGI_GROUP if not defined)
FCGI_SOCKET_OWNER="www-data"
FCGI_SOCKET_GROUP="www-data"
 :

最初に、

NAME=”fcgiwrap”
FCGI_SOCKET=”/var/run/$NAME.socket”

ですが、これはnginxとfcgiwrapとのデータの受け渡しの方法です。nginxでは静的コンテンツしか処理できずCGIのような動的コンテンツはfcgiwrapが行うことになるのですが、CGIへのリクエストをnginxからfcgiwrapにどのような方法で受け渡すかを設定します。受け渡しの方法にはUnixソケットを使う方法とTCP/IPを使う方法があるのですが、デフォルトの設定ではUnixソケットを使う方法となっています。一般的にnginxとfcgiwrapを動かすサーバーが同一であればUnixソケットを使ったほうが効率がよいです。ですので、FCGI_SOCKETの設定はこのまま(デフォルトのまま)とします。

実際に、/var/run配下にfcgiwrap.socketがあるかどうか確認してみると以下のようにありました(緑線の部分、ピンク色線については後述)。

次に、

FCGI_CHILDREN=”1″

ですが、これはfcgiwrapがリクエストを待つ待機プロセスの数です。fcgiwrapはこの数分のプロセスを生成(C言語でいうfork)をしていて、リクエストが連続で来た際に待ちを作らずに並列に処理を裁ける数となります。本来はリクエスト数とサーバー性能を考慮してチューニングするべきですが、ここではいったんデフォルトのままとしておきます。

続いて、

FCGI_USER=”www-data”
FCGI_GROUP=”www-data”

ですが、これはfcgiwrapを実行するユーザー、および、グループを設定します。ここにはCGIスクリプトを実行できるユーザーを設定するのですが、ubuntuでApacheを動かす際は一般的にwww-dataを使用していて、今回はApacheで動かしていたCGIスクリプトをそのまま動かす想定なのでwww-dataのままとしておきます。

最後に、

FCGI_SOCKET_OWNER=”www-data”
FCGI_SOCKET_GROUP=”www-data”

ですが、これは先ほど記載したfcgiwrap.socketの所有者、および、グループの設定です。記載がなかった場合はFCGI_USER、FCGI_GROUPと同じになると起動スクリプトのコメントには記載されていました。こちらもデフォルトのwww-dataのままとしていたのですがfcgiwrap.socketの所有者、グループはrootになっていました(fcgiwrap.socketを確認した際のピンク色の線のところ)。ここのところの差異の理由は、ちょっとわかりませんでした。所有者はrootですが、www-dataでも読み書きできる権限となっているため問題ないのかもしれません。

fcgiwrapの設定の確認はここまでです。結果的にですが、fcgiwrapの設定は何も変えずにデフォルトのままで行きます。引き続き、nginxの設定に入ります。

[広告]

nginxの設定ファイルにCGIスクリプトへのリクエストがあった場合の動きを記載します。記載する設定ファイルはnginx.conf、もしくは、conf.d配下のconfファイルです。

nginxの設定ファイルの場所
/etc/nginx/nginx.conf
/etc/nginx/conf.d 配下のconfファイル

※nginx.confがconf.d配下のconfファイルを読み込んで(includeして)います。

記載する内容は以下です。serverディレクティブの中に記載してください。


location ~* \.cgi$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.cgi)(/.+)$;
    include fastcgi_params;
    fastcgi_index index.cgi;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_intercept_errors on;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
}

この中で一番最後にあるfastcgi_passの設定が、nginxからfcgiwrapにUnixソケットを使用してデータを引き渡す、という設定になります。他の設定はパラメータファイルのインクルードとか404エラーファイルの出力などですが、細かい説明は省略します(すみません)。そのまま貼り付けてもらえれば動くはずです。。

上記を反映した僕の場合のserverディレクティブを載せておきます。

nginx.confにnginxを動かすユーザーが記載されているのですが、そのユーザーをnginxからwww-dataに変更しておきます。nginx.confの「user」という項目です。

変更前
user nginx;
 ↓
変更後
user www-data;

以上でnginxのconfファイルの修正は終了です。confファイルのテストを行います。

confファイルの確認
# nginx -t

「syntax is ok」および「test is successful」と表示されれば問題ありません。今回はfcgiwrapの設定は変えていないのでnginxの再起動のみ行います。

nginxの再起動
# systemctl restart nginx

nginxでCGIスクリプトにアクセスしてみてください。動くと思います。

SNI(Server Name Indication)の仕組みの話

1台のWEBサーバーにバーチャルホストを設定して複数のWEBサイトを運営することはよくあることです。バーチャルホストとは1つのIPアドレスを複数のドメインで共有する技術のことです。ここでバーチャルホストの仕組みをおさらいしておきます。

WEBサーバーではxxx.comとyyy.comのドメインを運営しているものとします。

①ブラウザがxxx.comにアクセスをします。HTTP1.1以降の仕様ではHTTPヘッダにHostフィールドが必須となっているためブラウザはHostフィールドにドメイン(ここではxxx.com)を設定してWEBサーバーにリクエストを送ります。

②WEBサーバーはリクエストを受け取りHostフィールドを取得します。Hostフィールドに設定されたドメインを参照することにより、どのドメインへのアクセスかがわかります。

③バーチャルホストの設定ファイルをもとにドメインのマッチングを行い、HTMLファイルの取得(もしくは生成)をします。

④WEBサーバーは該当するWEBサイトのHTMLファイルをブラウザに返却します。

今まではこれで問題なかったのですが近年は常時TLS化が進んだ影響でこの仕組みではドメインが判断できなくなりました。どういうことかというと、TLSによりHTTPリクエストが暗号化されているためWEBサーバーのほうでHostフィールドが判断不能になりました。

そこでこの問題を解決するために導入された仕組みがSNI(Server Name Indication)です。どうやるかというと、暗号化されたHTTPリクエストとは別に「Hostフィールドに該当する情報」をブラウザからWEBサーバーに送るようにしています。このやりとりはTLSのハンドシェイク時に行われます。

SNIはブラウザとWEBサーバーが対応していれば良いためインターネットを利用する人が特に意識をする必要はありません。常時TLS化が一般的になってきた現在ではほとんどのブラウザがSNIに対応済みです。WEBサーバー側はTLSのハンドシェイクにOpenSSLを利用している場合、0.9.8以降で対応しているようです。

ちなみにですが、Hostフィールドに該当する情報をTLSのハンドシェイクの中のどこでやりとりしているのか、どんな感じで設定しているのかを確認しようと思い、Wiresharkを使ってキャプチャしてみました。以下がキャプチャした結果です。通信相手のサーバー情報は伏せています。

どうやらブラウザからは「Client Hello」の中にある「Server Name Indication extension」という部分にドメイン情報を入れてWEBサーバーに送っているようです(赤枠で囲ったところ)。暗号化はされていないのでWEBサーバー側でバーチャルホストの判断はできそうですね。。
※TLSのハンドシェイクについてはこちらの記事の前半にも書いているので見てみてください。