Macにnginxでゼロから丁寧に簡易なHTTPサーバを立てる

Macにnginxでゼロから丁寧に簡易なHTTPサーバを立てる

Table of Contents

MacでWebサーバnginxを立ち上げるための入門記事シリーズ2回目です。

ゼロからnginx.confを書いてHTTPサーバを構築します

nginx.conf.defaultを一行ずつ読み解いて有無を決めながら簡易な静的ブログ向け設定ファイルを作り上げていきます。
また、nginx.conf.defaultにはないけど関連するものも取り上げます。

上から一つずつ設定しており、それぞれの段階で実行が出来る状態になってありますが、もし途中で躓いた場合は 最後にnginx.confの全容を載せてます。確認してみてください。

インストールや起動方法などが分からない方は過去記事を

前回はWebサーバの簡単な基礎とnginxのインストールから立ち上げるまでを説明しました。
▼前回の記事はこちらになります。
Macでnginxをインストールして起動する

最小設定でnginxのHTTPサーバを構築する

デフォルトで用意されたnginx.confには色々と書かれてて、やる気が萎えるのし分かりにくいです。
なので、まずはこの雛形を削りながら最少設定でHTTPサーバを構築します。

削りきった最少でHTTPサーバを構築する設定ファイルは下記になります。

nginx.conf

events {
}

http {
  server {
  }
}

events http server の3つのブロックディレクティブだけでいけました。
それ以外はデフォルト値として動いているようです。
当然何も機能を設定していないので、ほぼ何もできないHTTPサーバです。
ここまで削れればとっかかりやすいですね。

なおこれらブロックディレクティブのことはコンテキストと呼ぶようです。 これらブロックディレクティブに1つも囲まれていない部分(インデントなし部分)をmainコンテキストと呼ぶようです。

次は、デフォルト設定ファイル(nginx.conf.default)から一つずつ確認しながら先程の最少設定ファイルを埋めていきます。

mainコンテキスト

前述したどのブロックディレクティブにも囲まれていない部分から見ていきます。
nginx.conf.defaultファイルの冒頭部分です。

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

userディレクティブ

workerプロセスを実行するユーザーとグループを設定します。

構文: user user [group];
デフォルト: nobody nobody

デフォルトでもコメントアウトされてるように必須項目ではありません。

worker_processesディレクティブ

workerのプロセス数を設定します。

構文: worker_processes *number*|auto;
デフォルト: worker_processes 1;

最適値はCPUコア数、ストレージ数、負荷パターンなど多岐に渡ります。
悩ましい場合はCPUコア数を推奨します。
autoはそれを自動検出します。

今回はCPUコア数と同じ値にします。

worker_processes 4;

手元のMacのコア数はシステムレポートのハードウェア概要で確認できます。   システムレポートは Spotlight検索 > このMacについて.app > システムレポート でたどり着けます。

error_logディレクティブ

エラーログ出力先の設定します。

構文: error_log file [level];
デフォルト: error_log logs/error.log error;

出力パスとログレベルを設定します。
ログレベルはdebug, info, notice, warn, error, crit, alert, emergの8個です。
例えばログレベルをerrorにするとerror, crit, alert, emergのレベルのログが出力されます。

エラーログは欲しいのでログレベルerrorでログを取ろうと思います。

error_log /usr/local/var/log/nginx/error.log error;

pidディレクティブ

メインプロセスのプロセスIDの保存先を設定します。

構文: pid file;
デフォルト: pid logs/nginx.pid;

公式のデフォルト値は↑ですが、Macだと /usr/local/var/run/nginx.pid にあります。
デフォルト値でも問題ない場所にあるので特に指定はしないでいきます。

eventsコンテキスト

events {
    worker_connections  10;
}

接続処理に影響するブロックディレクティブです。

worker_connectionsディレクティブ

ワーカープロセスで開くことができる同時接続の最大数を設定します。

構文: worker_connections number;
デフォルト: worker_connections 512;

