Skip to main content

用 Nginx 代理 Strapi

🌐 Proxying Strapi with Nginx

Page summary:

使用 server.url 和 server.proxy 选项告诉 Strapi 它的公共地址以及前面有一个代理。然后添加一个 Nginx server 块,将请求连同原始客户端信息一起转发给 Strapi。

Strapi 监听一个普通的 HTTP 端口,并不会自己终止 TLS。像 Nginx 这样的反向代理会放在它前面来处理 HTTPS,在 443 端口提供你的应用,并将请求转发给 Strapi 进程。本指南涵盖了设置的两个部分:让你的应用支持代理的 Strapi 配置,以及把流量路由到 Strapi 的 Nginx 配置。Strapi 的改动属于你的项目,所以在部署前就做好。Nginx 的改动则需要在运行 Nginx 的机器或容器上进行。

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

Prerequisites
  • 一个可以在本地启动和运行的 Strapi 5 应用(参见部署指南)。
  • Nginx 可以运行在与 Strapi 相同的主机上,或者作为同一 Docker 网络上的容器运行(见 Nginx installation documentation)。
  • 一个 DNS A 记录指向该服务器的域名。
  • 具有 sudo 权限的 Shell 访问。

为反向代理配置 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:

/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'),
},
});
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

Nginx 会添加一个 X-Forwarded-For 头,携带原始客户端 IP 地址。Strapi 默认会忽略这个头,除非你开启代理支持。

🌐 Nginx 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:

/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'),
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.maxIpsCount5.52.0+ 要从转发头链的末尾读取的地址数量。对于单个代理设置为 1,如果请求经过多个代理,则设置为代理的数量。
proxy.ipHeader5.52.0+ 客户端 IP 要读取的头。默认是 X-Forwarded-For,所以只有在你的代理发送其他头(比如 CF-Connecting-IP)时才需要设置。
IP spoofing

在没有设置 proxy.maxIpsCount 的情况下将 proxy.koa 设置为 true 会让计数保持在默认值 0,也就是无限制。这样客户端就可以发送 X-Forwarded-For: 203.0.113.9,一旦 Nginx 添加了真实地址,Strapi 会从链的前面读取伪造的值,而不是最后的真实地址。一定要把 maxIpsCount 设置为 Strapi 前面代理的真实数量。

🌐 Setting proxy.koa to true without proxy.maxIpsCount leaves the count at its default of 0, which means unlimited. A client can then send X-Forwarded-For: 203.0.113.9 and, once Nginx appends the real address, Strapi reads the spoofed value from the front of the chain instead of the real one at the end. Always set maxIpsCount to the real number of proxies in front of Strapi.

Strapi 会读取名为 proxy.ipHeader 的头部,默认是 X-Forwarded-For。下面的 Nginx 配置设置了同样的头部,所以你不需要修改它。只有当上游的代理使用了不同的名字,比如 Cloudflare 后面的 CF-Connecting-IP 时,才需要覆盖它。

🌐 Strapi reads the header named by proxy.ipHeader, which defaults to X-Forwarded-For. The Nginx configuration below sets that same header, so you do not need to change it. Override it only when a proxy further upstream uses a different name, such as CF-Connecting-IP behind Cloudflare.

提高上传的文件大小限制​

🌐 Raise the body size limits for uploads

Nginx 和 Strapi 都有自己的请求大小限制,以较小的那个为准。如果你通过媒体库上传文件,需要两个都调大。

🌐 Nginx and Strapi each enforce their own request size limit, and the smaller of the 2 wins. If you upload files through the Media Library, raise both.

在 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:

/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。要更改它,请参阅 本地上传提供者配置 和 最大文件大小。

🌐 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.

配置 Nginx​

🌐 Configure Nginx

当 Strapi 知道代理存在后,下一步就是 Nginx 的 server 块,它会将流量转发给 Strapi。

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

创建服务器块​

🌐 Create the server block

为你的网站创建一个配置文件:

🌐 Create a configuration file for your site:

/etc/nginx/sites-available/strapi.conf
# Send the upgrade hint only on requests that actually ask for it
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 80;
server_name api.example.com;

