23
15

More than 1 year has passed since last update.

Let's EncryptでHTTPSを終端させたいだけならNginxよりCaddyを使うと楽だった件

Last updated at Posted at 2022-07-07

みなさん!Webサーバ立ててますか?HTTPSしてますか?

弊社でも、よく自社向けの開発サーバをDockerで立てており、開発サービスは生HTTPで運用、そのフロントにHTTPS終端用のNginxをたてて、その証明書はメンテ&費用フリーのLet's Encryptで取得するようにしています。

インターネット → (HTTPS) → [ Nginx → (HTTP) → 開発サービス ]

こういうDockerでLet's EncryptでHTTPSしたいだけなら、同一のDockerネットワークに特定条件のDockerコンテナが生えるたびにNginxファイルを動的生成してLet's EncryptのACMEも通してなど全部やってくれる、下記を組み合わせるのが定番なのですが、

上記は全般的に仕組みがやや煩雑で、LEのアップデートの煽りをくらったり、謎のエラーで死んでたり、コンテナが生きてないと通らなかったり、使わないDNSエントリでACMEが通らなかったり、何かしら転び始めるとちょうど忙しい時期に Let's Encrypt certificate expiration notice for domain "your.example.com" みたいなメールが来て対応を余儀なくされることがよくあります。

そこで Caddy

そういうことで、もっとシンプルなソリューションはないかと考えた結果、Webサーバ(リバースプロキシ)として、世界シェアナンバーワンのNginxではなく、Caddyを使えばいいという結論に達しました。

インターネット → (HTTPS) → [ Caddy → (HTTP) → 開発サービス ]

※ Caddy は localhost のポートにリバースプロキシ
※ 開発サービスは localhost 上にポート公開

Caddyとは?

CaddyはWebサーバの1種です。 2015年からリリースされているかなり若いサーバです。

ただし、公式が書いているように、Webサーバ以外としても動作可能なようです。

Most people use Caddy as a web server or proxy, but at its core, Caddy is a server of servers. With the requisite modules, it can take on the role of any long-running process!

(詳しくは Wikipedia にまとまっています)

Caddyのいいところ

  • Let's Encrypt にネイティブ対応している
    • これがCaddyの最強ポイント
    • certbot など別途準備が一切不要
    • DNSでAレコード設定してれば勝手に証明書取得〜設定全部完了する
      • そのバーチャルドメインへの初回アクセス時、必要に応じて証明書を取得しに行く
      • ワイルドカードドメインについては別途設定が必要です
  • Goで書かれている
    • 基本的にメモリ安全かつ並列性に優れ、モジュールポータビリティが高いことが期待できる
    • おそらくパフォーマンスは既存ウェブサーバに勝たないと思われるが、そんなピーキー用途は少ないと思うので問題なし
  • 設定ファイルにJSON/YAML/Tomlなど使える
    • ただまあ普通は Caddyfile で書く
  • 安全なデフォルト設定
    • 何も気をつけないでも HTTP/2.0 & TLS 1.3 が有効になる
    • 自動的に http が https にリダイレクトされる
  • 便利なデフォルト設定
    • リバースプロキシに下記のヘッダを自動でつけてくれる
      • X-Forwarded-For
      • X-Forwarded-Proto
      • X-Forwarded-Host
  • Caddy自身のWebAPI経由で設定を書き換えることができる
  • 再起動無しで設定を根本からリロードできる

正直普通に開発サーバ用にHTTPS終端を立てる用途ならCaddyはおすすめです。

Caddyの設定ファイルはすごくシンプル

 
たとえば、 http://localhost:8080 にHTTPSをつなぎたいサービスがある場合、下記のファイルを /etc/caddy/Caddyfile に置いて systemctl reload caddy (Linuxの場合) すればOKです。

{
    email your_mail_address_here@example.com
}

your.example.com {
    reverse_proxy localhost:8080
}

// ちなみに設定ファイルが間違っている場合、今の設定のままサーバが動き続けます。

まだ設定がCaddyにロードされた段階では証明書は取得されませんが、On-Demand TLSという仕組みで、 https://your.example.com への初回アクセス発生時にオンデマンドで証明書が取得&設定されます。

追記 (2023/05/30):
tls { on_demand } ディレクティブの設定が必要です。

この仕組みのおかげで、特にDNSが設定されていないドメインについて、バーチャルドメイン設定が設定ファイルに書いてあっても特に害にはなりません。(使ってないドメインについてDNS設定だけが抜けるのは開発サーバあるあるです)

