落とし穴とよくある間違い

ユーザの新しい古いに関係なく落とし穴に陥り得ます。以下で、頻繁に見かける問題の概要を説明し、それらの問題をどうやって解決するかを説明します。Freenode上の #nginx IRC チャンネルの中で、これらの問題を頻繁に見かけます。

このガイドで言いたいこと

よくある問題は、誰かが他のガイドから設定の断片をコピーして貼り付けようとしたときに起こります。表にある全てのガイドが間違っているわけでは無いですが、それらのうちの恐ろしい数がそうです。

これらのドキュメントは全ての種類のNGINXのユーザと直接仕事をするコミュニティのメンバーによって作成およびレビューされました。この特定のドキュメントがあるのは、コミュニティメンバーが見た一般的で繰り返し発生する問題のためだけです。

私の問題がリストされていない

あなたの特定の問題に関係する何かをここで見ません。Maybe we didn’t point you here because of the exact issue you’re experiencing. Don’t skim this page and assume you were sent here for no reason. ここにリストされている何かの間違いをしたので、あなたはここに送られました。

When it comes to supporting many users on many issues, community members don’t want to support broken configurations. 助けを求める前に設定を修正してください。これを読んで設定を修正してください。ざっと読むだけにしないでください。

Chmod 777

NEVER use 777. It might be one nifty number, but even in testing it’s a sign of having no clue what you’re doing. 全てのパスのパーミッションを見て、何が起きているかを考えてください。

パス上の全てのパーミッションを簡単に表示するには、以下を使うことができます:

namei -om /path/to/check

locationブロック内のroot

悪い:

server {
    server_name www.example.com;
    location / {
        root /var/www/nginx-default/;
        # [...]
      }
    location /foo {
        root /var/www/nginx-default/;
        # [...]
    }
    location /bar {
        root /some/other/place;
        # [...]
    }
}

これは動作します。locationブロック内にrootを置くと動作するでしょうし、それは完全に有効です。間違っているのは、いつ追加したlocationブロックを開始するかです。もし全てのlocationブロックにrootを追加すると、合致しないlocationブロックはrootを持たないでしょう。従って、location ブロックの前にrootディレクティブがあることが重要です。必要に応じてこのディレクティブを上書きすることができます。良い設定を見てみましょう。

良い:

server {
    server_name www.example.com;
    root /var/www/nginx-default/;
    location / {
        # [...]
    }
    location /foo {
        # [...]
    }
    location /bar {
        root /some/other/place;
        # [...]
    }
}

複数のindexディレクティブ

悪い:

http {
    index index.php index.htm index.html;
    server {
        server_name www.example.com;
        location / {
            index index.php index.htm index.html;
            # [...]
        }
    }
    server {
        server_name example.com;
        location / {
            index index.php index.htm index.html;
            # [...]
        }
        location /foo {
            index index.php;
            # [...]
        }
    }
}

なぜ必要の無い時にあまりに多くの行を繰り返すのですか?簡単に1度だけindexディレクティブを使ってください。http { } ブロック内で必要で、それ以下では継承されるでしょう。

良い:

http {
    index index.php index.htm index.html;
    server {
        server_name www.example.com;
        location / {
            # [...]
        }
    }
    server {
        server_name example.com;
        location / {
            # [...]
        }
        location /foo {
            # [...]
        }
    }
}

ifの使用

ifステートメントを使うことについて少しのページがあります。それは Ifは邪悪 と呼ばれ、本当にそれを調べるべきです。悪い if の使い方をちょっと見てみましょう。

参照

If は邪悪

Server Name (If)

悪い:

server {
    server_name example.com *.example.com;
        if ($host ~* ^www\.(.+)) {
            set $raw_domain $1;
            rewrite ^/(.*)$ $raw_domain/$1 permanent;
        }
        # [...]
    }
}

これには実際のところ3つの問題があります。1つ目はif です。私達が今心配しているのはそれです。なぜこれが悪いのか?Ifは邪悪 を読みましたか?NGINXがリクエストを受信すると - 要求されたサブドメインが何であっても、それが www.example.com あるいは単なる example.com であっても - この if ディレクティブは常に評価されます。全てのリクエストについてHostヘッダをチェックするようにNGINXに要求しているため、非常に非効率です。それはさけなければなりません。代わりに、以下の例のように2つのserverディレクティブを使います。

