お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。

WSL上のUbuntuで動作するnginxでHTTPSに対応したサーバーを作る方法。実機でも同様の手順でいける

確認環境

Env Ver
nginx nginx/1.18.0 (Ubuntu)
mkcert v1.4.4
Ubuntu 20.04.6 LTS
Windows 11 22621.3880

手順

  1. Windows側にmkcertを入れる
  2. mkcertで証明書を作る
    • mkcert sandbox.test
  3. 以下のpemファイルが生成される
    • sandbox.test.pem
    • sandbox.test-key.pem
  4. 生成されたpemファイルを/etc/nginx/conf.d/ssl/に移動する
  5. /etc/nginx/conf.d/sandbox.test.confを作成し、以下のような記述をする

    server {
        listen       443 ssl;
        client_max_body_size 100m;
        server_name  sandbox.test;
    
        ssl_certificate     conf.d/ssl/sandbox.test+1.pem;
        ssl_certificate_key conf.d/ssl/sandbox.test+1-key.pem;
    
        access_log   /var/log/nginx/sandbox.access.log;
        error_log    /var/log/nginx/sandbox.error.log;
    
        # ファイルホスト用
        #  location / {
        #    root /usr/share/nginx/html/sandbox;
        #    index index.html;
        #    try_files $uri /index.html =404;
        #  }
    
        # APサーバーへのリバプロ用
        location ~ ^/.*$ {
            rewrite ^/.*$ / break;
            proxy_set_header X-Request-Path $request_uri;
            proxy_set_header X-Host $host;
            proxy_pass  http://127.0.0.1:9999;
        }
    
        # fastcgi用
        location ~ \.php$ {
            root  /usr/share/nginx/html/sandbox;
            fastcgi_pass unix:/run/php/php8.0-fpm.sock;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        }
    }
    
  6. nginxを再起動する
    • sudo service nginx restart

他の端末に証明書を撒く方法

mkcertのRoot CAをエクスポートして他の端末に突っ込めば、他の端末でもpemが流用できる

投稿日:
言語::PHPミドルウェア::RDBMS::SQLite

"hoge", "piyo", "fuga"のように綺麗なCSVであるという前提。CSVファイルが巨大なので行読み込みする。

<?php

$db = new PDO('sqlite:./hoge.db');

$db->beginTransaction();
$fp = fopen("hoge.csv", "r");
$idx = 0;

if($fp){
  while ($line = fgets($fp)) {
    $row = createRow($line);
    $db->exec(
      'INSERT INTO hogehoge (`foo`, `bar`, `baz`)'
      . ' VALUES '
      .'(' . '"' . $row['foo'] .'", ' . '"' . $row['bar'] .'", ' . '"' . $row['baz'] .'"' . ')'
    );
    $idx++;
    echo $idx . "\n";
  }
}

fclose($fp);
$db->commit();

function createRow($text) {
  $r1 = preg_replace('/"/', '', $text);
  $row = explode(',', $r1);

  return [
    'foo' => $row[0],
    'bar' => $row[1],
    'baz' => $row[2],
  ];
}

トランザクションを張りっぱなしだが、exec毎にトランザクションを張りなおすと劇的に遅くなるのでやめたほうがいい(恐らく毎回同期処理でWriteが走っているのだと思う)

HTTPヘッダを持たないHTTPリクエストはあり得るのか?というのを検証しているときに気づいた話。

RFC 7230ではHostヘッダを持たないHTTPリクエストは禁止されており、これを受けたサーバーは400応答を返すことを必須としている。

RFC 7230:Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and RoutingHostより

A client MUST send a Host header field in an HTTP/1.1 request even if
the request-target is in the absolute-form, since this allows the
Host information to be forwarded through ancient HTTP/1.0 proxies
that might not have implemented Host.
A server MUST respond with a 400 (Bad Request) status code to any
HTTP/1.1 request message that lacks a Host header field and to any
request message that contains more than one Host header field or a
Host header field with an invalid field-value.

なお、Node.jsのHTTPサーバー機能ではHostヘッダーのない要求を受け入れることができる。

http.createServer([options][, requestListener])を見ると、以下のようにrequireHostHeader: falseを渡すことで実現可能だ。規定値はtrueであるため、基本的にはHostヘッダーなしの要求は400応答が返される。

import http from 'node:http';

