この blog を nginx + varnish で SSL + http/2 に対応した件


少し前の話になるが、最近は SSL かどうかで Google のスコアが変わること、Let’s Encrypt が普及して SSL 証明書の入手性も大幅に改善したことから、この blog を SSL 対応するついでに http/2 にも対応させた。


元々の構成と問題点

この blog は自宅サーバで構成されているが、現在ではルータと KVM ホストの 2 台構成になっている。元々は Apache + MovableType だったが、6 年以上前に
WordPress に移行した

blog 本体は KVM 上の nginx + php-fpm で走る WordPress に載っているが、KVM ホスト・ゲストともに LAN 内にいるので、ルータ上で走っている Varnish をリバースプロクシとして外部とのゲートウェイとしていた。

この構成で SSL 対応させるためには一つ問題がある。リバースプロクシとして使っている Varnish は http/2 に対応しているものの SSL 対応していないということだ。そのため、まず再前段は nginx か Apache, Squid なりで対応する必要がある。

さらに問題なのは、blog や各種サービスがすでに稼働しているという点に尽きる。今回は幸いに未使用のグローバル IP があったので、それを利用して既存のサービスをほぼ止めないようにしつつ、SSL + http/2 対応するために、何段階かに分けて移行を実施した。

SSL 対応

まずは再前段を Varnish から別のリバースプロクシに移行させる必要がある。選択肢はいくつかあるが、

  • Let’s Encrypt で認証する際に、そのドメインで認証ファイルをホストする必要がある → × Squid
  • すでに管理用にルータで Apache が走っており、そちらへの影響を最小限にしたい → × Apache
  • ある程度実績があるものをフロントに置きたい → × Hitch

ということで、nginx でリバースプロクシをホストすることにした。第 1 段階を図にすると以下のようになる。

リバースプロクシの用意

まず nginx をインストールし、完了したら未使用のグローバル IP アドレスを割り振ってリバースプロクシ用の設定を書くが、この時点では SSL 証明書が存在しないので http のみの設定を書いておく。今回はこのようにした。

server {
	listen 218.219.153.92:80;
	server_name damelog.com www.dameningen.jp wwww.dameningen.jp blog.dameningen.jp tech.dameningen.jp;

	root /var/www/damelog.com/public_html;

	access_log /var/log/nginx/damelog.com.access_log main;
	error_log /var/log/nginx/damelog.com.error_log info;

#	location / {
#		return 301 https://damelog.com$request_uri;
#	}
	location ^~ /.well-known/ {
		allow all;
	}
}

気をつけておくのは、

  • リバースプロクシだがドキュメントルートを用意しておく。
  • http → https へのリダイレクト設定は書かないでおくかコメントアウトしておく。

点だろうか。

/etc/hosts に新しい IP アドレスを書くなどして

  • リバースプロクシが動作していること
  • /.well-known ディレクトリ以下はドキュメントルートにあるファイルを配信すること

を確認したら、リバースプロクシの用意そのものは完了となる。

SSL 証明書の発行と nginx 側の https 化

次に SSL 証明書を発行する。Let’s Encrypt の場合は CLI が用意されていたり、証明書の更新を自動化できるようになっていたりするが、今回は最小限の構成で発行する。

Gentoo Linux の場合は標準の Portage tree で  CLI を提供しているので

emerge certbot

でインストールしてから証明書を発行する。

sudo certbot certonly --agree-tos --webroot -w /var/www/damelog.com/public_html -d damelog.com -m info@damelog.com

証明書を発行できたら、リバースプロクシ側の https 設定を追加する。