良い:

server {
    server_name www.example.com;
    return 301 $scheme://example.com$request_uri;
}
server {
    server_name example.com;
    # [...]
}

おまけに設定ファイルが読みやすくなりました。このやり方はNGINXの処理要求を減らします。偽のifを削除しました。また、httpあるいはhttpsであっても、使用しているURIスキーマをハードコードしない$schemeを使っています。

(もし) ファイルが存在したらを調べている

ファイルが存在することを確認するためにifを使うことは恐ろしいことです。それでは意地悪ですね。どの最近のNGINXのバージョンでも、もっと生きやすくなるtry_filesを見るべきです。

悪い:

server {
    root /var/www/example.com;
    location / {
        if (!-f $request_filename) {
            break;
        }
    }
}

良い:

server {
    root /var/www/example.com;
    location / {
        try_files $uri $uri/ /index.html;
    }
}

私達が変更したものは if を必要とせずに $uriが存在するかを調べようとすることです。try_filesの使用は順列をテストすることを意味します。$uriが存在しない場合、$uri/ を試し、それが存在しなければフォールバックlocationを試します。

この場合、$uriファイルが存在すれば、それを提供します。存在しなければ、そのディレクトリが存在するかを調べます。無ければ、存在すると確認しているindex.htmlの提供に進むでしょう。It’s loaded – but oh-so-simple!これは If を完全に排除できる別のインスタンスです。

フロント コントローラーパターン Webアプリケーション

“Front Controller Pattern” 設計は一般的で、ほとんどの一般的なPHPソフトウェア パッケージで使われています; しかし、多くの例は必要以上に複雑です。Drupal, Joomlaなどについては、以下を使うだけです:

try_files $uri $uri/ /index.php?q=$uri&$args;

注意 - パラメータ名は使用するパッケージに基づいて異なります。例えば:

  • "q"はDrupal, Joomla, WordPressで使われるパラメータです。
  • "page"はCMS Made Simpleで使われます。

幾つかのソフトウェアはクエリ ストリングを必要とさえせず、REQUEST_URIから読むことができます。例えば、WordPressは以下をサポートします:

try_files $uri $uri/ /index.php;

ディレクトリの存在を確認する必要が無い場合は、$uri/を削除してスキップすることができます。

もちろんあなたの道のりは違っていて必要に応じてもっと複雑なものが必要かも知れませんが、基本的なサイトのためにはこれらは完璧に動作するでしょう。常に簡単なものから始め、そこから構築するべきです。

制御できないリクエストをPHPに渡す

web上のPHPのための多くの例のNGINX設定は .phpで終わる全てのURLをPHPインタプリタに渡すように主張しています。これは、サードパーティによる任意のコードの実行を許可するかも知れないので、ほとんどのPHPセットアップ上で深刻なセキュリティ問題を与えることに注意してください。

問題のセクションは通常このようなものです:

location ~* \.php$ {
    fastcgi_pass backend;
    # [...]
}

ここで、.phpで終わる全てのリクエストはFastCGIバックエンドに渡されます。これの問題は、フルパスがファイルシステム上の実際のファイルに繋がらない場合、デフォルトのPHP設定は実行したいファイルを推測しようとすることです。

例えば、もし/forum/avatar/1232.jpg/file.phpへのリクエストが作られ、これが存在しないが /forum/avatar/1232.jpgが存在する場合、PHPインタプリタは代わりに /forum/avatar/1232.jpg を処理しようとするでしょう。もしこれが埋め込みのPHPコードを含む場合、コードはその結果実行されるでしょう。

これを避けるためのオプションは:

  • php.inicgi.fix_pathinfo=0 を設定します。これはPHPインタプリタに渡された文字通りのパスだけを試すようにし、ファイルが存在しなければ処理を停止させます。
  • NGINXは実行のための特定のPHPファイルだけを通すようにします:
location ~* (file_a|file_b|file_c)\.php$ {
    fastcgi_pass backend;
    # [...]
}
  • 特に、ユーザのアップロードを含む任意のディレクトリ内のPHPファイルの実行を無効にします:
location /uploaddir {
    location ~ \.php$ {return 403;}
    # [...]
}
  • 問題の条件を濾しとるためにtry_files ディレクティブを使ってください:
location ~* \.php$ {
    try_files $uri =404;
    fastcgi_pass backend;
    # [...]
}
  • 問題の条件を濾しとるために入れ子のlocationを使ってください:
location ~* \.php$ {
    location ~ \..*/.*\.php$ {return 404;}
    fastcgi_pass backend;
    # [...]
}

Scriptファイル名内のFastCGIパス

巷にあるあまりにも多くのガイドが情報を取得するために絶対パスに依存しがちです。これは一般的にPHPブロックに見受けられます。リポジトリからNGINXをインストールする場合、通常はconfig内に結局include fastcgi_paramsをぽいっと入れることになるでしょう。これは通常/etc/nginx/あたりにあるNGINXのrootディレクトリの中にあるファイルです。

良い:

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;

悪い:

fastcgi_param  SCRIPT_FILENAME    /var/www/yoursite.com/$fastcgi_script_name;

$document_rootが設定されるのはどこですか?serverブロック内にある筈のrootディレクティブによって設定されます。rootディレクティブがありませんか?最初の落とし穴を見てください。

厄介なrewrite

気を悪くしないでください。正規表現を使って混乱するのは簡単です。実際、非常に簡単なので、それらをきれいに保つ努力をする必要があります。完全に簡単に、酷いものを追加しないでください。

悪い:

rewrite ^/(.*)$ http://example.com/$1 permanent;

良い:

rewrite ^ http://example.com$request_uri? permanent;

より良い:

return 301 http://example.com$request_uri;

上を見てください。そしてここに戻ってください。そして上に行き、ここに戻ってください。OK. 最初のrewriteは完全なURIから最初のスラッシュを引いたものをキャプチャします。組み込み変数 $request_uri を使うことで、キャプチャやマッチングを多少なりとも効果的に回避できます。

欠落しているhttp://の書き換え

非常に簡単に言えば、NGINXにそうでないと伝えない限り、書き換えは相対的です。書き換えを絶対にするのは簡単です。スキーマを追加します。

悪い:

rewrite ^ example.com permanent;

良い:

rewrite ^ http://example.com permanent;

上で私達がしたことはrewriteにhttp:// を追加したことだと分かるでしょう。単純で、簡単で、効果的です。

全てをプロキシする

悪い:

server {
    server_name _;
    root /var/www/site;
    location / {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/tmp/phpcgi.socket;
    }
}

Yucky. この例では、全てをPHPに渡しています。なぜ?Apacheはこれをするかも知れませんが、あなたはする必要はありません。try_filesディレクティブは驚くべき理由のために存在します: 特定の順番でファイルを試します。NGINXは最初に静的コンテンツを提供しようとしますが、できない場合は先に進みます。このことは、PHPは全く巻き込まれないことを意味します。とても速いです。1MBのイメージをphpを通じて数千回提供している場合に対して、直接それを提供する場合は特にそうです。それをどうやってやるかを見てみましょう。

良い:

server {
    server_name _;
    root /var/www/site;
    location / {
        try_files $uri $uri/ @proxy;
    }
    location @proxy {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/tmp/phpcgi.socket;
    }
}

また良い:

server {
    server_name _;
    root /var/www/site;
    location / {
        try_files $uri $uri/ /index.php;
    }
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/tmp/phpcgi.socket;
    }
}

簡単でしょ?リクエストされたURIが存在し、NGINXで提供できるかどうかを調べます。できない場合は、提供できるディレクトリかどうかを調べます。もしそうでなければ、それをプロキシに渡します。NGINXがリクエストされたURIを直接提供できない場合のみ、プロキシのオーバーヘッドが巻き込まれます。

静的コンテンツ (images, css, javascript など) に対するリクエストの数を考慮してください。おそらく大量のオーバーヘッドが節約されます。