ちなみに、下記の設定だと、バーチャルドメインの指定すら不要になります。アクセスされたドメインで証明書を勝手に取得しにいきます。

https:// {
    tls {
        on_demand
    }
    reverse_proxy localhost:8080
}

(ただし、HTTP上はリクエスト側でバーチャルドメインは任意に指定でき、Rate Limits上限に達するまでACMEをさせる攻撃が成立してしまうので実際に上記をやってはいけません

Caddyのインストール方法について

詳しくは下記のURLに書いてあるのですが、CaddyはGoで書かれているおかげか、Windows含めいろんなプラットフォームに対応しています。

ただ、若干マイナーなせいかLinuxのディストリビューション標準のリポジトリに入っていないので、apt / dnf コマンド一発というわけにはいきません。加えて、私が手元のAmazon Linux2(ややメンテされてない)でインストールしようとした時も、インストール手順に従ってもインストールできませんでした。

そういうことで、若干インストールの面倒さを考慮すると、パッケージで入れずに Docker で立ててしまうのは一つの手だと思います。

下記のような docker-compose.yml で立ちます。

docker-compose.yml
version: "3"

services:
  caddy_server:
    image: caddy
    # ローカルホストにそのままプロキシさせたいのでホストネットワークモード
    network_mode: host
    volumes:
      # Dockerはinodeでボリュームマウントするので、
      # 設定ファイルはディレクトリごとマウントしたほうが設定ファイル書き換え時に無難
      - ./caddy:/etc/caddy:ro
      # 証明書やACME情報等の保存先
      - ./data:/data
      # 今の設定状態(保存する必要がある場合は永続化)
      - ./config:/config
./caddy/Caddyfile
{
    email your_mail_address_here@example.com
}

your.example.com {
    reverse_proxy localhost:8080
}

設定ファイルリロードは docker-compose exec -w /etc/caddy caddy_server caddy reload で出来ます。 (-w /etc/caddy は設定ファイルのあるディレクトリの指定)

Caddyのちょっと面倒なところ

これはしょうがないのですが、またディレクティブを覚え直ししなきゃいけないのが面倒です。案外IPアドレスのホワイトリストなど指定が初見時に面倒だったりします。

また、ディレクティブについても書いた順ではなく、暗黙に持っている優先度順で適用されるのが、微妙に罠っぽいです。

たとえば、下記は思ったとおりに動きません。 redirhandle より優先されます。

Caddyfile
your.example.com {
  handle /images/* {
    redir https://imageserver.example.com{uri}
  }
  # /images/ 以外は別サイトにリダイレクト
  redir https://anothersite.example.com{uri}
}

// https://your.example.com/images/foo.png
//   => https://anothersite.example.com/images/foo.png にリダイレクトされる

これは route ディレクティブを使って回避できます。名前からはあんまり想像できないですが、書いた順にディレクティブを評価するように強制できます。

Caddyfile
your.example.com {
  route {
    handle /images/* {
      redir https://imageserver.example.com{uri}
    }
    # /images/ 以外は別サイトにリダイレクト
    redir https://anothersite.example.com{uri}
  }
}

// https://your.example.com/images/foo.png
//   => https://imageserver.example.com/images/foo.png にリダイレクトされる

Caddy は LocalTunnel (ngrok) の代わりにも使える

SirTunnel という ngrok みたいに localhost のWebサーバを外部に向けてHTTPS公開できるシンプルなトンネルプロキシツールがあります。

ツールの実装として単純にポートフォワーディング(-R 9001:localhost:8080)を行うSSHコマンドを叩いてサーバ上でPythonスクリプト(sirtunnel.py sub1.example.com 9001)を動かしているだけです。

$ ssh -tR 9001:localhost:8080 example.com sirtunnel.py sub1.example.com 9001

このスクリプトの中で、ポートフォワーディング先にHTTPSからのリクエストを流し込むようにするCaddyへの構成の変更を CaddyのAPI 経由で行っているようです。

非常にシンプルですね。これをヒントに色々 Caddy で面白いプロキシサーバを作れそうな感じもします。

まとめ

特にピーキーな用途でなければ Caddy は十分に実用的なHTTPS終端になります。

日常使っている感想としては、本当に設定が楽で、面倒な証明書更新に苦しまなくなりました。みなさんも使いましょう!

23
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
15