# Raise this to match the largest upload you accept
client_max_body_size 100M;

location / {
proxy_pass http://127.0.0.1:1337;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}

X-Forwarded-Proto 头告诉 Strapi 原始请求是通过 HTTPS 到达的。Strapi 使用它将管理员面板的刷新令牌 cookie 标记为 Secure。密码重置、登录提供者回调和媒体资源的绝对 URL 则是从 url 选项构建的,而不是从这个头构建。

🌐 The X-Forwarded-Proto header tells Strapi that the original request arrived over HTTPS. Strapi uses it 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 this header.

启用网站并重新加载 Nginx。下面的 sites-available 和 sites-enabled 布局是 Debian 和 Ubuntu 的惯例。在基于 RHEL 的发行版上,将文件放在 /etc/nginx/conf.d/,然后跳过创建符号链接:

🌐 Enable the site and reload Nginx. The sites-available and sites-enabled layout below is the Debian and Ubuntu convention. On RHEL-based distributions, place the file in /etc/nginx/conf.d/ instead and skip the symlink:

terminal
sudo ln -s /etc/nginx/sites-available/strapi.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

nginx -t 命令在你重新加载之前会验证配置。重新加载有问题的配置会让网站宕机,所以别跳过这一步。

🌐 The nginx -t command validates the configuration before you reload. Reloading a broken configuration takes the site down, so do not skip it.

上面的 map 块只会在请求本身需要升级时才转发连接升级,而不是给每个代理请求都附加升级提示。Strapi 需要它来支持 远程数据传输 功能,当你对远程实例运行 strapi transfer 时,它会打开一个 WebSocket 连接。

🌐 The map block above forwards the connection upgrade only on requests that request one, rather than attaching an upgrade hint to every proxied request. Strapi needs it for the remote data transfer feature, which opens a WebSocket connection when you run strapi transfer against a remote instance.

代理到运行在容器中的 Strapi​

🌐 Proxy to Strapi running in a container

当 Nginx 和 Strapi 都作为容器运行时,有两个细节会发生变化。

🌐 When Nginx and Strapi both run as containers, 2 details change.

首先,proxy_pass 是通过名称而不是回环地址来定位 Strapi 服务的。在 Nginx 容器内部,127.0.0.1 指的是容器本身,而不是 Strapi。Docker 会在共享网络上解析服务名称,所以真正能到达 Strapi 的是服务名称:

🌐 First, proxy_pass targets the Strapi service by name rather than by loopback address. Inside the Nginx container, 127.0.0.1 refers to that container itself, not to Strapi. Docker resolves service names on a shared network, so the service name is what reaches Strapi:

./nginx/strapi.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 80;
server_name api.example.com;

client_max_body_size 100M;

location / {
proxy_pass http://strapi:1337;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}

其次,Strapi 必须绑定到 0.0.0.0。如果绑定到 localhost,它只接受来自自身容器内部的连接,Nginx 就无法访问它。本指南前面显示的 host 值已经使用了 0.0.0.0,这就是让容器可访问的原因。

🌐 Second, Strapi must bind to 0.0.0.0. Bound to localhost, it accepts connections only from inside its own container and Nginx cannot reach it. The host value shown earlier in this guide already uses 0.0.0.0, which is what makes the container reachable.

下面的 Compose 文件将两个服务放在同一个网络上,并且只发布 Nginx:

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

./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

nginx:
image: nginx:alpine
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx/strapi.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- strapi
networks:
- web

networks:
web:

在 Strapi 服务中使用 expose 而不是 ports 可以让它不出现在公网接口上,这样流量只能通过 Nginx 进入。

🌐 Using expose rather than ports for the Strapi service keeps it off the public interface, so traffic can only arrive through Nginx.

终止 TLS​

🌐 Terminate TLS

到目前为止显示的服务器块监听的是 80 端口。生产环境部署需要一个证书和 443 端口的监听。Nginx 本身不会获取证书,所以可以配合一个颁发和续期证书的工具,或者在流量到达 Nginx 之前就终止 TLS。

🌐 The server block shown so far listens on port 80. A production deployment needs a certificate and a listener on port 443. Nginx does not obtain certificates itself, so pair it with a tool that issues and renews them, or terminate TLS before traffic reaches Nginx at all.

以下选项都是常用的:

🌐 The following options are all in common use:

选项适用情况
CertbotLet's Encrypt 的参考客户端。它的 Nginx 插件可以帮你重写服务器块并设置续期。
acme.sh一个不依赖 Python 的 shell 客户端,并且支持广泛的 DNS 提供商,在你需要通过 DNS-01 挑战获取通配符证书时很有用。
lego一个单独的 Go 可执行文件,在精简镜像和容器构建中很方便。
nginx-proxy and acme-companion容器部署场景,每个容器颁发证书,无需手动编辑配置。
云负载均衡器或 CDNTLS 在上游终止的设置,例如由 AWS Certificate Manager、Cloudflare 或你的主机提供商的负载均衡器处理。

无论你选择哪一个,服务器块最终都会监听443端口并指向证书文件。用下面的内容替换上面创建的80端口块,这样会在80端口保留第二个块,让普通的HTTP请求被重定向,而不是直接到达Strapi:

🌐 Whichever you choose, the server block ends up listening on 443 and pointing at the certificate files. Replace the port 80 block created above with the following, which keeps a second block on 80 so plain HTTP requests are redirected rather than reaching Strapi:

/etc/nginx/sites-available/strapi.conf
# Send the upgrade hint only on requests that actually ask for it
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 80;
server_name api.example.com;

return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
http2 on;
server_name api.example.com;

ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;

# Raise this to match the largest upload you accept
client_max_body_size 100M;

location / {
proxy_pass http://127.0.0.1:1337;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}

http2 on; 自 Nginx 1.25.1 起是一个独立指令。在旧版本中,请改用 listen 443 ssl http2;。将证书文件放好后重载 Nginx,下面的检查会针对 HTTPS 地址进行。

Caution

如果 TLS 在上游被负载均衡器或 CDN 终止,Nginx 可以继续监听 80 端口。上游仍然必须发送 X-Forwarded-Proto: https,否则 Strapi 会将请求视为普通的 HTTP 并生成 http:// URL。在设置 proxy.maxIpsCount 时要计算链条中的每一个代理。

🌐 If TLS is terminated upstream by a load balancer or CDN, Nginx can keep listening on port 80. That upstream must still send X-Forwarded-Proto: https, otherwise Strapi treats the request as plain HTTP and generates http:// URLs. Count every proxy in the chain when setting proxy.maxIpsCount.

检查代理设置​

🌐 Verify the proxy setup

Strapi 在 /_health 提供了一个健康检查路由,会返回 HTTP 204 No Content 和一个 strapi 头。通过代理请求它,可以确认 Nginx 可以访问 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 Nginx reaches Strapi:

terminal
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 在前台运行,地址会出现在那个终端里。

故障排除​

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

🌐 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:

  • Nginx 返回 502 Bad Gateway。 Nginx 无法访问 Strapi。请确认 Strapi 进程正在运行并监听 proxy_pass 中使用的端口。然后检查 /config/server.js 中的 host 没有绑定到 Nginx 无法访问的接口。
  • 上传失败,错误代码 413 Request Entity Too Large。 请求超出了大小限制。在 Nginx 中提高 client_max_body_size,在 Strapi 的 body 中间件中提高 formidable.maxFileSize,如果文件大于 1 GB,还要提高提供者 sizeLimit。这三个都必须允许该文件。
  • Strapi 把 127.0.0.1 记录为客户端 IP。 proxy.koa 没有设置为 true,所以 Strapi 读取的是套接字地址,而不是转发的头信息。
  • 密码重置邮件链接到 localhost:1337。 url 选项未设置或仍指向本地地址。将其设置为公共 URL 然后重建管理面板。
  • 管理面板刷新 cookie 没有标记为 Secure。 Nginx 没有转发 X-Forwarded-Proto,所以 Strapi 把请求当作普通的 HTTP。这个 cookie 仍然通过 HTTPS 到达浏览器,因为它默认是 SameSite=Lax,但是它会丢失防止在普通 HTTP 下发送的 Secure 标志。把这个头添加到 location 块里。

下一步​

🌐 Next steps