用 Traefik 代理 Strapi
🌐 Proxying Strapi with Traefik
Page summary:告诉 Strapi 它的公共地址,并且有一个代理在它前面,通过使用
server.url和server.proxy选项。然后给 Strapi 容器添加 Traefik 标签,让 Traefik 发现它、路由到 1337 端口,并为你的域名请求证书。
Strapi 监听一个普通 的 HTTP 端口,本身不处理 TLS。像 Traefik 这样的反向代理会位于它前面处理 HTTPS,在 443 端口提供你的应用,并将请求转发到 Strapi 进程。Traefik 和本节中的其他代理不太一样:它不是通过配置文件来描述路由,而是监控 Docker API 并从每个容器的标签中读取路由规则。这种方式适合容器频繁增删的部署。本文指南会先讲如何配置 Strapi 让你的应用能识别代理,再讲 Traefik 的设置如何把流量路由到它。
🌐 Strapi listens on a plain HTTP port and does not terminate TLS itself. A reverse proxy such as Traefik sits in front of it to handle HTTPS, serve your application on port 443, and forward requests to the Strapi process. Traefik differs from the other proxies in this section: instead of a configuration file describing your routes, it watches the Docker API and reads routing rules from labels on each container. That suits deployments where containers come and go. This guide covers the Strapi configuration that makes your application proxy-aware, then the Traefik setup that routes traffic to it.
- 一个可以在本地启动和运行的 Strapi 5 应用(参见部署指南)。
- Strapi 已打包为容器镜像(见 Docker 安装)。
- Docker Compose 已安装在主机上。
- 一个 DNS
A记录指向该主机的域名。 - 端口 80 和 443 对公共互联网开放。Traefik 需要其中一个来完成证书验证。
为反向代理配置 Strapi
🌐 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
服务器配置中的 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:
- JavaScript
- TypeScript
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'),
},
});
export default ({ 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'),
},
});
host 的值必须保持为 0.0.0.0。绑定到 localhost 后,Strapi 只接受来自自己容器内部的连接,Traefik 无法访问它。
🌐 The host value must stay 0.0.0.0. Bound to localhost, Strapi accepts connections only from inside its own container and Traefik cannot reach it.
更改 /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, then rebuild your image.
信任代理头
🌐 Trust the proxy headers
Traefik 会添加一个 X-Forwarded-For 头,其中包含原始客户端的 IP 地址。Strapi 默认会忽略这个头,除非你开启代理支持。
🌐 Traefik 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:
- JavaScript
- TypeScript
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
url: env('PUBLIC_URL', 'https://api.example.com'),
proxy: {
koa: true,
maxIpsCount: 1,
},
app: {
keys: env.array('APP_KEYS'),
},
});
export default ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
url: env('PUBLIC_URL', 'https://api.example.com'),
proxy: {
koa: true,
maxIpsCount: 1,
},
app: {
keys: env.array('APP_KEYS'),
},
});
每个选项都有不同的作用:
🌐 Each option plays a different role:
| 选项 | 作用 |
|---|---|
proxy.koa | 当 true 时,Strapi 会信任 X-Forwarded-* 头。客户端 IP、协议和主机将从代理读取,而不是从套接字读取。 |
proxy.maxIpsCount | 5.52.0+ 要从转发头链的末尾读取的地址数量。对于单个代理设置为 1,如果请求经过多个代理,则设置为代理的数量。 |
proxy.ipHeader | 5.52.0+ 客户端 IP 要读取的头。默认是 X-Forwarded-For,所以只有在你的代理发送其他头(比如 CF-Connecting-IP)时才需要设置。 |
在没有设置 proxy.maxIpsCount 的情况下,将 proxy.koa 设置为 true 会让计数保持默认的 0,也就是无限制。把 maxIpsCount 设置为 Strapi 前面代理的实际数量,这样就只会读取你自己基础设施添加的地址。
🌐 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.
Strapi 会读取名为 proxy.ipHeader 的请求头,默认是 X-Forwarded-For,而 Traefik 会设置同样的请求头。Traefik 还会设置 X-Forwarded-Proto,Strapi 用它来把管理面板的刷新令牌 cookie 标记为 Secure。密码重置、登录提供者回调和媒体资源的绝对 URL 是从 url 选项构建的,而不是从那个请求头获取。
🌐 Strapi reads the header named by proxy.ipHeader, which defaults to X-Forwarded-For, and Traefik sets that same header. Traefik also sets X-Forwarded-Proto, which Strapi uses to mark the admin panel refresh-token cookie as Secure. Absolute URLs for password resets, login provider callbacks, and media assets are built from the url option instead, not from that header.
Traefik 在这里的作用不是每个代理都能做到的。默认情况下,它会清理客户端发送的 X-Forwarded-* 头,并写入自己的头,所以客户端无法在链中注入假的地址。这种保护依赖于 Traefik 是看到请求的第一个代理。如果 CDN 或负载均衡器在 Traefik 前面,就在入口点的 forwardedHeaders.trustedIPs 中列出它们的地址,这样 Traefik 就能保留上游添加的内容,然后在设置 proxy.maxIpsCount 时计算链中每个代理的数量:
🌐 Traefik helps here in a way that not every proxy does. By default it sanitizes the X-Forwarded-* headers a client sends and writes its own, so a client cannot inject a fake address into the chain. That protection depends on Traefik being the first proxy to see the request. If a CDN or load balancer sits in front of Traefik, list its addresses in the entry point's forwardedHeaders.trustedIPs so Traefik preserves what that upstream added, then count every proxy in the chain when you set proxy.maxIpsCount:
entryPoints:
websecure:
address: ':443'
forwardedHeaders:
trustedIPs:
- '203.0.113.0/24'
提高上传的文件大小限制
🌐 Raise the body size limits for uploads
Traefik 会把请求体直接传到后端,不会有大小限制,除非你自己加一个,所以默认情况下是 Strapi 的限制生效。如果你通过媒体库上传文件,就把限制提高一下吧。
🌐 Traefik streams request bodies through to the backend without a size cap unless you add one, so the Strapi limits are the ones that apply by default. 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:
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。要更改它,请参阅 本地上传提供者配置 和 最大文件大小。
🌐 The Media Library provider enforces a separate sizeLimit, which defaults to 1 GB. To change it, see local upload provider configuration and max file size.
配置 Traefik
🌐 Configure Traefik
Traefik 读取两种配置。静态配置定义入口点和证书解析器,只在 Traefik 启动时设置一次。动态配置定义路由,来自容器标签,并会在容器出现时被自动识别。
🌐 Traefik reads 2 kinds of configuration. Static configuration, which defines entry points and certificate resolvers, is set once when Traefik starts. Dynamic configuration, which defines routes, comes from container labels and is picked up as containers appear.
写静态配置
🌐 Write the static configuration
以下文件在每个公共端口定义了一个入口点,将 HTTP 重定向到 HTTPS,并注册了一个 Let's Encrypt 证书解析器:
🌐 The following file defines an entry point on each public port, redirects HTTP to HTTPS, and registers a Let's Encrypt certificate resolver:
entryPoints:
web:
address: ':80'
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ':443'
providers:
docker:
# Only route to containers that opt in with traefik.enable=true
exposedByDefault: false
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com
storage: /acme/acme.json
tlsChallenge: {}
将 exposedByDefault 设置为 false 很重要。如果保持默认设置,Traefik 会发布它能看到的每个容器,包括那些你根本不想暴露的容器。
🌐 Setting exposedByDefault to false matters. Left at its default, Traefik publishes every container it can see, including ones you never meant to expose.