Caddy WebServer入門


Caddy WebServer入門

こちらで同じ記事を書いています

CaddyはGolangで書かれた軽量かつ高機能なWebサーバです
Let'sEncryptの証明書を自動生成したり、HTTP/3に逸早く対応出来たりと高機能なのが特徴です

また、設定ファイルも柔軟かつ簡単に記述出来ます
しかし設定ファイルに書き方が色々ありすぎて、結局どう書くのが正解なのかよく分からないという問題もあります
(2020年5月にバージョン2になりましたが、そのためネットで見つかる解決策がバージョン1のものが多かったりもします)

現在はWebServerというとNginxが優勢な状況ですが、情報が増えてくれば今後はCaddyの使用率も上がるかもしれません

1.Caddyの実験環境を作る

環境を汚さず手元で簡単に動かすためにDockerを使用します

1.1 ファイルの作成

実験が簡単に行えるように最小限の設定を記述したファイルを作ります

docker-compose.yml

version: "3.7"
services:
  caddy:
    container_name: caddy
    image: caddy:alpine
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./contents :/contents
    ports:
      - "80:80"
      - "443:443/tcp"
      - "443:443/udp"
Caddyfile
:80 {
    file_server {
        root /contents
    }
}
contents/index.html
<html>
  <head>
    <title>Caddyテスト</title>
  </head>
  <body>Caddyテストコンテンツ</body>
</html>

1.2 実行

以下のコマンドでCaddyを実行します
最後に-dを付けていないのは、その場でエラーメッセージを確認するためです

docker-compose -f docker-compose.yml up

1.3 実行結果

http://localhost にアクセスして、以下の結果を確認します

Caddyテストコンテンツ

2.各種設定

2.1 VirtualHost

CaddyのVirtualHost機能を使ってみます

Caddyfile
127.0.0.1:80 {
    file_server
    root * /contents/host1
}
localhost:80 {
    file_server
    root * /contents/host2
} 
contents/host1/index.html
<html>
  <head>
    <title>Caddyテスト</title>
  </head>
  <body>
    HOST1
  </body>
</html>
contents/host2/index.html
<html>
  <head>
    <title>Caddyテスト</title>
  </head>
  <body>
    HOST2
  </body>
</html>

2.1.1 結果

以下のように表示されます

http://127.0.0.1 -> HOST1
http://localhost -> HOST2

2.2 location

nginxのlocation的なものを試してみます
ファイル配置は先ほどの続きになります

想定している結果は以下の内容です
http://localhost/a/ -> HOST1
http://localhost/b/ -> HOST2

2.2.1 駄目な例

