# 用 Caddy 代理 Strapi

> Source: https://strapi.nodejs.cn/cms/deployment/guides/caddy

🌐 Proxying Strapi with Caddy

将 `server.url` 指向你的公共域，并将 `server.proxy.koa` 设置为 `true`，这样 Strapi 就会信任转发的头信息。然后写一个三行的 Caddyfile，它会为你获取和续订 TLS 证书。

Strapi 监听的是普通的 HTTP 端口，它本身不会终止 TLS。你可以在它前面放一个反向代理，比如 Caddy，来处理 HTTPS，把你的应用提供在 443 端口，并将请求转发给 Strapi 进程。Caddy 就是通过为你申请和续期 TLS 证书来完成这个工作的。这个指南会讲如何配置 Strapi，让你的应用能识别代理，然后是如何写 Caddyfile 来路由流量到 Strapi。Strapi 的改动需要在你的项目里进行，所以部署前先改好。Caddy 的改动则是在运行 Caddy 的机器或容器上进行。

🌐 Strapi listens on a plain HTTP port and does not terminate TLS itself. A reverse proxy such as Caddy sits in front of it to handle HTTPS, serve your application on port 443, and forward requests to the Strapi process. Caddy fills that role by provisioning and renewing TLS certificates for you. This guide covers the Strapi configuration that makes your application proxy-aware, then the Caddyfile that routes traffic to it. The Strapi changes belong in your project, so make them before you deploy. The Caddy changes are made on the machine or in the container that runs Caddy.

:::prerequisites