server {
	listen 218.219.153.92:443 http2;
	server_name damelog.com;

	gzip on;
	gzip_types text/css application/javascript application/json application/font-woff application/font-tff application/octet-stream;

	ssl on;
	ssl_certificate /etc/letsencrypt/live/damelog.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/damelog.com/privkey.pem;
	ssl_ciphers  ECDHE+AESGCM:DHE+AESGCM:HIGH:!aNULL:!MD5;
	add_header Strict-Transport-Security "max-age=15768000; includeSubdomains";

	access_log /var/log/nginx/damelog.com.ssl_access_log main;
	error_log /var/log/nginx/damelog.com.ssl_error_log info;

	location / {
		proxy_set_header    Host    $host;
		proxy_set_header    X-Real-IP    $remote_addr;
		proxy_set_header    X-Forwarded-Host       $host;
		proxy_set_header    X-Forwarded-Server    $host;
		proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;
		proxy_set_header    X-Forwarded-Proto https;

		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";

		proxy_pass    http://192.168.1.211/;
	}
}

この時点では http 側のリダイレクトはコメントアウトしたままにして nginx を再起動する。https でアクセスができるようになったらとりあえず http/2 対応は完了になる。

WordPress 側対応

nginx 側はこれで https 対応したが、そもそも WordPress 側はまだ http 前提になっている。さらに、これまでに貯め込んだ WordPress の記事は http が前提になっている。なので、記事内の画像などはすべて http になっており、https と http が混在して https に移行した意味がなくなってしまう。
そこで、WordPress 側設定とプラグインで完全 https 対応させる。

まず、https ではなく http で WordPress にログインし、https 対応させるプラグインをインストールする。この blog の場合は SSL Insecure Content Fixer をインストールした。

SSL Insecure Content Fixer をインストールしたら、設定画面から修正方法を調整する。この blog の場合は記事内にべた書きで <img> タグを書いていたので、「コンテンツ」を選択した。

この時点で https でも期待通りに表示できるはずなので、https でも問題なく表示できるかを確認する。

表示できれば「設定」→「一般」にあるサイトの URL を https に書き換える。

真の意味での https 化

ここまで来れば、対外的な意味では SSL 証明書の更新時を除いて http は不要になる。https 側の設定でコメントアウトしておいたリダイレクト設定を戻し、http へのアクセスをすべて https にリダイレクトするようにすればひとまず https 対応は完了となる。

コンテンツキャッシュ

最後に Varnish を経路に戻す。元々 Varnish は使ってみたいからという理由だけで使っていたが、WordPress そのものが結構重いこともあって、Varnish + WordPress という構成は非常に魅力的だと思う。

今回は Varnish で SSL を受けられないこともあって、最終的にこういう構成にした。

元々 Varnish をリバースプロクシとして使っていたが、今回はリバースプロクシとしての機能は nginx に寄せてしまい、Varnish はコンテンツキャッシュとしてのみ使うようにした。そのため、若干時間をかけて既存のサービスのリバースプロクシを全部 Varnish から nginx に移行させ、いったん Varnish を止めても問題ないことを確認してからコンテンツキャッシュとして再構成した。

Varnish 自体 VCL で細かくキャッシュ動作を作り込めるが、今回は

  • GET メソッドのみキャッシュ対象
  • 管理画面以下および WordPress にログイン中はキャッシュしない

方針で以下のように書いた。

backend backend01 {
  .host = "192.168.1.211";
  .port = "80";
}
sub vcl_recv {
  set req.backend_hint = backend01;

  if (! req.method == "GET") {
    return (pass);
  }
  if (req.url ~ "^/wp-(login|admin)" || req.http.Cookie ~ "wordpress_logged_in_" ){
    return (pass);
  } else {
    return (hash);
  }
}

Varnish を立ち上げたのち、リバースプロクシ側の proxy_pass を書き換えて Varnish を指すようにする。

proxy_pass    http://192.168.1.1:8080/;

これで、キャッシュ対象の URL のヘッダを Chrome の開発者ツールなどで確認する。

上のように x-varnish ヘッダの値が 2 つになっていれば、Varnish でキャッシュされていることが分かる。


今回はあくまでもサーバサイドでコンテンツキャッシュしつつ SSL 対応するところに主眼を置いて構成変更を行った。

個人的には、nginx よりもより最適化された形でコンテンツキャッシュできる Varnish には引き続き興味を持っているので、いずれ Varnish についても書いてみたい。Varnish に任せるよりも Fastly などに投げた方がいいという話もあるが……