この接続とは、クライアントだけでなくすべての接続(プロキシサーバとの接続など)も含まれます。
また、同時接続の実際の数が、worker_rlimit_nofile に依存します。
せっかくなので worker_rlimit_nofile についても取り上げます。

worker_rlimit_nofileディレクティブ

ワーカープロセスが開けるファイルの最大数(RLIMIT_NOFILE)の制限を設定します。
メインプロセスを再起動せずに制限を増やすために使います。

通常1プロセスはRLIMIT_NOFILEの数以上のファイルは開けませんが、
この値を設定することでその上限を超えたファイル数を処理できるようになります。

なおworker_rlimit_nofileディレクティブはmainコンテキストで使えるディレクティブです。**

RLIMIT_NOFILEとは?

1プロセスが開けるファイルの上限です。
確認するにはulimit -n で取れます。Macだとデフォルトは256しかないようです。

同時接続に関するパラメータを整理する

  • worker_rlimit_nofileはworkerプロセスが扱えるファイル上限数(RLIMIT_NOFILE)を設定する
  • RLIMIT_NOFILEは1プロセスが開けるファイル上限でulimit -nで確認できる。デフォルトでは256しかないようです。
  • OS全体で扱えるファイル数はlaunchctl limit maxfilesで確認できる。デフォルトでは256しかないようです。

launchctl limit maxfilesを変更は可能ですが、今回は目的ではないのでデフォルトのままにします。 ちなみに変えたい場合は「launchctl limit maxfiles」で検索するとたくさん出てくるのでそちらを参考にしてください。

worker_rlimit_nofileの算出式

OS全体ファイル上限、プロセスファイル上限、workerプロセス数の3つからworkerプロセスの同時接続数を算出できそうです。

workerプロセス数(worker_processes) * プロセスファイル上限(worker_rlimit_nofile) < OS全体ファイル上限(maxfiles)

この式からプロセスファイル上限(worker_rlimit_nofile)を求めます。

プロセスファイル上限(worker_rlimit_nofile) = OS全体ファイル上限(maxfiles) / workerプロセス数(worker_processes)

この式に実際の値を当てはめます。

256 / 4 = 64

ただしMacでnginxを使う目的は学習や開発版で、nginx以外にも様々なアプリでファイルを開いていると思うので、その分を考慮します。 今回は半分にします。

64 * 0.5 = 32

worker_rlimit_nofile 32;

ということですね。

次にworker_connectionsですが、1接続1ファイルオープンではなく少なくとも2つは消耗するようです。参考

上記記事からして worker_connectionsworker_rlimit_nofile の 1/3 にしておけば余裕だと思います。

32 の 1/3 は10.6 きりよく10にします。

events {
  worker_connections 10;
}

同時接続は4*10で大体40といったところでしょうか。

実際のサーバ(Linux)では上限を結構あげたりすると思いますが、上限が上がったからといってその上限まで同時接続を捌けるとは限りません。あくまでもファイルオープンの限界値が増えただけであって、それ以外のCPU負荷やネットワーク負荷、DB負荷、メモリ負荷の問題は以前変わらずです。

httpコンテキスト

http {
  include       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  logs/access.log  main;

  sendfile        on;
  #tcp_nopush     on;

  #keepalive_timeout  0;
  keepalive_timeout  65;

  #gzip  on;
  server {

  }
}

HTTPサーバに関するブロックディレクティブです。

includeディレクティブ

構文: include file|mask;
デフォルト: なし

別ファイルや一致するファイル群を設定として読み込みます。
取り込むファイルは、構文やディレクティブなども見てくれます。

デフォルトnginx.confの include mime.types;minem.types という別ファイルをロードしているようです。

このファイルは/usr/local/etc/nginx/ディレクトリにあります。

$ ls -l | grep mime.types
-rw-r--r--  1 mothule  admin  5170  7  1  2018 mime.types
-rw-r--r--  1 mothule  admin  5231  2 20 03:07 mime.types.default

中身は types コンテキスト1つにたくさんのファイル・タイプの列挙がされてます。

nginx.conf.defaultの行末付近にもincludeは使われています。