- 一个可以在本地启动和运行的 Strapi 5 应用（参见[部署指南](/cms/deployment#general-guidelines)）。
- Caddy 可以运行在与 Strapi 相同的主机上，也可以作为容器运行在同一个 Docker 网络上（参见 [Caddy installation documentation](https://caddyserver.com/docs/install)）。
- 一个 DNS `A` 记录指向该服务器的域名。
- 端口 80 和 443 对公网开放。Caddy 需要端口 80 来完成证书挑战。
- 具有 `sudo` 权限的 Shell 访问。

:::

## 为反向代理配置 Strapi {#configure-strapi-for-a-reverse-proxy}

🌐 Configure Strapi for a reverse proxy

Strapi 需要知道它被访问的公共地址，并且需要信任代理添加的头信息。没有这两个设置，Strapi 会用 `localhost:1337` 构建 URL，并把代理的 IP 地址当作客户端 IP。

🌐 Strapi needs to know the public address it is served from, and it needs to trust the headers the proxy adds. Without these 2 settings, Strapi builds URLs from `localhost:1337` and reads the proxy's IP address as the client IP.

### 设置公共网址 {#set-the-public-url}

🌐 Set the public URL

服务器配置中的 `url` 选项定义了你应用的公网地址。Strapi 会用它来生成密码重置邮件、第三方登录提供商以及媒体资源路径的绝对 URL。

🌐 The `url` option in the server configuration defines the public address of your application. Strapi uses it to build absolute URLs for password reset emails, third-party login providers, and media asset paths.

将它设置为你应用访客在浏览器中使用的地址：

🌐 Set it to the address your application's visitors use in their browser:

```js title="/config/server.js"
module.exports = ({ env }) => ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  url: env('PUBLIC_URL', 'https://api.example.com'),
  app: {
    keys: env.array('APP_KEYS'),
  },
});
```
```ts title="/config/server.ts"

  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  url: env('PUBLIC_URL', 'https://api.example.com'),
  app: {
    keys: env.array('APP_KEYS'),
  },
});
```

:::caution

更改 `/config/server.js` 需要重建管理面板。保存文件后运行 `yarn build` 或 `npm run build`。

🌐 Changing `/config/server.js` requires rebuilding the admin panel. Run `yarn build` or `npm run build` after saving the file.

:::

### 信任代理头 {#trust-the-proxy-headers}

🌐 Trust the proxy headers

Caddy 会添加一个 `X-Forwarded-For` 头，包含原始客户端的 IP 地址。Strapi 会忽略这个头，直到你开启代理支持。

🌐 Caddy adds an `X-Forwarded-For` header carrying the original client IP address. Strapi ignores that header until you turn proxy support on.

通过服务器配置中的 `proxy` 选项启用代理支持：

🌐 Enable proxy support through the `proxy` options in the server configuration:

```js title="/config/server.js"
module.exports = ({ env }) => ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  url: env('PUBLIC_URL', 'https://api.example.com'),
  // highlight-start
  proxy: {
    koa: true,
    maxIpsCount: 1,
  },
  // highlight-end
  app: {
    keys: env.array('APP_KEYS'),
  },
});
```
```ts title="/config/server.ts"

  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  url: env('PUBLIC_URL', 'https://api.example.com'),
  // highlight-start
  proxy: {
    koa: true,
    maxIpsCount: 1,
  },
  // highlight-end
  app: {
    keys: env.array('APP_KEYS'),
  },
});
```

每个选项都有不同的作用：

🌐 Each option plays a different role:

| 选项 | 作用 |
|--------|--------|
| `proxy.koa` | 当 `true` 时，Strapi 会信任 `X-Forwarded-*` 头。客户端 IP、协议和主机将从代理读取，而不是从套接字读取。 |
| `proxy.maxIpsCount` |  要从转发头链的末尾读取的地址数量。对于单个代理设置为 `1`，如果请求经过多个代理，则设置为代理的数量。 |
| `proxy.ipHeader` |  客户端 IP 要读取的头。默认是 `X-Forwarded-For`，所以只有在你的代理发送其他头（比如 `CF-Connecting-IP`）时才需要设置。 |

:::warning IP spoofing

如果在没有 `proxy.maxIpsCount` 的情况下将 `proxy.koa` 设置为 `true`，计数会保持在默认的 `0`，也就是无限。将 `maxIpsCount` 设置为你实际在 Strapi 前面的代理数量，这样只有你的基础设施添加的地址会被读取。Caddy 默认会丢弃客户端提供的 `X-Forwarded-*` 值，所以当代理或 CDN 位于 Caddy 前面时，这一点就显得尤其重要。

🌐 Setting `proxy.koa` to `true` without `proxy.maxIpsCount` leaves the count at its default of `0`, which means unlimited. Set `maxIpsCount` to the real number of proxies in front of Strapi so that only addresses added by your own infrastructure are read. Caddy discards client-supplied `X-Forwarded-*` values by default, so this matters most when a proxy or CDN sits in front of Caddy.

:::

Strapi 会读取名为 `proxy.ipHeader` 的头部，默认是 `X-Forwarded-For`。Caddy 会设置同样的头部，所以你不需要更改它。

🌐 Strapi reads the header named by `proxy.ipHeader`, which defaults to `X-Forwarded-For`. Caddy sets that same header, so you do not need to change it.

Caddy 在这里的作用是并不是每个代理都能做到的。它会丢弃客户端发送的任何 `X-Forwarded-*` 值，并写入自己的值，这样客户端就无法在链中注入伪造的地址。这个保护依赖于 Caddy 是第一个看到请求的代理。如果在 Caddy 前有 CDN 或负载均衡器，应该将它声明为可信代理，这样 Caddy 就会保留上游添加的地址：

🌐 Caddy helps here in a way that not every proxy does. It discards any `X-Forwarded-*` values a client sends and writes its own, so a client cannot inject a fake address into the chain. That protection depends on Caddy being the first proxy to see the request. If a CDN or load balancer sits in front of Caddy, declare it as a trusted proxy so Caddy preserves the addresses that upstream added:

```
{
    servers {
        trusted_proxies static private_ranges
    }
}

api.example.com {
    reverse_proxy 127.0.0.1:1337
}
```

在 Strapi 端设置 `proxy.maxIpsCount` 时，计算那条链中的每一个代理。

🌐 Count every proxy in that chain when you set `proxy.maxIpsCount` on the Strapi side.

### 提高上传的文件大小限制 {#raise-the-body-size-limits-for-uploads}

🌐 Raise the body size limits for uploads

Caddy 默认不会限制请求体，所以适用的是 Strapi 的限制。如果你通过媒体库上传文件，需要提高它们的大小。

🌐 Caddy does not cap request bodies by default, so the Strapi limits are the ones that apply. If you upload files through the Media Library, raise them.

在 Strapi 这边，`body` 中间件会解析传入的请求。上传的文件会以 multipart 数据的形式到来，所以 `formidable.maxFileSize` 是限制它们的选项。`formLimit` 和 `jsonLimit` 选项处理普通表单字段和 JSON 数据，而不是文件本身：

🌐 On the Strapi side, the `body` middleware parses incoming requests. Uploaded files arrive as multipart data, so `formidable.maxFileSize` is the option that caps them. The `formLimit` and `jsonLimit` options cover ordinary form fields and JSON payloads, not the file itself:

```js title="/config/middlewares.js"
module.exports = [
  // ...
  {
    name: 'strapi::body',
    config: {
      formLimit: '100mb', // form body
      jsonLimit: '100mb', // JSON body
      textLimit: '100mb', // text body
      formidable: {
        maxFileSize: 100 * 1024 * 1024, // uploaded file size, in bytes
      },
    },
  },
  // ...
];
```

媒体库提供者会执行一个单独的 `sizeLimit`，默认值是 1 GB。要更改它，请参阅 [本地上传提供者配置](/cms/configurations/media-library-providers/local-upload) 和 [最大文件大小](/cms/features/media-library#max-file-size)。

🌐 The Media Library provider enforces a separate `sizeLimit`, which defaults to 1 GB. To change it, see [local upload provider configuration](/cms/configurations/media-library-providers/local-upload) and [max file size](/cms/features/media-library#max-file-size).

## 配置 Caddy {#configure-caddy}

🌐 Configure Caddy

在 Strapi 识别了代理之后，下一步就是配置 Caddyfile 将流量转发给它。

🌐 With Strapi aware of the proxy, the next step is the Caddyfile that forwards traffic to it.

### 写 Caddyfile {#write-the-caddyfile}

🌐 Write the Caddyfile

Caddy 从单个文件读取其配置，默认路径为 `/etc/caddy/Caddyfile`。一个可用的 Strapi 代理只需三行：

🌐 Caddy reads its configuration from a single file, by default at `/etc/caddy/Caddyfile`. A working Strapi proxy takes 3 lines:

```
api.example.com {
    reverse_proxy 127.0.0.1:1337
}
```

在区块顶部命名一个公共域会触发自动 HTTPS。Caddy 在第一次启动时会为 `api.example.com` 获取 Let's Encrypt 证书，将 HTTP 重定向到 HTTPS，并在证书过期前自动续期。配置中不会出现证书路径。

🌐 Naming a public domain at the top of the block is what triggers automatic HTTPS. Caddy obtains a Let's Encrypt certificate for `api.example.com` on first start, redirects HTTP to HTTPS, and renews the certificate before it expires. No certificate paths appear in the configuration.

`reverse_proxy` 指令会自行设置 `X-Forwarded-For`、`X-Forwarded-Proto` 和 `X-Forwarded-Host`，这就是为什么这个块保持简短。上述 Strapi `proxy` 选项依赖的就是这些头部。WebSocket 连接也会在无需额外配置的情况下被代理，[远程数据传输](/cms/features/data-management/transfer) 功能就是依赖这个的。

🌐 The `reverse_proxy` directive sets `X-Forwarded-For`, `X-Forwarded-Proto`, and `X-Forwarded-Host` on its own, which is why the block stays short. Those are the headers the Strapi `proxy` options above rely on. WebSocket connections are also proxied without extra configuration, which the [remote data transfer](/cms/features/data-management/transfer) feature relies on.

:::note

Caddyfile 是大多数部署使用的格式，但它不是唯一的一种。Caddy 的原生配置格式是 JSON，配置适配器可以将 YAML、TOML、HCL、CUE，甚至现有的 Nginx 配置转换为 JSON。使用 `--adapter` 标志选择一种格式，就像 `caddy run --config caddy.yaml --adapter yaml` 中一样。完整列表请参见  [Caddy config adapters documentation](https://caddyserver.com/docs/config-adapters) 。不管你选择哪种格式，本指南中的 Strapi 选项都是一样的。

:::

重新加载 Caddy 以应用文件：

🌐 Reload Caddy to apply the file:

```bash
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
```

`caddy validate` 命令在你重新加载之前会检查配置。重新加载有问题的配置会让网站宕机，所以别跳过这一步。

🌐 The `caddy validate` command checks the configuration before you reload. Reloading a broken configuration takes the site down, so do not skip it.

### 设置上传大小上限 {#set-an-upload-size-cap}

🌐 Set an upload size cap

除非你另有说明，否则 Caddy 可以接受任何大小的请求体。在 `/etc/caddy/Caddyfile` 中设置明确限制会在代理阶段就拒绝超大上传，而不是等 Strapi 缓存后才处理：

🌐 Caddy accepts request bodies of any size unless you tell it otherwise. Setting an explicit cap in `/etc/caddy/Caddyfile` rejects oversized uploads at the proxy rather than after Strapi has buffered them:

```
api.example.com {
    request_body {
        max_size 100MB
    }

    reverse_proxy 127.0.0.1:1337
}
```

在 Strapi 的 `body` 中将该值保持在 `formidable.maxFileSize` 或以上，否则 Caddy 会拒绝 Strapi 本来可以接受的上传。不要拿 `formLimit` 来比较：它限制的是普通表单字段，而不是上传的文件。

🌐 Keep this value at or above `formidable.maxFileSize` in the Strapi `body` middleware, otherwise Caddy rejects uploads that Strapi would have accepted. `formLimit` is not the one to compare against: it caps ordinary form fields, not the uploaded file.

:::note

`max_size` 子指令从 Caddy v2.3.0 开始可用。在更早的版本中，可以省略这个块，让 Strapi 中的 `formidable.maxFileSize` 来限制即可。

🌐 The `max_size` subdirective is available since Caddy v2.3.0. On an earlier version, leave the block out and let `formidable.maxFileSize` in Strapi enforce the limit instead.

:::

### 代理到运行在容器中的 Strapi {#proxy-to-strapi-running-in-a-container}

🌐 Proxy to Strapi running in a container

当 Caddy 和 Strapi 都以容器运行时，`reverse_proxy` 通过名称定位 Strapi 服务。在 Caddy 容器内部，`127.0.0.1` 指的是该容器本身，而不是 Strapi。下面的 Caddyfile 由接下来的 Compose 文件挂载到容器中：

🌐 When Caddy and Strapi both run as containers, `reverse_proxy` targets the Strapi service by name. Inside the Caddy container, `127.0.0.1` refers to that container itself, not to Strapi. The Caddyfile below is mounted into the container by the Compose file that follows:

```
api.example.com {
    request_body {
        max_size 100MB
    }

    reverse_proxy strapi:1337
}
```

Strapi 还必须绑定到 `0.0.0.0`。绑定到 `localhost` 时，它只接受来自自己容器内的连接，Caddy 无法访问它。本指南前面显示的 `host` 值已经使用了 `0.0.0.0`。

🌐 Strapi must also bind to `0.0.0.0`. Bound to `localhost`, it accepts connections only from inside its own container and Caddy cannot reach it. The `host` value shown earlier in this guide already uses `0.0.0.0`.

下面的 Compose 文件把两个服务放在同一个网络上，并且只发布 Caddy：

🌐 The following Compose file puts both services on one network and publishes only Caddy:

```yml title="./docker-compose.yml"
services:
  strapi:
    image: my-strapi-app
    environment:
      HOST: 0.0.0.0
      PORT: 1337
      PUBLIC_URL: https://api.example.com
    # expose keeps the port reachable inside the network only
    expose:
      - '1337'
    networks:
      - web

  caddy:
    image: caddy:alpine
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
    depends_on:
      - strapi
    networks:
      - web

volumes:
  caddy_data:

networks:
  web:
```

:::warning

在 `/data` 挂载一个持久卷，如上所示。Caddy 会把发放的证书存放在那里。如果没有，每次容器重启都会请求新的证书，这会达到 [Let](https://letsencrypt.org/docs/rate-limits/) ，并且在限制重置之前网站没有有效证书。

:::

## 检查代理设置 {#verify-the-proxy-setup}

🌐 Verify the proxy setup

Strapi 在 `/_health` 提供了一个健康检查路由，该路由会返回 HTTP `204 No Content` 和一个 `strapi` 头。通过代理请求它可以确认 Caddy 已经连接到 Strapi：

🌐 Strapi exposes a health check route at `/_health` that responds with HTTP `204 No Content` and a `strapi` header. Requesting it through the proxy confirms that Caddy reaches Strapi:

```bash
curl -I https://api.example.com/_health
```

一个可用的设置会返回状态行和头部：

🌐 A working setup returns the status line and the header:

```
HTTP/2 204
strapi: You are so French!
```

然后确认链的其余部分：

🌐 Then confirm the rest of the chain:

1. 在浏览器中打开 `https://api.example.com/admin` 并登录。管理面板通过 HTTPS 加载，没有证书警告。
2. 在媒体库上传一张图片。它的 URL 使用你的域名，而不是 `localhost:1337`。
3. 检查 Strapi 输出中的真实客户端 IP 地址，而不是 `127.0.0.1`。如果 Strapi 在前台运行，地址会出现在那个终端里。

##  故障排除 {#icon-namebug--troubleshooting}

以下每一个症状都指向设置的一侧。症状以粗体显示，后面跟着原因以及需要更改的地方：

🌐 Each of the following symptoms points at one side of the setup. The symptom is in bold, followed by what causes it and what to change:

- **Caddy 获取证书失败。** 证书验证需要公网上可以访问端口 80，并且该域名的 DNS `A` 记录必须已经指向这台服务器。先检查这两点，然后用 `journalctl -u caddy --no-pager | tail -50` 查看 Caddy 日志。
- **Caddy 返回 `502 Bad Gateway`。** Caddy 无法连接到 Strapi。请确认 Strapi 进程正在运行并监听 `reverse_proxy` 中使用的端口，然后检查 `/config/server.js` 中的 `host` 没有绑定到 Caddy 无法访问的接口。
- **上传被拒绝，因为文件太大。** 请求超过了大小限制。在 Caddyfile 的 `request_body` 块中提高 `max_size`，在 Strapi 的 `body` 中间件中提高 `formidable.maxFileSize`，如果文件大于 1 GB，再提高提供商 `sizeLimit`。
- **Strapi 把 `127.0.0.1` 记录为客户端 IP。** `proxy.koa` 没有设置为 `true`，所以 Strapi 读取的是套接字地址，而不是转发的头信息。
- **密码重置邮件链接到 `localhost:1337`。** `url` 选项未设置或仍指向本地地址。将其设置为公共 URL 然后重建管理面板。

## 下一步 {#next-steps}

🌐 Next steps

<NextSteps title="">
    <NextSteps.Step
    title="Run Strapi under a process manager"
    description="PM2 restarts Strapi on failure and starts it again after a reboot."
    link="/cms/deployment/guides/pm2"
  />
  <NextSteps.Step
    title="Review the server configuration options"
    description="The full list of options available in the server config file."
    link="/cms/configurations/server"
  />
