確認時のApache2のバージョンはApache/2.4.58 (Ubuntu)
Apache2のインストール
sudo apt -y install apache2
公開ディレクトリを触りやすくする
公開ディレクトリの所有者はデフォルトだとroot:rootで、そのままでは扱いづらいため自分自身でも手軽に扱えるように権限を調整する。
# 公開ディレクトリを見たりいじる為に自分をwww-dataに入れる
sudo usermod -aG www-data <自分のユーザー名>
# 公開ディレクトリを扱いやすくするためwww-data:www-dataに変える
sudo chown -R www-data:www-data /var/www
# 公開ディレクトリを扱いやすくするためにグループに全権限を付与する
sudo chmod -R g+rwx /var/www
mod_rewriteやmod_cgiを使えるようにする
CGIの実行に必要なPerlなどのランタイムは必要に応じて別途入れておく。PHPは一般的にCGIとして実行しないため、次項の方法で動かせるようにする。
sudo a2enmod rewrite
sudo a2enmod cgi
sudo service apache2 restart
PHPを使えるようにする
バージョンの部分は適宜書き換える。PHP本体をインストールしていない場合は別途インストールしておく(依存解決で勝手にインストールされるかもしれないが)
sudo apt install -y libapache2-mod-php8.3
sudo a2enmod php8.3
sudo service apache2 restart
nginxでhttps://example.com
を受けて、https非対応のApache2にリバプロし、Apache2側でもhttps://example.com
でアクセスされているように振舞わせる方法。取り敢えず最低限こんだけあると無難だろうという内容。
Apache2(後段)の設定
/etc/apache2/ports.conf
80や443を開けるとnginxと衝突するため、適当にずらしておく。
Listen 8080
/etc/apache2/sites-enabled/001-example.conf
ServerNameは外側と同じものにする。
<VirtualHost *:8080>
ServerName example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/example-error.log
CustomLog ${APACHE_LOG_DIR}/example-access.log combined
</VirtualHost>
nginx(前段)の設定
/etc/nginx/nginx.conf
http
セクションにupstream apache
を追記することで、conf.d/
配下の設定ファイルから共通的に参照できるようにする。同じことを複数の設定ファイルに書くと構文エラーで起動しなくなる。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/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 /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
upstream apache {
server [::1]:8080;
}
include /etc/nginx/conf.d/*.conf;
}
/etc/nginx/conf.d/example.conf
ここでは/etc/nginx/nginx.conf
で設定したupstream
にプロキシする設定を作っている。
proxy_set_header Host $host;
はnginxが受けたホストヘッダをそのままApacheに飛ばすことで、Apache側がバーチャルホストでルーティングできるようにしている。Apacheとnginxで別々のホスト名にすることもできるが、そうした場合、Apache側はApache側で定義したホスト名で動作するため、CGIなどのスクリプトを動かすときに支障があるうえ、Apache側のホスト名をhostsに書かないと疎通できないこともあり、手間なので基本的に前段のドメインに合わせておくのが無難だ。
proxy_set_header X-Real-IP $remote_addr;
はnginxにアクセスしてきたクライアントのIPをApacheに渡すための設定。そのままだとnginxのIPが渡ってしまう。
proxy_set_header X-Forwarded-Proto $scheme;
もホストヘッダの転送と似ていて、nginxが受けたプロトコルスキーマをApacheに飛ばすための設定。こうしておくとApache側はhttpで受けているのに、httpsでアクセスされたものとして認識させることが出来るため、プロトコルスキーマ付きでURLを生成するCGIがいるときに都合がいい。単にスキーマを誤魔化しているだけなので実際は暗号化されていない。
server {
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/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;
}
}
余談:HTTP通信に用いられるHOSTヘッダーについて
HTTP通信で利用されるドメインは単にHOSTヘッダーにドメインを書いているだけなので、以下のようなことをするとhostsを書かなくとも疎通できる。
curl -H "HOST: lycolia.info" http://"[2403:3a00:101:13:133:167:8:98]"
これは一般的なHTTPクライアントの仕組みとして、まずドメインをDNSに問い合わせ、IPを取得したのち、ドメインをホストヘッダーに載せてIPアドレス宛にHTTP要求を出しているからだと思われる。
hostsの書き換えができない環境で、任意のドメインに対してHTTP要求を出したい時にも重宝するので、覚えておくと何かと役に立つ。
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 |
手順
- Windows側にmkcertを入れる
- mkcertで証明書を作る
mkcert sandbox.test
- 以下のpemファイルが生成される
sandbox.test.pem
sandbox.test-key.pem
- 生成されたpemファイルを
/etc/nginx/conf.d/ssl/
に移動する /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; } }
- nginxを再起動する
sudo service nginx restart
他の端末に証明書を撒く方法
mkcertのRoot CAをエクスポートして他の端末に突っ込めば、他の端末でもpemが流用できる
"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 RoutingのHostより
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応答が返された。