SCRIPT_FILENAMEのために$request_filenameを使います

$document_root$fastcgi_script_nameの代わりに$request_filenameを使います。

If you use the alias directive with $document_root$fastcgi_script_name, $document_root$fastcgi_script_name will return the wrong path.

悪い:

location /api/ {
     index  index.php index.html index.htm;
     alias /app/www/;
     location ~* "\.php$" {
         try_files      $uri =404;
         fastcgi_pass   127.0.0.1:9000;
         fastcgi_index  index.php;
         fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
     }
 }

/api/testing.phpをリクエストします:

  • $document_root$fastcgi_script_name == /app/www//api/testing.php
  • $request_filename == /app/www/testing.php

/api/をリクエストします:

  • $document_root$fastcgi_script_name == /app/www//api/index.php
  • $request_filename == /app/www/index.php

また、$request_filenameを使う場合、index ディレクティブを使ってインデックスを作成しなければなりません。fastcgi_index は動作しません。

良い:

location /api/ {
     index  index.php index.html index.htm;
     alias /app/www/;
     location ~* "\.php$" {
         try_files      $uri =404;
         fastcgi_pass   127.0.0.1:9000;
         fastcgi_param  SCRIPT_FILENAME  $request_filename;
     }
 }

設定の変更が反映されない

ブラウザのキャッシュあなたの設定は完璧かも知れませんが、一ヶ月の間そこに座って頭を記憶の壁に打ち付けているでしょう。間違っているのはあなたのブラウザのキャッシュです。何かをダウンロードした時に、ブラウザはそれを保存します。ブラウザはファイルがどうやって提供されたかも保存します。types{} ブロックを使って遊んでいる場合に、これに遭遇するでしょう。

処置:

  • Firefoxでは、Ctrl+Shift+Delete を押し、Cacheをチェックし、Clear Nowをクリックします。他のブラウザでは、お気に入りの検索エンジンに聞いてください。(必要ないと知らない場合は)各変更の後にこれをしてください。自身を多くの頭痛から助けるでしょう。
  • curlを使ってください。

VirtualBox

これが動作せず、NGINXをVirtualBox内のvirtulaマシーン上で実行している場合は、問題を起こしているのはsendfile()かも知れません。単純にsendfileディレクティブをコメントアウトするか、それを"off"に設定します。ディレクティブはほとんどnginx.confファイルの中で見つかります:

sendfile off;

失われた (消えた) HTTP ヘッダ

明示的に underscores_in_headers on; を設定しない場合、NGINXは無言でアンダースコアを持つHTTPヘッダを落とすでしょう(これはHTTP標準に基づいて完全に根拠があります)。これはダッシュおよびアンダースコアの両方が処理の間にアンダースコアにマップされるために、ヘッダーがCGIにマッピングされる時の曖昧さを避けるために行われます。

標準のドキュメントルートのlocationを使わないでください

どのようなファイルシステムにもデータをホストするために使われない幾つかのディレクトリがあります。これらは /root を含みます。それらをドキュメントルートとして使うべきではありません。

これを行うと、プライベート データを返す予定のエリア外のリクエストを開きます。

これをしないでください!!!(はい、私たちはこれを見ました)

server {
    root /;

    location / {
        try_files /web/$uri $uri @php;
    }

    location @php {
        # [...]
    }
}

/foo にリクエストがあった場合、ファイルが見つからないためにリクエストがphpに渡されます。リクエストが /etc/passwd にあるまでは、これは良いように思えるかも知れません。はい。そのサーバ上の全てのユーザのリストを渡しました。幾つかの場合で、NGINXサーバはrootとしてワーカーを実行するようにセットアップされさえします。はい、これでユーザリストとパスwードハッシュ、およびそれらのハッシュ方法が分かりました。これで私達はあなたの箱を所有します。

The File System Hierarchy defines where data should exist. 確実にそれを読むべきです。短いバージョンは、webコンテンツが/var/www/, /srv, /usr/share/wwwのいずれかに存在することです。

デフォルトのドキュメントルートを使う

