Envoy API 网关
Architecture and configuration of the Envoy API gateway for self-hosted Supabase.
自托管 Supabase 默认使用基于 Envoy 的 API 网关。它接收客户端的请求,将它们路由到内部服务(Auth、PostgREST、Realtime、Storage、Edge Functions、postgres-meta、Studio),并通过将不透明的 sb_ 密钥转换为这些服务使用的内部凭据来执行 API 密钥认证。
🌐 Self-hosted Supabase uses an Envoy-based API gateway by default. It accepts incoming client requests, routes them to internal services (Auth, PostgREST, Realtime, Storage, Edge Functions, postgres-meta, Studio), and enforces API key authentication by translating opaque sb_ keys into the internal credentials used by those services.
本指南向想要了解或自定义 Envoy 网关的运维人员介绍其架构、配置布局和安全状态。这不是 Envoy 教程——有关过滤器、路由和集群的参考,请查看 Envoy 文档。
🌐 This guide explains the architecture, configuration layout, and security posture of the Envoy gateway for operators who want to understand or customize it. It is not an Envoy tutorial - for reference on filters, routes, and clusters, see the Envoy documentation.
使用网关 #
🌐 Using the gateway
Envoy 是默认的 API 网关,启动堆栈时会自动运行——不需要额外配置。
🌐 Envoy is the default API gateway and runs automatically when you start the stack - no extra configuration is required.
Envoy 被注册为 api-gw 服务,同时还将 envoy 和 kong 作为网络别名,以帮助从旧配置过渡。
🌐 Envoy is registered as the api-gw service and also exposes envoy and kong as network aliases to help transition from legacy configurations.
确认网关正在路由请求并执行 API 密钥:
🌐 Confirm the gateway is routing requests and enforcing API keys:
curl -i -H "apikey: your-supabase-secret-key" http://<your-domain>/rest/v1/来自 PostgREST 的 200 OK 响应确认网关已启动。一个没有 apikey 头的 401 Unauthorized 确认执行已激活。
🌐 A 200 OK response from PostgREST confirms the gateway is up. A 401 Unauthorized without the apikey header confirms enforcement is active.
架构 #
🌐 Architecture
Envoy作为一个单容器(supabase-envoy)运行,在端口8000上有一个监听器。每个传入请求都会先经过一个按顺序排列的HTTP过滤器链,然后才被转发到上游集群:
🌐 Envoy runs as a single container (supabase-envoy) with one listener on port 8000. Every incoming request passes through an ordered chain of HTTP filters before being forwarded to an upstream cluster:
Client │ ▼Listener (port 8000) │ ▼HTTP filter chain ├─ CORS ├─ Basic Auth (dashboard only) ├─ Lua: copy ?apikey query to header ├─ Lua: 401 for missing/invalid API key ├─ Lua: translate opaque keys in query ├─ Lua: translate opaque keys in header ├─ Lua: mirror apikey to x-api-key (Realtime WS) ├─ Lua: synthesize Authorization header ├─ RBAC (global: service_role → /pg/, apikey → other API routes; │ per-route DENY override on /mcp and Realtime │ /api/tenants, /api/openapi) └─ Router │ ▼Upstream clusters ├─ auth (auth:9999) ├─ rest (rest:3000) ├─ realtime (realtime-dev.supabase-realtime:4000) ├─ storage (storage:5000) ├─ functions (functions:9000) ├─ meta (meta:8080) └─ studio (studio:3000)路由会按照监听器配置中声明的顺序进行匹配。每条路由都会选择一个上游集群,重写请求路径前缀,并且可以覆盖过滤器的行为(例如,在 API 路由上禁用基本认证,或者在 MCP 路由上拒绝所有流量)。
🌐 Routes are matched in the order declared in the listener config. Each route selects an upstream cluster, rewrites the request path prefix, and can override filter behavior (for example, disabling basic auth on API routes or denying all traffic on MCP routes).
配置文件结构 #
🌐 Configuration file structure
所有 Envoy 配置都存放在 ./volumes/api/envoy/(相对于包含 docker-compose.yml 的目录):
🌐 All Envoy configuration lives in ./volumes/api/envoy/ (relative to the directory containing docker-compose.yml):
| 文件 | 作用 |
|---|---|
envoy.yaml | 引导配置。将 Envoy 指向 CDS 和 LDS 文件,配置管理界面,并对下游连接设置过载管理器限制。 |
cds.yaml | 集群发现服务 - 上游服务定义(DNS、端口、健康检查、连接超时、熔断器)。 |
lds.template.yaml | 监听器发现服务模板。定义监听器、过滤器链、路由、RBAC 策略和 CORS 策略。包含密钥和凭证的占位符。 |
docker-entrypoint.sh | 在容器启动时,将 LDS 模板渲染到 lds.yaml,通过替换环境变量,计算 DASHBOARD_PASSWORD 的 SHA1+base64 哈希用于基本认证,然后启动 Envoy。 |
启动时配置是如何呈现的 #
🌐 How the configuration is rendered at startup
Envoy 本身不能直接在配置中读取环境变量。入口脚本会在启动 Envoy 之前,用 sed 将环境特定的值渲染到监听器配置中:
🌐 Envoy cannot natively read environment variables inside its config. The entrypoint script renders environment-specific values into the listener config with sed before launching Envoy:
- 对
DASHBOARD_PASSWORD计算 SHA1+base64 哈希,然后将其与DASHBOARD_USERNAME拼接成一个DASHBOARD_BASIC_AUTH字符串,格式为 Envoy 的basic_auth过滤器所期望的username:{SHA}<base64-encoded-sha1>格式。不支持其他哈希格式。 - 接着将以下变量代入
lds.template.yaml,结果写入lds.yaml:
| 变量 | 用途 |
|---|---|
ANON_KEY | 旧版 HS256 匿名 JWT(API 密钥验证,RBAC) |
SERVICE_ROLE_KEY | 旧版 HS256 service_role JWT(API 密钥验证,RBAC) |
SUPABASE_PUBLISHABLE_KEY | 不透明的 sb_publishable_* 密钥(翻译源) |
SUPABASE_SECRET_KEY | 不透明的 sb_secret_* 密钥(翻译源) |
ANON_KEY_ASYMMETRIC | 预签名 ES256 匿名 JWT(翻译目标;仅内部使用) |
SERVICE_ROLE_KEY_ASYMMETRIC | 预签名 ES256 service_role JWT(翻译目标;仅内部使用) |
DASHBOARD_BASIC_AUTH | 仪表板基础认证凭证(在步骤 1 中计算) |
- 如果
SUPABASE_SECRET_KEY、SUPABASE_PUBLISHABLE_KEY、ANON_KEY_ASYMMETRIC和SERVICE_ROLE_KEY_ASYMMETRIC都设置了,就会启用不透明密钥翻译。否则,Envoy 会以仅支持传统模式运行,只接受传统的 HS256 密钥。入口点会在启动时把当前模式打印到容器日志里。
配置更改需要重启容器,这样入口点才能重新渲染模板:
🌐 Configuration changes require restarting the container so the entrypoint re-renders the template:
sh run.sh restart api-gw路线 #
🌐 Routes
路由会按声明的顺序匹配。第一个匹配的前缀优先。受保护的路由需要有效的 apikey 头;开放路由无需 API 密钥验证即可通过。
🌐 Routes are matched in the order declared. The first matching prefix wins. Protected routes require a valid apikey header; open routes pass through without API key validation.
| 路径前缀 | 上游 | 路径重写 | 访问控制 | 备注 |
|---|---|---|---|---|
/auth/v1/verify | 认证 | /verify | 开放 | 邮箱验证 |
/auth/v1/callback | 认证 | /callback | 开放 | OAuth 回调 |
/auth/v1/authorize | 认证 | /authorize | 开放 | OAuth 授权 |
/auth/v1/.well-known/jwks.json | 认证 | /.well-known/jwks.json | 开放 | 用于第三方验证的 JWKS |
/.well-known/oauth-authorization-server | 认证 | - | 开放 | OAuth 2.0 授权服务器元数据 (RFC 8414) |
/auth/v1/sso/saml/acs | 认证 | /sso/saml/acs | 开放 | SAML 断言消费者 |
/auth/v1/sso/saml/metadata | 认证 | /sso/saml/metadata | 开放 | SAML 元数据 |
/functions/v1/ | 功能 | / | sb_ 键 | 拒绝无效和冲突的 sb_ 键;其他通过;闲置超时 160 秒,上游响应超时 410 秒 |
/storage/v1/ | 存储 | / | 绕过 | 存储执行它自己的授权 |
/auth/v1/ | 认证 | / | API 密钥 | 受保护的认证端点 |
/rest/v1/ | 剩余 | / | API 密钥 | PostgREST 开放 API 根(需要密钥) |
/rest/v1/* | 剩余 | / | API 密钥 | PostgREST 数据端点 |
/graphql/v1 | 剩余 | /rpc/graphql | API 密钥 | pg_graphql(添加 Content-Profile: graphql_public) |
/realtime/v1/api/tenants | 实时 | - | 拒绝 | 实时管理 API(默认阻止) |
/realtime/v1/api/openapi | 实时 | - | 拒绝 | 实时 OpenAPI 规范(默认被阻止) |
/realtime/v1/api | 实时 | /api | API 密钥 | 实时 REST API(广播、通道、ping) |
/realtime/v1/ | 实时 | /socket/ | API 密钥 | 实时 WebSocket |
/pg/ | 元数据 | / | API 密钥 | postgres-meta / Studio 数据库操作(需要密钥) |
/api/mcp | 工作室 | - | 拒绝 | MCP 端点(默认通过 RBAC 拒绝阻止) |
/mcp | 工作室 | /api/mcp | 拒绝 | MCP 端点(默认通过 RBAC 拒绝阻止) |
/(通用) | 工作室 | - | 基本认证 | 仪表板;会去掉入站的 Authorization 头 |
默认情况下,网关会拒绝 MCP 路由。要允许本地访问,需要编辑 /mcp 路由上的 RBAC 规则。有关允许模式,请查看 lds.template.yaml 中的内联注释,安全模型请参考 启用 MCP 服务器访问。
🌐 MCP routes are denied at the gateway by default. Allowing local access requires editing the RBAC rule on the /mcp route. See the inline comments in lds.template.yaml for the allowed pattern, and Enabling MCP Server Access for the security model.
/functions/v1/ 路由会在 volumes/api/envoy/lds.template.yaml 中将 idle_timeout 设置为 160s,并将 timeout 设置为 410s。这样可以让运行时的 150 秒请求空闲超时和 400 秒工作线程生命周期限制先到期。有关运行时设置,请参见 内存或超时错误。
🌐 The /functions/v1/ route sets idle_timeout to 160s and timeout to 410s in volumes/api/envoy/lds.template.yaml. These allow the runtime's 150-second request idle timeout and 400-second worker lifetime limit to expire first. See Memory or timeout errors for the runtime settings.
身份验证 #
🌐 Authentication
网关处理三个与认证相关的步骤:在通用路由上进行仪表板基本认证、在受保护路由上执行 API 密钥验证,以及在执行前进行不透明到内部密钥的转换步骤。
🌐 The gateway handles three authentication-related steps: dashboard basic auth on the catch-all route, API key enforcement on protected routes, and an opaque-to-internal key translation step that runs before enforcement.
仪表板基本认证 #
🌐 Dashboard basic auth
通配的 / 路由需要使用来自 DASHBOARD_USERNAME 和 DASHBOARD_PASSWORD 的 HTTP 基本认证。Authorization 头会在请求转发到 Studio 之前被移除(不管是什么方案),所以基本认证的凭据永远不会到达上游服务。
🌐 The catch-all / route requires HTTP basic auth with credentials from DASHBOARD_USERNAME and DASHBOARD_PASSWORD. The Authorization header is stripped (regardless of scheme) before the request is forwarded to Studio, so the basic auth credentials never reach the upstream service.
在受保护的路由上强制使用 API 密钥 #
🌐 API key enforcement on protected routes
受保护的路由(/auth/v1/、/rest/v1/、/graphql/v1、/realtime/v1/api、/realtime/v1/、/pg/)需要 apikey 头包含一个有效的配置密钥。可接受的密钥是新的不透明 sb_publishable_* 和 sb_secret_* 密钥(转换为内部 JWT)或旧版的 ANON_KEY 和 SERVICE_ROLE_KEY HS256 JWT API 密钥。
🌐 The protected routes (/auth/v1/, /rest/v1/, /graphql/v1, /realtime/v1/api, /realtime/v1/, /pg/) require the apikey header to contain a valid configured key. Acceptable keys are the new opaque sb_publishable_* and sb_secret_* keys (translated to internal JWTs) or the legacy ANON_KEY and SERVICE_ROLE_KEY HS256 JWT API keys.
一个 Lua 过滤器会用 HTTP 401 Unauthorized 拒绝缺失或无效的键。然后,RBAC 过滤器会应用更精细的规则:
🌐 A Lua filter rejects missing or invalid keys with HTTP 401 Unauthorized. An RBAC filter then applies finer-grained rules:
/pg/- 只允许使用service_role键(sb_secret_*或旧版SERVICE_ROLE_KEY)/rest/v1/(确切路径,OpenAPI 架构根)- 只允许使用service_role键/rest/v1/<table>和其他数据 API 路径对所有有效密钥仍然可访问
不透明密钥翻译 #
🌐 Opaque key translation
当配置了新的 API 密钥(sb_publishable_*、sb_secret_*)时,一系列 Lua 过滤器会将不透明密钥转换为相应的预签名内部 JWT,然后请求才会到达 API 密钥执行和上游服务。在 /functions/v1/ 上会跳过这个链,它有自己的过滤器。参见 Edge Functions。
🌐 When the new API keys (sb_publishable_*, sb_secret_*) are configured, a chain of Lua filters translates opaque keys into the corresponding pre-signed internal JWTs before the request reaches API key enforcement and upstream services. The chain is skipped on /functions/v1/, which has its own filter. See Edge Functions.
这个链条按这个顺序运行:
🌐 The chain operates in this order:
- 查询参数复制。 如果请求包含
?apikey=...但没有apikey头部,该值会被复制到apikey头部。这可以统一仅使用查询参数的客户端(例如来自浏览器的 Realtime WebSocket 连接,因为自定义头部不可用)。 - 查询参数翻译。 如果
apikey查询参数包含一个不透明的密钥,它会被替换——无论是在 URL 中还是在apikey头中——为对应的预签名内部 JWT。 - 头部翻译。 如果
apikey头部包含一个不透明的密钥,它会被对应的预签名内部 JWT 替换。 x-api-key镜像。 在实时 WebSocket 路由上,apikey值会被复制到x-api-key头中(Realtime 首先读取它用于 WebSocket 认证)。Authorization合成。 如果客户端在Authorization头中没有发送真实的 JWT(或者只发送了Bearer sb_*值,这不是有效的 JWT),网关会从(可能已翻译的)apikey头合成Authorization: Bearer <apikey>。在实时 WebSocket 路由上会跳过这一步,该路由使用的是x-api-key。
有关不透明密钥与非对称密钥的背景信息,请参见 新 API 密钥与非对称认证。
🌐 For background on opaque vs asymmetric keys, see New API Keys and Asymmetric Authentication.
边缘函数 #
🌐 Edge Functions
在 /functions/v1/ 上,一个专用的 Lua 过滤器处理 API 密钥,而不会更改 apikey 或 Authorization 头。当配置了新的 API 密钥时,它的工作方式如下:
🌐 On /functions/v1/, a dedicated Lua filter handles API keys without changing the apikey or Authorization headers. When the new API keys are configured, it works as follows:
| 请求 | 结果 |
|---|---|
没有 apikey 或 Authorization | 直接通过 |
apikey 是 sb_publishable_* 或 sb_secret_* | 通过,但 sb-api-key 设置为内部 JWT |
Authorization: Bearer sb_... 并且没有 apikey | 同上,使用 Authorization 的密钥 |
非 sb_ 值(旧版 API 密钥、用户会话令牌或其他任意值) | 直接通过 |
未知 sb_ 密钥 | 拒绝,并返回 401 Unauthorized |
apikey 和 Authorization: Bearer sb_... 包含不同的密钥 | 拒绝,并返回 401 Unauthorized |
这个过滤器总是会移除客户端发送的任何 sb-api-key 头。没有配置新的 API 密钥时,所有其他请求都会照常通过。sb-api-key 的值是原始 JWT,没有 Bearer 前缀。这个路由不会读取 apikey 查询参数。
🌐 The filter always removes any sb-api-key header sent by the client. Without the new API keys configured, all other requests pass through unchanged. The sb-api-key value is the raw JWT, without a Bearer prefix. The apikey query parameter is not read on this route.
当 FUNCTIONS_VERIFY_JWT 是 true 时,函数服务会在 Authorization 中验证 JWT。如果 Authorization 缺失或包含 sb_ 键,它会改为验证 sb-api-key。在请求到达你的函数之前,它会移除 sb-api-key。函数也可以自己检查 API 密钥,例如使用 @supabase/server 的 withSupabase。参见 自托管边缘函数。
🌐 When FUNCTIONS_VERIFY_JWT is true, the functions service verifies the JWT in Authorization. If Authorization is missing or contains an sb_ key, it verifies sb-api-key instead. It removes sb-api-key before the request reaches your function. Functions can also check API keys themselves, for example with withSupabase from @supabase/server. See Self-hosted Edge Functions.
转发的头信息和跨域资源共享 #
🌐 Forwarded headers and CORS
X-Forwarded 头 #
🌐 X-Forwarded headers
Envoy 会把转发的头信息附加到每个上游请求上,这样下游服务就可以重建最初面向客户端的 URL。
🌐 Envoy attaches forwarded headers to every upstream request so downstream services can reconstruct the original client-facing URL.
| 头部 | 值 |
|---|---|
X-Forwarded-Host | 客户端的 Host 头部(如果尚未存在则添加) |
X-Forwarded-Port | 监听器端口(8000) |
X-Forwarded-Proto | 由 Envoy 根据连接自动设置(http 或 https) |
X-Forwarded-Prefix | 按路由设置为匹配的路径前缀(例如,Storage 的 /storage/v1) |
X-Forwarded-For | 自动设置,因为监听器上启用了 use_remote_address: true |
X-Forwarded-Prefix 是 Storage 用于 S3 签名 v4 验证和构建 TUS 上传位置 URL 所必需的。
跨域资源共享 #
🌐 CORS
网关在虚拟主机层面应用了宽松的 CORS 策略:
🌐 The gateway applies a permissive CORS policy at the virtual-host level:
- 允许所有来源
- 方法:
GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD, CONNECT, TRACE - 允许所有请求和响应头
- 预检最大存活时间:3600 秒
这与当前的 Supabase 平台行为以及之前基于 Kong 的网关相匹配。Supabase API 的认证边界是 apikey 头,而不是请求来源。
🌐 This matches both the current Supabase platform behavior and the previous Kong-based gateway. The auth boundary for Supabase APIs is the apikey header rather than the request origin.
/pg/ 路由(通过 meta 直接进行 Postgres 内省,由服务角色密钥控制)是个例外:它有自己更严格的每路由 CORS 覆盖,仅限 SUPABASE_PUBLIC_URL 和 localhost/127.0.0.1 来源,因为没有合理的理由让这个路由被任意第三方来源调用。
🌐 The /pg/ route (direct Postgres introspection via meta, gated by the service role key) is the exception: it carries its own stricter per-route CORS override, restricted to SUPABASE_PUBLIC_URL and localhost/127.0.0.1 origins, since there's no legitimate reason for this route to be called from an arbitrary third-party origin.
如果你在 lds.template.yaml 中自定义 cors: 块以启用 allow_credentials: true,你必须将 allow_origin_string_match 限制到特定来源——浏览器(以及 Envoy)会拒绝将凭证与通配符来源一起使用的组合。
🌐 If you customize the cors: block in lds.template.yaml to enable allow_credentials: true, you must restrict allow_origin_string_match to specific origins - browsers (and Envoy) reject the combination of credentials with a wildcard origin.
安全加固 #
🌐 Security hardening
网关已按这些面向生产的设置进行了配置:
🌐 The gateway is configured with these production-oriented settings:
| 设置 | 目的 |
|---|---|
normalize_path: true、merge_slashes: true | 防止绕过 RBAC 前缀规则的路径混淆 |
path_with_escaped_slashes_action: REJECT_REQUEST | 拒绝路径中包含 URL 编码斜杠的请求 |
use_remote_address: true | 将 Envoy 视为边缘代理:使用对等连接 IP 作为可信客户端地址(而不是信任客户端提供的 X-Forwarded-For),并移除不可信的 x-envoy-* 请求头 |
headers_with_underscores_action: REJECT_REQUEST | 阻止利用下划线和连字号标准化差异的头部走私攻击 |
per_connection_buffer_limit_bytes: 32768 | 将每个连接的缓冲区内存限制为 32 KiB |
max_active_downstream_connections: 30000 | 下游总连接数的超载管理器限制 |
管理界面绑定到 127.0.0.1:9901 | 管理 API 只能从容器内部访问,其他容器或宿主机无法访问 |
在 127.0.0.1:9901 的 Envoy 管理界面会暴露 /config_dump,其中包含完整的渲染配置,包括所有 API 密钥、JWT 和明文的基本认证哈希。不要将 9901 端口暴露给其他容器或主机。
🌐 The Envoy admin interface at 127.0.0.1:9901 exposes /config_dump, which contains the fully rendered configuration including all API keys, JWTs, and the basic auth hash in plaintext. Never expose port 9901 to other containers or to the host.
网关只监听普通 HTTP,不进行 TLS 终止。对于公网部署,应在上游反向代理处终止 TLS——Docker 设置提供了 docker-compose.caddy.yml 和 docker-compose.nginx.yml 来实现这个功能。
🌐 The gateway listens on plain HTTP only and does not terminate TLS. For public deployments, terminate TLS at an upstream reverse proxy - the Docker setup ships docker-compose.caddy.yml and docker-compose.nginx.yml for this purpose.
自定义配置 #
🌐 Customizing the configuration
所有路由、过滤器和集群的更改都在 ./volumes/api/envoy/ 下的 YAML 文件中进行。
🌐 All routing, filter, and cluster changes are made in the YAML files under ./volumes/api/envoy/.
- 添加或修改路由。 编辑
lds.template.yaml。路由是有顺序的——将新路由放在通配/路由之前以确保匹配。保留每个路由的basic_auth: disabled用于 API 路由,如果某条路由需要绕过全局策略,请设置适当的 RBAC 覆盖。 - 添加一个新的上游服务。 在
cds.yaml中添加一个集群定义,包含服务的 DNS 名称和端口,然后在路由的cluster:字段中引用它。 - 添加一个新的环境变量。 模板中的占位符使用
${VAR_NAME}形式。如果你添加了一个占位符,需要同时更新docker-compose.yml中的api-gw服务(以便将变量传入容器)和docker-entrypoint.sh(用sed进行替换)。 - 正在应用更改。 Envoy 会从文件系统读取渲染后的
lds.yaml。配置更改需要重启容器,以便入口点重新渲染模板:
sh run.sh restart api-gw本指南不涵盖 Envoy 的路由、过滤器和集群参考。有关完整的配置内容,请参阅 Envoy 文档。
🌐 This guide does not cover Envoy's route, filter, and cluster reference. See the Envoy documentation for the full configuration surface.
管理界面 #
🌐 Admin interface
Envoy 在容器内部的 127.0.0.1:9901 上暴露了一个管理接口,有像 /ready、/clusters、/stats 和 /config_dump 这样的端点。标准的 envoyproxy/envoy 镜像是最小化的(没有 curl 或 wget),所以没有添加工具的话,管理员端点无法通过 docker exec 访问。
🌐 Envoy exposes an admin interface on 127.0.0.1:9901 inside the container, with endpoints like /ready, /clusters, /stats, and /config_dump. The standard envoyproxy/envoy image is minimal (no curl or wget), so the admin endpoints are not reachable via docker exec without adding tooling.
在调试时,你可以通过运行一个短期存在的 curl 容器来查询管理界面,该容器会加入与 supabase-envoy 相同的网络命名空间:
🌐 You can query the admin interface during debugging by running a short-lived curl container that joins the same network namespace as supabase-envoy:
docker run --rm --network container:supabase-envoy curlimages/curl http://127.0.0.1:9901/clustersdocker run --rm --network container:supabase-envoy curlimages/curl http://127.0.0.1:9901/statsdocker run --rm --network container:supabase-envoy curlimages/curl http://127.0.0.1:9901/config_dump/config_dump 包含明文的秘密信息。切勿将端口 9901 暴露给公共网络或不受信任的主机,也不要在未先移除秘密信息的情况下分享其输出。
故障排除 #
🌐 Troubleshooting
日志 #
🌐 Logs
Envoy 会将访问日志和过滤器输出写入 stdout。可以这样查看它们:
🌐 Envoy writes access logs and filter output to stdout. View them with:
docker compose logs api-gw访问日志格式是一个标准的组合日志,包含请求方法、原始路径、响应代码和发送的字节数。
🌐 The access log format is a standard combined log with the request method, original path, response code, and bytes sent.
常见问题 #
🌐 Common issues
401 Unauthorized在受保护的路由上。apikey头缺失或与任何配置的密钥不匹配。请确认头的值完全匹配你在.env文件中的ANON_KEY、SERVICE_ROLE_KEY、SUPABASE_PUBLISHABLE_KEY或SUPABASE_SECRET_KEY之一。注意,只有当新的密钥配置完全设置好时,才接受SUPABASE_PUBLISHABLE_KEY和SUPABASE_SECRET_KEY—— 详细信息见 新 API 密钥和非对称认证。403 Forbidden在/pg/。/pg/路由需要 service_role 密钥(SUPABASE_SECRET_KEY或旧版SERVICE_ROLE_KEY)。匿名和可发布密钥会被拒绝。403 Forbidden在/api/mcp或/mcp上。 这些路径默认是阻塞的。请参阅 启用 MCP 服务器访问。403 Forbidden在/realtime/v1/api/tenants或/realtime/v1/api/openapi上。 这些实时管理端点在网关处被设计性阻止,外部客户端即使有有效密钥也无法访问。SignatureDoesNotMatch在 S3 上发起对存储的请求。 确认docker-compose.yml中的存储服务配置包含了REQUEST_ALLOW_X_FORWARDED_PATH=true和STORAGE_PUBLIC_URL。存储会使用网关发送的X-Forwarded-Prefix头来重建原始请求路径,以进行 SigV4 验证。- 带下划线的
400 Bad Request头部。headers_with_underscores_action: REJECT_REQUEST已启用。一些客户端发送像X_Forwarded_For这样的带下划线的头部;这些会被拒绝。头部名称请使用连字符。
另请参阅 #
🌐 See also
- 新 API 密钥与非对称认证 - 关于不透明密钥和非对称 JWT 的背景
- 配置反向代理和 HTTPS - 在网关前使用 Caddy 或 Nginx
- Envoy 文档 - 过滤器、路由和集群参考
- 自托管 Supabase:Envoy 成为默认 API 网关(讨论 #48048)