http
  .createServer({requireHostHeader: false}, (req, res) => {
    console.log(req.headers);
    res.statusCode = 200;
    res.end();
  })
  .listen(3000);

nginxにおいてもHostヘッダーなしの要求は以下の応答が返されたため同様と思われる。

<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.26.0</center>
</body>
</html>

但し、nginxにおいてHostなしの要求を許容する方法は見つからなかった。Server namesによるとserver_name "";とすることで出来そうに見えたが、これは機能させることができず、400応答が返された。

adiaryをnginxの上で動作させつつ開発する方法をメモしておく。

前提条件

動作URLとしてはhttps://hoge.example.com/adiaryのようなサブドメイン付きのサブディレクトリとする。

adiaryはネイティブでfcgiをサポートしているらしいが、そこは無視して一般的な環境で起動するのがゴール。

SSLで動かす設定を書いているため、nginxでローカル開発環境向けのhttps環境を作るも参考にすること。

特別な設定は不要で、サブドメインモードにしなくとも動く。

確認環境

Env Ver
OS Ubuntu 22.04.3 LTS
nginx 1.24.0
fcgiwrap 1.1.0-12 amd64
adiary 3.50n

手順

nginxはインストールされており、既に稼働している状態として話を進める

# adiaryを格納するディレクトリを作成する
sudo mkdir -p /usr/share/nginx/html/sites
# 既にどっかに展開済みのadiaryをディレクトリに突っ込む
sudo cp -R adiary /usr/share/nginx/html/sites
cd /usr/share/nginx/html/sites
# 所有権とパーミッションの調整
sudo chwon -R $USER:$USER adiary/
cd adiary/
sudo chmod -R 777 __cache/
sudo chmod -R 777 data/
sudo chmod -R 777 pub/
# Image::Magickを入れる
sudo apt install -y libimage-magick-perl
# nginxでCGIを動かすために、fcgiwrapをインストールする
sudo apt install fcgiwrap
# /etc/nginx/conf.dに設定ファイルを作成
cat <<'EOF' | sudo tee -a /etc/nginx/conf.d/hoge.example.com.conf
server {
    listen       443 ssl;
    server_name  hoge.example.com;

    ssl_certificate     ssl/hoge.example.com.pem;
    ssl_certificate_key ssl/hoge.example.com-key.pem;

    access_log   /var/log/nginx/hoge.example.com.access.log;
    error_log    /var/log/nginx/hoge.example.com.error.log;

    # マルチテナント用にこう書いている
    root /usr/share/nginx/html/sites;
    # /usr/share/nginx/html/sites/adiaryにadiary一式が入っている想定

    location / {
        index index.html;
    }

    location /adiary {
        index adiary.cgi;
    }

    # fastcgi用
    location ~ \.cgi$ {
        gzip off;
        fastcgi_pass  unix:/var/run/fcgiwrap.socket;
        include /etc/nginx/fastcgi_params;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    }
}
EOF
# nginxの設定を再読み込みする
sudo service nginx restart

トラブルシューティング

403エラーで起動せず、エラーログにパスがないと出る

以下のコマンドを叩き、パスが整合するまで設定を調整する

sudo strace -f -e trace=file -p $(pidof fcgiwrap)

書いてなさ過ぎて忘れるので備忘録として書き出しておく。必要最低限の設定なので要件が他にある場合は追加が必要。

React(SPA)

SPAなのでパスがなければ全部index.htmlに飛ばす。原理はどれも同じなのでVueやAngularなどのSPAもこれで行けると思う。ケツの=404がないと無限リダイレクトを起こす。

server {
  listen       80;

  location / {
    root /path/to;
    index index.html;
    try_files $uri /index.html =404;
  }
}

Next.js(SSR)

SSRなのでNext.jsのサーバープロセスにリバースプロキシする。別に直結してもいいが手前にルーティング機構があるほうが便利である。

mapステートメントがあるのは、これがないとiOS Safariでアクセスできないため

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
  listen       80;

  location / {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass  http://localhost:3000;
  }

}

Next.js(SSG)

SPAではないのでパスがなければ同名のhtmlファイルに飛ばす。ケツの=404がないと無限リダイレクトを起こす。

server {
  listen       80;

  location / {
    root /path/to;
    index index.html;
    try_files $uri $uri.html =404;
  }

}