Caddyfile
:80 {
    file_server
    root /a/* /contents/host1
    root /b/* /contents/host2
} 

これは罠です
http://localhost/a にアクセスすると /contents/host1/a にファイルを探しに行きます
nginxと同じつもりで書くと、ファイルは見つかりません

2.2.2 正しい書き方

Caddyfile
:80 {
    file_server
    handle_path /a/* {
        root * /contents/host1
    }
    handle_path /b/* {
        root * /contents/host2
    }
}

handle_pathを使うと、ブロック内部へパスが結合されなくなります
かなり重要な項目なのですが、caddyのhandle_pathに関して日本語の情報は皆無でした

再利用可能ブロック

Caddyfile
(SETTING1) {
    root * /contents/host1
}
(SETTING2) {
    root * /contents/host2
}
:80 {
    file_server
    handle_path /a/* {
        import SETTING1
    }
    handle_path /b/* {
        import SETTING2
    }
}

()で囲んで名前を付けて、importで呼び出します
定義位置はブロックの中でも問題ありません

2.3 Methodによるアクセス分岐

Caddyfile
:80 {
    @PATH_A {
        method GET POST
        path *
    } 
    @PATH_B {
        method POST
        path *
    } 

    file_server
    handle_path /a/* {
        root @PATH_A /contents/host1
    }
    handle_path /b/* {
        root @PATH_B /contents/host2
    }
}

http://localhost/a/ はGETでもPOSTでもアクセスできます
http://localhost/a/ はPOSTのみコンテンツを返します
@の宣言はブロック内でのみ可能です

2.4 Headerの操作

Caddyfile
:80 {
    file_server
    handle_path /a/* {
        root * /contents/host1
        header {
            Content-Type text/plain
        }
    }
    handle_path /b/* {
        root * /contents/host2
    }
}

http://localhost/a/ にアクセスするとHTMLがテキストとして表示されます
以下の書き方も可能です

Caddyfile
:80 {
    file_server
    handle_path /a/* {
        root * /contents/host1
    }
    handle_path /b/* {
        root * /contents/host2
    }
    header /a/* {
        Content-Type text/plain
    }
}

2.5 Proxy

Caddyfile
:80 {
    file_server
    handle_path /proxy/* {
        reverse_proxy / unix//app/sock/app.sock {
            header_up Location_path /proxy
        }
    }
}

reverse_proxyで他のプロセスなどからコンテンツをとってくることも出来ます
UNIXソケットを使う場合はunix/という書き方になります
また、http://アドレスでコンテンツを取得することも可能です
header_upはProxy先にヘッダを追加するときに使用します
header_downでレスポンスのヘッダを操作することも可能です

2.6 httpsとLet'sEncryptの証明書発行

Caddyfile
www.example.com {
    file_server {
        root /contents
    }
}

httpsを使いたいときはドメイン名を書くだけです
対象ドメインでアクセス可能な状態になっていれば、特に追加設定を書かずともLet'sEncryptの証明書は自動的に生成されます
(httpでのアクセスは自動的にhttpsにリダイレクトされます)

2.7 レスポンスの強制指定

Caddyfile
:80 {
    file_server
    respond * 200 {
        body "Hello World"
        close
    }
}

どこにアクセスしてもHello Worldを返します

2.8 HTTP3を使用する

Caddyfile
{
    experimental_http3
}
www.example.com:443 {
    file_server {
        root /contents
    }
} 

experimental_http3を追加するだけです
あとはサーバが443/UDPに対するアクセスが可能になっていれば、自動的にHTTP/3が使用されます
個別のブロックの中にexperimental_http3を記述すれば、特定のドメインのみHTTP/3を有効に出来ます

2.9 アクセス制限

このサンプルはWordpressによく見られる攻撃をループバックアドレスへリダイレクトする設定です
remote_ipを単独で使えば対象のアドレスを設定でき、notを使えば対象外を設定できます

Caddyfile
www.example.com {
    …
    @disallowed {
        path /xmlrpc.php
        path *.sql
        path /wp-content/uploads/*.php
        path /wp-login.php
        not {
            remote_ip 許可したいIP
        }
    }
    redir @disallowed http://127.0.0.1/
}

2.10 ログ設定とimportの引数

ログの出力設定でargsを使っています
特定のログ出力設定を複数書きたい場合などに便利です
argsはimportから引数を渡せます

Caddyfile
(log) {
    log {
        format filter {
            wrap json
            fields {
                common_log delete
            }
        }        
        output file /var/log/access-{args.0}.log {
            roll_size 100mb
            roll_keep 5
            roll_keep_for 30d
        }
   }
}
www.example.com {
    import log ファイル名
}

2.11 私の使っている共通設定

テキストファイルの圧縮や画像ファイルのキャッシュ設定が主です

Caddyfile
(default) {
    file_server
    push
    @encode {
        path / *.html *.js *.css *.svg
    }
    encode @encode zstd gzip
    @static {
        file 
        path *.ico *.css *.js *.gif *.jpg *.jpeg *.png *.svg *.woff *.woff2 *.webp
    }
    header @static Cache-Control public, max-age=31536000, immutable
}
www.example.com {
    import default
}

3.まとめ

 Caddyの設定はブロックを省略して一行で全て完結するような書き方もあり、同じことをやろうと思っても、色々書き方が可能です。
 柔軟と言えば柔軟なのですが、そのせいで分かりにくくなっている部分もあります。
 設定を行うときは、とにかく色々試してみて、動くかどうか確認してみるというのが一番のようです。