Ubuntu, Debian あるいは他のオペレーティングシステムにあるNGINXパッケージは、インストールが簡単なパッケージとして、多くの場合設定法の例として‘デフォルト’の設定ファイルを提供し、多くの場合基本的なHTMLファイルを保持するdocument rootを含みます。

これらのパッケージングシステムは、デフォルトのドキュメントルート内でファイルが修正されたかあるいは存在するかどうかを調べないため、パッケージがアップグレードされた時にコードの紛失に繋がります。熟練したシステム管理者はデフォルトのドキュメントルート内のデータがアップグレードの間に触られないでいることを期待できない事を知っています。

サイトにとって致命的なファイルのためにデフォルトのドキュメントルートを使ってはいけません。デフォルトのドキュメントルートがシステムによって触られないでいるだろうという期待はできません。オペレーティングシステムのためにNGINXをアップデートおよびアップグレードする時にサイトにとって致命的なデータが失われえるかも知れない可能性はとても高いです。

アドレスの解決のためにホスト名を使う

悪い:

upstream {
    server http://someserver;
}

server {
    listen myhostname:80;
    # [...]
}

listenディレクティブの中でホスト名を使うべきではありません。これは動作するかも知れませんが、ものすごく多くの問題が付いてきます。そのような問題の一つは、ホスト名は起動時あるいはサービスの再起動の間に解決されないかも知れないということです。これはNGINXが希望するTCPソケットにバイドできないようにするかも知れず、それはNGINXを全く起動できなくするでしょう。

安全な慣習は、ホスト名の変わりに結合されるべきIPアドレスを知り、そのアドレスを使うことです。これにより、NGINXはアドレスのルックアップをしなくて済み、外部および内部のリゾルバへの依存を取り除きます。

これと同じ問題はupstream locationにも適用されます。upstreamブロック内でホスト名を使うことを常に避けることはできないかも知れませんが、それは悪い慣習であり、問題を避けるために注意深い考慮を要求するでしょう。

良い:

upstream {
    server http://10.48.41.12;
}

server {
    listen 127.0.0.16:80;
    # [...]
}

HTTPSと一緒にSSLv3を使う

SSLv3のPOODLE 脆弱性のため、SSLを有効にしたサイトではSSLv3を使わないことをお勧めします。この行を使ってSSLv3をとても簡単に無効にすることができ、代わりにTLSプロトコルだけを提供します:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

try_files $uri ディレクティブとalias

この症状は診断が困難です: 通常全てを正しく実行したように見えても、謎の404エラーが表示されます。なぜ?デバッグ レベルのエラーログを有効にすると、try_files$uriを既に設定されている aliasに追加していることを明らかにします。これはNGINXのバグによるものですが、心配しないで下さい — 回避策は簡単です!try_filesの行がtry_files $uri $uri/ =404;のようである限りは、重大な悪影響無しにtry_filesの行を削除するだけです。try_filesを使用できない例を次に示します。

悪い:

location ~^/\~(?<user>[^/]*)/(?<page>.*)$ {
    alias /home/$user/public_html/$page;
    try_files $uri $uri/ =404;
}

良い:

location ~^/\~(?<user>[^/]*)/(?<page>.*)$ {
    alias /home/$user/public_html/$page;
}

1つの注意点は、この回避策ではPATH_INFOアタックを回避するためにtry_files を使うことができないということです。これらの攻撃を緩和する別の方法については、制御されていないリクエストをPHPに渡すを見てください。

また、一部のLinuxディストリビューションに同梱されているsnippets/fastcgi-php.conf ファイルは、alias付きのlocationブロック内にtry_filesディレクティブが含まれている場合にそれを削除するために編集する必要があるかもしれないことに注意してください。

Incorrect return context

The return directive applies only inside the topmost context it’s defined in. In this example:

server {
    location /a/ {
        try_files test.html =404;
    }

    return 301 http://example.org;
}

A request to /a/test.html will return a 301. To make this work as expected wrap the second block inside a location /:

server {
    location /a/ {
        try_files test.html =404;
    }

    location / {
        return 301 http://example.org;
    }
}
inserted by FC2 system