强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Caddy 从入门到精通 / 07 - 中间件 / Middleware

中间件 / Middleware

中间件是位于请求和响应之间的处理层,用于完成认证、压缩、限流、日志等横切关注点。

Middleware sits between request and response, handling cross-cutting concerns like authentication, compression, rate limiting, and logging.


🟢 基础 / Basics

压缩(Compression / Encode)

example.com {
    encode gzip zstd
    root * /var/www/html
    file_server
}

encode 自动根据 Accept-Encoding 头选择压缩算法,对文本资源进行压缩。

基本认证(Basic Auth)

example.com {
    basicauth /* {
        # 密码:用 caddy hash-password 生成
        # caddy hash-password --plaintext "mypassword"
        admin $2a$14$Zkx19XLiW6VYouLRRfbb3OHOJGEqJ3p8fGk5ME3m1/RMvZQU5FhOS
    }
    reverse_proxy localhost:3000
}

生成密码哈希:

caddy hash-password --plaintext "mypassword"

请求头操作

example.com {
    # 添加响应头
    header X-App-Name "MyApp"
    header X-Frame-Options "SAMEORIGIN"

    # 删除响应头
    header -Server
    header -X-Powered-By

    # 安全头组合
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
    }

    reverse_proxy localhost:3000
}

日志

{
    log {
        output file /var/log/caddy/access.log
    }
}

example.com {
    log {
        output file /var/log/caddy/example.log {
            roll_size 10mb
            roll_keep 10
        }
    }
    reverse_proxy localhost:3000
}

🟡 进阶 / Intermediate

限流(Rate Limiting)

需要第三方插件 caddy-ratelimit

{
    order rate_limit before reverse_proxy
}

example.com {
    rate_limit {
        zone dynamic {
            key    {remote_host}
            events 100
            window 1m
        }
        zone static {
            key    {remote_host}
            events 500
            window 1m
        }
    }

    @static_files path *.css *.js *.png *.jpg
    handle @static_files {
        root * /var/www/static
        file_server
    }

    handle {
        reverse_proxy localhost:3000
    }
}

编译包含插件:

xcaddy build --with github.com/mholt/caddy-ratelimit

IP 白名单 / 黑名单

example.com {
    # 仅允许内网访问
    @admin {
        remote_ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
    }

    handle /admin/* {
        @not_lan not remote_ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
        respond @not_lan 403
        reverse_proxy localhost:9090
    }

    reverse_proxy localhost:3000
}

CORS 配置

example.com {
    @cors_preflight {
        method OPTIONS
        header Origin *
    }

    @cors_api {
        header Origin *
    }

    handle @cors_preflight {
        header {
            Access-Control-Allow-Origin "*"
            Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
            Access-Control-Allow-Headers "Content-Type, Authorization"
            Access-Control-Max-Age "86400"
        }
        respond "" 204
    }

    handle @cors_api {
        header Access-Control-Allow-Origin "*"
        reverse_proxy localhost:3000
    }

    handle {
        reverse_proxy localhost:3000
    }
}

日志格式自定义(JSON 格式)

{
    log {
        format json {
            time_format "2006-01-02T15:04:05Z07:00"
        }
        output file /var/log/caddy/access.log {
            roll_size 50mb
            roll_keep 20
            roll_keep_for 720h
        }
    }
}

example.com {
    log {
        format json {
            time_format "2006-01-02T15:04:05Z07:00"
        }
        output file /var/log/caddy/example.log {
            roll_size 100mb
            roll_keep 10
        }
    }
    reverse_proxy localhost:3000
}

JSON 格式日志更方便 ELK / Loki 等日志系统采集。

自定义日志字段

(log_custom) {
    log {
        format json {
            time_format "2006-01-02T15:04:05Z07:00"
        }
        output file /var/log/caddy/{args[0]}.log {
            roll_size 50mb
            roll_keep 10
        }
    }
}

example.com {
    import log_custom example
    reverse_proxy localhost:3000
}

按条件记录日志

example.com {
    @not_health {
        not path /healthz /readyz /metrics
    }

    log {
        format json
        output file /var/log/caddy/access.log
    }

    # 只记录非健康检查的请求
    reverse_proxy localhost:3000
}

注意:Caddyfile 中目前没有直接的「条件日志」语法,但可以通过 JSON API 实现。

追踪请求 ID

example.com {
    header X-Request-ID {request_id}
    reverse_proxy localhost:3000 {
        header_up X-Request-ID {request_id}
    }
}

{request_id} 是 Caddy 内置变量,每个请求自动生成唯一 ID。


🔴 高级 / Advanced

中间件执行顺序

Caddyfile 中中间件按声明顺序执行,但理解背后的模型很重要:

请求进入
  → encode(压缩 — 在响应阶段生效)
  → basicauth(认证)
  → header(请求头处理)
  → reverse_proxy(处理请求)
  → header(响应头处理)
  → 返回响应

对于 encode 这类"响应中间件",它实际上在请求阶段注册一个拦截器,在响应阶段执行压缩。

使用 route 强制顺序

example.com {
    route {
        # 1. 先认证
        basicauth {
            admin $2a$14$hash
        }
        # 2. 再限流
        rate_limit { ... }
        # 3. 再代理
        reverse_proxy localhost:3000
    }
}

route 块内严格按声明顺序执行。

order 指令控制全局顺序

{
    order rate_limit before reverse_proxy
    order basicauth before reverse_proxy
    order encode after reverse_proxy
}

当使用第三方插件时,order 可能是必需的。

IP 拒绝列表(基于 CrowdsSec)

xcaddy build --with github.com/hslatman/caddy-crowdsec-bouncer
{
    order crowdsec first
}

example.com {
    crowdsec {
        api_url http://127.0.0.1:8080
        api_key {env.CROWDSEC_API_KEY}
    }
    reverse_proxy localhost:3000
}

CrowdSec 是一个去中心化的安全引擎,可以自动分析日志并封禁恶意 IP。

集成 Prometheus Metrics

xcaddy build --with github.com/mholt/caddy-events-prometheus
{
    metrics
}

example.com {
    reverse_proxy localhost:3000
}

:2019 {
    metrics /metrics
}

暴露 Prometheus 格式的 metrics 端点。

请求体修改

example.com {
    @post method POST

    handle @post {
        request_body {
            max_size 10MB
        }
        reverse_proxy localhost:3000
    }
}

限制请求体大小,防止大文件上传耗尽资源。

按 User-Agent 分流

example.com {
    @mobile {
        header_regexp Mobile User-Agent "(?i)(iphone|android|mobile)"
    }
    @desktop not header_regexp Mobile User-Agent "(?i)(iphone|android|mobile)"

    handle @mobile {
        root * /var/www/mobile
        file_server
    }

    handle @desktop {
        root * /var/www/desktop
        file_server
    }
}

静态文件 + 反向代理 + 认证组合

example.com {
    # 公开静态资源
    handle /public/* {
        header Cache-Control "public, max-age=86400"
        root * /var/www/static
        file_server
    }

    # 需要认证的 API
    handle /api/* {
        basicauth {
            admin $2a$14$hash
        }
        reverse_proxy localhost:8080
    }

    # 需要认证的管理后台
    handle /admin/* {
        basicauth {
            admin $2a$14$hash
        }
        reverse_proxy localhost:9090
    }

    # 默认:SPA
    handle {
        root * /var/www/spa/dist
        try_files {path} /index.html
        file_server
    }
}

小结 / Summary

层级内容
🟢 基础encode 压缩、basicauth 认证、header 头操作、日志
🟡 进阶限流、IP 黑白名单、CORS、JSON 日志、请求 ID
🔴 高级执行顺序模型、order 指令、CrowdSec、Prometheus、复杂组合

下一章:API 与动态配置 / API & Dynamic Config