お知らせ

現在サイトのリニューアル作業中のため、表示が崩れているページが存在することがあります。

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が流用できる

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、後段にapache2を配置し、adiaryはapache2側で動作させる
  • 動作URLはhttp://adiary.example.test/のようなサブドメイン付きURLとする
    • adiary.cgiなしでアクセス可能にする
  • 開発を楽にするため、ホームディレクトリからシンボリックリンクを飛ばし、VSCodeで編集できるようにする
  • nginxやapache2、perlは既にインストールされているものとする
    • apache2はポート8080でListenしているものとする

確認環境

Env Ver
OS Ubuntu 24.04.3 LTS
nginx 1.26.1
apache2 2.4.58 (Ubuntu)
adiary 3.51a

手順

この手順では手描きした内容をteeに書き直しており、正しくファイルが生やせるか見てないので注意。

  1. /etc/nginx/nginx.confhttpセクションのincludeの手前にapacheのupstream情報を追記する
    upstream apache {
      server [::1]:8080;
    }
    
  2. nginx→apache2のリバプロ設定の作成

    cat <<'EOF' | sudo tee /etc/nginx/conf.d/adiary.conf
    server {
      listen        [::]:80;
      client_max_body_size 100M;
      server_name  adiary.example.local;
    
      access_log   /var/log/nginx/adiary.example.access.log;
      error_log    /var/log/nginx/adiary.example.error.log;
    
      location / {
        proxy_pass http://apache/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
      }
    }
    EOF
    
    sudo service nginx restart
    
  3. Apache2でadiaryをホスティングするバーチャルホスト設定の作成

    cat <<'EOF' | sudo tee /etc/apache2/sites-enabled/001-adiary.conf
    <VirtualHost *:8080>
          # internal use only domain
          ServerName adiary.example.local
    
          ServerAdmin webmaster@localhost
          DocumentRoot /var/www/html/sites/adiary
    
          # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
          # error, crit, alert, emerg.
          # It is also possible to configure the loglevel for particular
          # modules, e.g.
          #LogLevel info ssl:warn
    
          ErrorLog ${APACHE_LOG_DIR}/adiary-error.log
          CustomLog ${APACHE_LOG_DIR}/adiary-access.log combined
    
          # For most configuration files from conf-available/, which are
          # enabled or disabled at a global level, it is possible to
          # include a line for only one particular virtual host. For example the
          # following line enables the CGI configuration for this host only
          # after it has been globally disabled with "a2disconf".
          #Include conf-available/serve-cgi-bin.conf
    </VirtualHost>
    EOF
    
  4. /etc/apache2/apache2.confを開き<Directory /var/www/>のセクションを次のように書き換える
    Options Indexes FollowSymLinks
    AllowOverride All
    Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
    AddHandler cgi-script .cgi
    Require all granted
    
    • Options +ExecCGI -MultiViews +SymLinksIfOwnerMatchの行は標準のserve-cgi-bin.confからパクってきたのでOptions +ExecCGIだけでも動く可能性がある
    • AllowOverride Allはindex.htmlを残したまま表示させないために.htaccessを書くのに使う
  5. mod_rewriteとmod_cgiを有効化し、apache2を再起動

    sudo a2enmod rewrite
    sudo a2enmod cgi
    
    sudo service apache2 restart
    
  6. /var/www/html/sites/adiaryを作成し、adiaryの動作環境一式を配置

  7. 開発し易いように権限を緩和
    chmod 777 -R /var/www/html/sites/adiary
    sudo chown -R <your-user-name>:<your-user-name> /var/www/html/sites/adiary/
    
  8. /var/www/html/sites/adiary/.htaccessにadiary用の.htaccessを書く

    DirectoryIndex
    
    RewriteEngine   On
    
    # favicon
    RewriteRule     ^favicon\.ico           /path/to/icon.png [L]
    
    # 正規URLは通す
    RewriteCond     %{REQUEST_FILENAME}     !-f
    RewriteRule     ^(.*)                  adiary.cgi/$1   [L]
    

Apache2でadiaryを動かす理由

結論から書くとnginx + fcgiwrapだとtry_filesの仕様的にadiaryが行うパス解析が上手く行かずに正常に動作しないためだ。

元々ModRewrite前提のCGIとして設計されており、個人的なユースケースがCGIとしての動作であることから、Apacheで動かすことにした。

adiaryの作者がcgiラッパーを作っているため、これを使えばnginxでも動かせそうだが、adiary専用のデーモンを起動するのもなんか嫌というのも理由の一つとしてある。

何より現在このブログが動作している環境が、さくらのレンタルサーバーであり、構成的にnginx + Apacheであるため、この構成を作ることに興味があったというのもあるし、現状のローカル環境ではnginxをフロントに立たせて後ろで様々なものを動かしている都合で、フロントはnginxで捌いた方が管理がしやすいのもある。

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

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;
  }

}