include servers/*;

これは、サーバー毎の設定ファイルを外部ファイル化し、それをロードする場合に使います。

▼詳しくは同シリーズの記事をご確認ください。
ドメイン別設定ファイル置き場を用意する

typesコンテキスト

レスポンスのMIMEタイプと拡張子名のマッパーです。
追加時は小文字で必須です。

デフォルトでほとんどのMIMEタイプをカバーできているのでこれはロードしたほうが良いです。

http {
  include mime.types;
}

default_typesディレクティブ

レスポンスのデフォルトMIMEタイプを設定します。

構文: default_types mime-type;
デフォルト: default_types text/plain;

typesコンテキストで設定したMIMEタイプに当てはまらない場合は text/plain になり、レスポンスによってはやばいものもあると思うので、デフォルトenginx.conf同様application/octet-stream にしておきます。

http {
  default_type  application/octet-stream;
}

access_logディレクティブ

アクセスログ出力先の設定します。

構文: access_log file [format [buffer=size][gzip[=level]][flush=time][if=condition]]
デフォルト: access_log logs/access.log combined;

構文内の[]について説明すると、[]内の文字列や値は任意を表します。
例えば[buffer=size] は無指定でも問題ありません。
[gzip[=level]]だと無指定, gzip, gzip=level の3つとなります。

引数 意味
file 出力先パス
format 1行ログの形式. 複雑な場合は後述するlog_formatの名前を指定します。
buffer=size ファイルに書き込む前のバッファサイズ
gzip[=level] 圧縮して書き込む.levelは1~9で最大は9,デフォルトは1,ログはzcatで圧縮解除や読み取り可能です
flush=time バッファをファイルに書き込むインターバル. この時間経過するとバッファをファイルに書き込みます
if=condition 条件付きログ. 条件が0または空文字列なら書き込まない

if=condition例

次の例は$statusつまりHTTPコードが200番台,300番台ならログに書き込まない。

map $status $loggable {
    ~^[23]  0;
    default 1;
}

access_log /path/to/access.log combined if=$loggable;

log_formatディレクティブ

ログのフォーマットを設定します。

構文: log_fomat name [escape=deafult|json|none] string ...;
デフォルト: log_format combined "...";

構文内の|について説明すると、|で区切られた値のみを設定できることを表します。
例えばescape=default escape=json escape=none の3パターンのみとなります。

string には通常変数とログ書き込み時のみ存在する変数を含めたフォーマットを設定できます。

変数名 説明
$bytes_sent クライアントへ送信したバイト数
$connection 接続シリアルNo
$connection_requests 接続を介した現在のリクエスト数
$msec ログ書き込み時間(ミリ秒)
$pipe リクエストがパイプライン化されたらpが書き込まれ、それ以外は.が書き込まれます。
$request_length リクエスト行、ヘッダー、本文含むリクエストの長さ
$request_time リクエスト処理時間(ミリ秒).リクエストを受けてから返すまでの時間
$status HTTPステータス
$time_iso8601 ISO8601フォーマットに沿ったローカル時間
$time_local 一般的なログフォーマットに沿ったローカル時間

ログのデフォルトは $combined が指定されており、これは次の形式になっています。

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

実際にnginxサーバにアクセスしてaccess.logを覗いてみると下記形式でログが書き込まれます。

127.0.0.1 - - [21/Feb/2020:18:43:21 +0900] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"

デフォルト設定はテストすると落ちる

前述したnginx.confだとテストが落ちます。
理由はログのパスがnginxの場所からの相対パスとなっており、そこにフォルダが存在しないためです。
また場所もhomebrewの管理下になり、一般的なログ場所とは異なります。
ログのパスは下記のように絶対パスで指定します。

http {
  access_log  /usr/local/var/log/access.log  main;
}

sendfileディレクティブ

sendfile() の仕様有無を設定します。

構文: sendfile on|off;
デフォルト: sendfile off;

sendfileとは?

ファイルディスクリプター間のデータ転送関数。read/write組み合わせより効率が良い。
ただ、動作不安定や静的ファイルが更新不備など不安定な評判もある。

とりあえず使ってみて動作不安定な気があればここを無効化します。

http {
  sendfile on;
}

tcp_nopushディレクティブ

TCP_NOPUSHソケットオプション または TCP_CORKオプションの使用有無を設定します。
FreeBSDではTCP_NOPUSHでLinuxではTCP_CORKを使います。
このディレクティブはsendfileが有効時のみ働きます。

構文: tcp_nopush on|off;
デフォルト: tcp_nopush off;

これが有効だと、レスポンスヘッダーとファイルをまとめて送信するため、パケット数を抑え効率よくなります。

今回はsendfileを有効化しており、tcp_nopushにより高速化が見込めるので、ここも有効化します。

http {
  tcp_nopush on;
}

keepalive_timeoutディレクティブ

keep-aliveによる接続時間を設定します。

構文: keepalive_timeout timeout [header_timeout];
デフォルト: keepalive_timeout 75s;

[header_timeout]は任意オプションです。
これはMozillaおよびKonquerorによって認識されます。 MSIEは約60秒でキープアライブ接続を自動的に閉じます。

keep-aliveとは?

簡易説明すると、HTTPはリクエスト毎にサーバ/クライアント間で接続を確立し、レスポンスを返します。 レスポンスを返したら接続を切るため、リクエストが大量にあれば、たとえ同じクライアントだとしてもそれだけ接続/切断を繰り返すことになります。
これでは無駄なので、レスポンス送信後すぐに切断せずにしばらく待つことで、次リクエストの接続処理スキップすることで効率よくなります。

詳細はこちらを参考にしてください。

keep-aliveはタイムアウト値が適切であれば通信処理を効率良く出来るため、入れておきます。

http {
  keepalive_timeout: 60s;
}

gzipディレクティブ

gzipコマンドで圧縮の使用有無を設定します。

構文: gzip on|off;
デフォルト: gzip off;

圧縮することで通信量を減らせます。しかしトレードオフとして圧縮に必要なCPUリソースを消費します。また解凍(=展開)が必要になり解凍はクライアント側が行います。つまりクライアント側にも解凍に必要なCPUリソースを消費します。

今回はonにしてみます。
ただし、gzipはon/offの二択ではなく、onの場合に細かいパラメータ設定が可能となっています。
せっかくなのでgzipのパラメータ設定用ディレクティブもまとめます。

http {
  gzip on;
}

gzip_buffersディレクティブ

レスポンス圧縮に使うバッファサイズと数を設定します。

構文: gzip_buffers number size;
デフォルト: gzip_buffers 32 4k|16 8k;

デフォルトは1メモリページと同等になってます。そのためプラットフォームに応じて 32個 4kBytes16個 8kBytes のいずれかです。

ここはバッファサイズがメモリページを横断すると、メモリ操作によるコストがかさむのでデフォルトのままでよいと思います。

gzip_comp_levelディレクティブ

圧縮レベル(1~9)を設定します。小さいほど圧縮率は下がります。

構文: gzip_comp_level level;
デフォルト: gzip_comp_level 1;

圧縮は線形比例ではありません。なので9が1より9倍圧縮率が高くなることはありません。
圧縮レベル別のパフォーマンスはこちらの記事が参考になります。

ここは1か2がコスパよいのでデフォルトと同じ1のままにします。

gzip_disableディレクティブ

いずれかの正規表現に一致するUser-Agentにはgzip圧縮を無効に設定します。

構文: gzip_disable regex ...;

今回は特に除外はしないので、未設定にします。

gzip_http_versionディレクティブ

レスポンス圧縮に必要な最小HTTPバージョンを設定します。

構文: gzip_http_version 1.0 | 1.1; デフォルト: gzip_http_version 1.1;

最近は1.1は当たり前なので、これはデフォルトのままにします。

gzip_min_lengthディレクティブ

レスポンスを圧縮する最小サイズを設定します。この最小サイズはContent-Lengthが使われます。

構文: gzip_min_length length; デフォルト: gzip_min_length 20;

これは無圧縮のほうがコスパ良いサイズについて設定します。

ここの値は通信帯域やパケットサイズに関係します。デフォルトは小さすぎるので1k(1024)にします。

http {
  gzip_min_length 1024;
}

gzip_proxiedディレクティブ

リクエスト/レスポンスに応じてプロキシされたリクエストのレスポンスも圧縮するかを設定します。
つまり、プロキシから来たリクエストの結果(レスポンス)を圧縮するかどうかを設定します。

パラメータは複数指定が可能です。

構文: gzip_proxied off|expired|no-cache|no-store|private|no_last_modified|no_etag|auth|any ...;
デフォルト: gzip_proxied off;

nginxはリバースプロキシ機能を持っており、そこで圧縮したレスポンスを内部ネットワークに応答することもできます。
その場合は既に圧縮したレスポンスに関しては何もする必要がありません。

プロキシ経由かどうかはViaヘッダーフィールドの存在で確認できます。

パラメータが多いので表にします。

パラメータ 説明
off プロキシされたリクエストのレスポンスの圧縮を無効
expired 期限切れExpiresがあれば有効
no-cache Cache-Controlno-cacheがあれば有効
no-store Cache-Controlno-storeがあれば有効
private Cache-Controlprivateがあれば有効
no_last_modified Last-Modifiedなければ有効
no_etag ETagなければ有効
auth Authorizationがあれば有効
any プロキシされたリクエストのレスポンスの圧縮を有効

複数指定は、スペース区切りで設定します。

今回はリバースプロキシは介さないのでデフォルト(off)のままにします。

gzip_typesディレクティブ

圧縮対象となるMIMEタイプを設定します。
なおtext/htmlは常に圧縮されます。また *を指定すると全てのMIMEに一致します。

構文: gzip_types mime-type ...; デフォルト: gzip_types text/html;

今回は、CSSやJavascript、JSONも圧縮対象にします。

http {
  gzip_types text/css text/javascript application/json;
}

gzip_varyディレクティブ

圧縮ディレクティブ(gzip, gzip_static, gunzip)が有効であれば、 Vary: Accept-Encodingヘッダの挿入の有無を設定します。

構文: gzip_vary on | off; デフォルト: gzip_vary off;

これは圧縮可能と不可能の2クライアントから同一リソースにアクセスがあった場合に起きる問題の回避として使います。 詳しくはこちらの記事が参考になります。

serverコンテキスト

バーチャルサーバの設定をします。

少し長いですがnginx.cnf.defaultからserverを抜粋したものが下記です。

server {
  listen       8080;
  server_name  localhost;

  #charset koi8-r;

  #access_log  logs/host.access.log  main;

  location / {
      root   html;
      index  index.html index.htm;
  }

  #error_page  404              /404.html;

  # redirect server error pages to the static page /50x.html
  #
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
      root   html;
  }

  # proxy the PHP scripts to Apache listening on 127.0.0.1:80
  #
  #location ~ \.php$ {
  #    proxy_pass   http://127.0.0.1;
  #}

  # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
  #
  #location ~ \.php$ {
  #    root           html;
  #    fastcgi_pass   127.0.0.1:9000;
  #    fastcgi_index  index.php;
  #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
  #    include        fastcgi_params;
  #}

  # deny access to .htaccess files, if Apache's document root
  # concurs with nginx's one
  #
  #location ~ /\.ht {
  #    deny  all;
  #}
}

listenディレクティブ

サーバがリクエストを受け付けるか設定します。

構文:

  1. listen address
    listen address[:port] [default_server] [ssl] [http2 | spdy] [proxy_protocol] [setfib=number]
     [fastopen=number] [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter]
     [deferred] [bind] [ipv6only=on|off] [reuseport] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
    
  2. listen port
    listen port [default_server] [ssl] [http2 | spdy] [proxy_protocol] [setfib=number] [fastopen=number]
     [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind]
     [ipv6only=on|off] [reuseport] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
    
  3. listen unix:path
    listen unix:path [default_server] [ssl] [http2 | spdy] [proxy_protocol] [backlog=number]
    [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind]
    [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
    

デフォルト: listen *:80 | *:8000;

パターンは大きく3つに分かれており、オプションも膨大です。

パラメータ 説明
address IPまたはホストネーム
:port ポート番号
default_server どのserverにもマッチしない場合はこのオプションがついたサーバに振り向けます
ssl SSL接続必須指定
http2 HTTP/2接続 spdyと同時指定はできません
spdy SPDY接続 http2と同時指定はできません
proxy_protocol PROXY protocol必須指定
setfib=number FIB(SO_SETFIB)を設定,FreeBSD限定
fastopen=number TCP Fast Openを有効にし、3ウェイハンドシェイク未完了の接続キューの最大長
backlog=number 保留中接続キューの最大長.接続要求が最大長超えるとECONNREFUSEDエラーが起きる.FreeBSD,DragonFly BSD, macOSは-1、その他は511.詳細はこちら
rcvbuf=size 受信バッファサイズ(SO_RCVBUF)
sndbuf=size 送信バッファサイズ(SO_SNDBUF)
accept_filter=filter 受信フィルタ(SO_ACCEPTFILTER)の名前。FreeBSDとNetBSD5.0+のみ
deferred Linuxで遅延accept()TCP_DEFER_ACCEPTを使用有無.詳細はこちら
bind 指定されたaddress:portペアに個別でbind()する。hoge:80,fuga:80,:80の3つlistenがあったら,:80にだけbind()する。注意点は接続受付先アドレスの決定にgetsockname()がが使われる。setfib, backlog,rcvbuf, sndbuf, accept_fileter, deferred, ipv6only,so_keepaliveが使われたら、渡されたaddress:portは個々に常にbind()する
ipv6only= IPV6_V6ONLYを経由してワイルドカードアドレス[::]でlistenしてるIPv6ソケットがIPv6接続だけかIPv4接続の両方を受け付けるかを決める。パラメータはonoffでデフォルトはon
reuseport カーネルが接続をワーカープロセス間に分散して、各ワーカープロセスにSO_REUSEPORTを使って個々のlistenソケットを生成指示する。Linux3.9+とDragonFly BSDのみ。
so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]] TCP keepaliveの挙動設定する。パラメタ省略するとOS値が設定される。onだとSO_KEEPALIVEがソケットに対しonになる。 offだとSO_KEEPALIVEに対しoffとなる。Linux2.4+, NetBSD 5+ FreeBSD 9.0-Stableでは、TCP_KEEPIDLE,TCP_KEEPINTVL,TCP_KEEPCNTを使ってkeepaliveをサポートする。省略した場合はOSのデフォルト値が使われる。

今回はその他のパラメータは今回は使わず、3030ポートを開こうと思います。

server {
  listen 3030;
}

もし既にポートが使われている場合 次のエラーが出力されます。

nginx: [emerg] bind() to 127.0.0.1:8080 failed (48: Address already in use)
nginx: [emerg] bind() to [::1]:8080 failed (48: Address already in use)

server_nameディレクティブ

バーチャルサーバの名前を設定します。

構文: server_name name ...;
デフォルト: server_name "";

ワイルドカード

サーバ名には最初と最後の名前にワイルドカード(*)が使えます。

server_name example.com *.example.com api.example.*;

ちなみにexample.com*.example.com.example.comに結合できます。

正規表現

名前の前にチルダ(~)をつけて

servername www.exmaple.com ~^www\d+\.example\.com$;

のように正規表現を使えます。

またキャプチャも使えます。

server {
  server_name ~^(www\.)?(.+)$;
  location / {
    root /sites/$2;
  }
}

名前付きキャプチャは変数として使えます。

server {
  server_name ~^(www\.)?(?<domain>.+)$;

  location / {
    root /sites/$domain;
  }
}

ちなみに $hostnameにはマシンのホスト名が入ります。

今回はローカルホストにサーバを立てるのでlocalhostを使います。

server {
  server_name localhost;
}

charsetディレクティブ

指定した文字セットをContent-Typeレスポンスヘッダフィールドに追加します。

構文: charset charset | off; デフォルト: charset off;

注意点:source_charset と違うと変換が発生する。

今回は無難にutf-8で行きます。

server {
  charset utf-8;
}

access_logディレクティブ

このディレクティブは前述しているため基本説明は省略します。

追記としては、このサーバに対するアクセスのみログが記録されます。

今回はこのサーバだけのアクセスログを残しておきます。

server {
  access_log /usr/local/var/log/nginx/localhost.access.log  main;
}

formatは既に定義済みのmainを使います。

open() 2: No such file or directory が出る場合

nginx: [emerg] open() "/usr/local/var/log/enginx/localhost.access.log" failed (2: No such file or directory)

よく見ると分かりますが、パスが間違ってます。

error_pageディレクティブ

エラーページとエラーコードのマッピングを設定します。
uriには変数を使えます。

構文: error_page code ... [=[response]] uri;

例えば404と500番台でエラーページを用意してるなら

GETに変更してリダイレクト

server {
  error_page 404 /404.html;
  error_page 500 502 503 504 /50x.html;
}

と設定すると、ステータスコードと一致するページにHEAD以外のリクエストを全てGETに変更して、内部的にリダイレクトします。

応答コードの変更

例えば404ではなく200にしてEmtpy pageを出したいなら

server {
  error_page 404 = 200 /empty.html;
}

のように、応答コードを変更できます。

応答ページを別サーバに渡す

例えばPHPなどで404を返すページがある場合

server {
  error_page 404 = 404php;
}

のように設定できます。

別locationコンテキストにエラー処理を渡す

内部リダイレクトでURIとメソッド変更させない場合は

server {
  location / {
    error_page 404 = @fallback;
  }
  location @fallback {
    proxy_pass http://backend;
  }
}

のように別locationに移すことができます。

今回は404は専用ページを用意する設定にします。

server {
  error_page 404 /404.html;
}

locationコンテキスト

リクエストURIに応じて構成を設定する。

構文:

  • location [ = | ~ | ~* | ^~ ] uri { ... }
  • location @name {... }

  • “%XX”形式エンコードされてテキストはデコード
  • 相対パス...の参照は解決
  • 2つ以上続くスラッシュ/は1つに差し替え

その後、正規化されたURIに対してマッチングが実施される。

プリフィック文字正規表現 の2つで定義できる

正規表現のオプション

  • 先頭文字が~*修飾子なら大文字小文字を区別しないマッチング
  • 先頭文字が~修飾子なら大文字小文字を区別するマッチング

locationの検索フロー

指定されたリクエストに合致するlocationを見つけるために、

  1. nginxはまずプリフィックス文字列を使って定義されるlcoationを調べます
  2. それらの中で、一番長くマッチするプリフィックスlocationが選択され記憶されます
    • 記憶したプリフィックスlocationに^~修飾子があれば以降の正規表現チェックはしません。
    • 完全一致=修飾子で一致したら検索は終了します
  3. 設定ファイルの中に現れる順番で正規表現を調べます
    1. 正規表現の検索は一番最初にマッチした時点で終了します
    2. 正規表現にマッチしなかったら、前に記憶されたプリフィックスlocationが使われます

完全一致は指定URIのアクセスが高頻度の場合に適用すると高速化します。

検索フロー例

location = / {
  # A
}
location / {
  # B
}
location /documents/ {
  # C
}
location ^= /images/ {
  # D
}
location ~* \.(gif|jpeg|jpg|png)$ {
  # E
}

というlocationがあった場合、

  • / はAにマッチ
  • /index.html はBにマッチ
  • /documents/document.html はCにマッチ
  • /images/1.gif はDにマッチ
  • /documents/1.jpg はEにマッチ

というマッチング結果となります。

名前付きlocation

@プレフィックスをつけることで名前付きとして定義できます。
リクエストのリダイレクトに使います。

パーマネントリダイレクト

locationが末尾が/で終わる定義で、設定が proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, grpc_passの1つで処理された場合は、特別処理がされます。 同じURIだが最後の/がないリクエストの応答は、/がリクエストのURIに追加されて301として返される。

rootディレクティブ

ルートディレクトリを設定する。

構文: root path; デフォルト: root html;

例えば次の設定の場合

location /i/ {
  root /data/wwww;
}

/i/top.pngリクエストは、/data/www/i/top.pngが応答します。

path には $document_root$realpath_rootを除く変数が使えます。

ルートディレクトリの値にURIを追加するだけでファイルパスが構築されます。 URIを変更する場合は、aliasディレクティブを使います。

今回はトップページ、記事ページ、画像の3locationを用意します。

location = / {
  root /usr/local/var/www;
}
location /articles/ {
  root /usr/local/var/www;
}
location ~* \.(gif|jpg|jpeg|png)$ {
  root /usr/local/var/www/images;
}

aliasディレクティブ

指定locationの置き換えを設定します。

構文: alias path;

例えば次の設定の場合

location /i/ {
  alias /data/www/images/;
}

/i/top.pngリクエストは、/data/www/images/top.pngが応答します。

もしlocationがディレクティブの最後の場所に一致する場合

location /images/ {
  alias /data/www/images/;
}

この場合は、rootディレクティブを使ったほうがいいです。

location /images/ {
  root /data/www;
}

indexディレクティブ

indexとして使われるファイル定義を設定します。

fileには変数を使えます。 ファイルは設定順でチェックします。

構文: index file ...; デフォルト: index index.html;

注意点:

indexを使うと内部リダイレクトが発生し、想定外のlocationで処理されることがあります。

location = / {
  index index.html;
}
location / {
  # B
}

この場合、/ リクエストは/index.htmlとして、2番目のlocation(B)で処理されます。

今回はトップページのみ指定します。

location = / {
  root /usr/local/var/www;
  index index.html;
}

denyディレクティブ

指定ネットワークやアドレスのアクエス拒否を設定します。

構文: deny address | CIDR | unix: | all;

unix:の場合は全てのUNIXドメインソケットのアクセス拒否になります。

特定のファイルに対してアクセス拒否したい場合などに使います。
今回は特に拒否したいファイルなどはないため設定しません。

出来上がった設定ファイル(nginx.conf)

この記事を通してnginx.conf.defaultから必要なディレクティブのみを設定したものになります。

worker_processes  4;

error_log /usr/local/var/log/nginx/error.log error;

worker_rlimit_nofile 32;

events {
  worker_connections 1024;
}

http {
  include 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 /usr/local/var/log/nginx/access.log main;

  sendfile on;
  tcp_nopush on;

  keepalive_timeout 60;

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


  server {
    listen 8080;
    server_name localhost;

    charset utf-8;

    access_log /usr/local/var/log/nginx/localhost.access.log  main;

    error_page 404 /404.html;

    location = / {
      root /usr/local/var/www;
    }

    location /articles/ {
      root /usr/local/var/www;
    }

    location ~* \.(gif|jpg|jpeg|png)$ {
      root /usr/local/var/www/images;
    }
  }
}

ファイル配置図

/usr/local/var/www 配下は次のように配置してます。 $ tree . ├── 404.html ├── articles │   └── 1.html ├── images │   └── nginx.png └── index.html

  • localhost:8080にアクセスすると index.htmlが返ってきます。
  • localhost:8080/articles/1.html にアクセスすると 1.html が返ってきます。
  • localhost:8080/nginx.pngにアクセスすると nginx.pngが返ってきます。
  • localhost:8080/hogeにアクセスすると 404.htmlが返ってきます。

Macにnginxでゼロから丁寧に簡易なHTTPサーバを立てる

今回は、とても単純なブログサーバをnginxで立ち上げました。前回と違い結構なボリュームでした。
一つずつディレクティブを確認したためこのボリュームになりました。
しかしこの部分がnginxでもっとも基礎の中でも重要な基礎となります。
ここを理解することで次の記事「Mac1台にnginxでWebサーバとPumaでアプリサーバを立てる 」へと繋げられます。

このエントリーをはてなブックマークに追加