> 原始 Markdown 孪生体（构建期从源 Markdown 生成）。渲染页：https://docs.gatellm.io/zh-CN/reference/configuration · 文档索引：https://docs.gatellm.io/zh-CN/llms.txt


# 环境变量配置参考

官方镜像已经内置了一份运行配置，你**不需要**编写或挂载任何配置文件——只用环境变量覆盖想改的项即可。`docker run -e`、docker-compose 的 `environment:` 段、k8s 的容器 env 都行。改完 `docker restart <容器名>` 生效。

本页列出镜像接受的**全部**环境变量、默认值与取值说明。默认值就是镜像内置值（写在 Dockerfile 的 `ENV` 里），未设即取该默认值。

业务配置（上游、模型、访问密钥、密钥组、负载均衡器、MCP、ACL、脚本、SSO）**不在**这里——它们存在数据库里、由控制台管理，改完即时生效，与环境变量无关。

## 先设这几个

零配置主路径下镜像自举控制台与管理员，下面这些只在有对应需求时才设：

- [`ENCRYPTION_KEY`](#storage-multi-instance) — 配上游 API key、签发访问密钥前必设，否则保存报 `encryption_key not set in config`；`openssl rand -hex 32` 生成
- [`CONSOLE_PASSWORD`](#console) — 想预设控制台管理员密码，而不是用日志里打印的随机密码（**仅首次启动生效**）
- [`LICENSE_KEY`](#image-level-vars) — 激活授权，解锁多实例/Redis/PostgreSQL 与更高的内存上限
- [`STORAGE_MODE`](#storage-multi-instance) 与 [`POSTGRES_URL`](#storage-multi-instance)、[`REDIS_URL`](#storage-multi-instance) — 多实例部署必设
- [`RESET_ADMIN`](#image-level-vars) — 忘记控制台密码时的一次性重置

## 取值与默认值的两条规则

**1. 空值 ≠ 未设 ≠ 代码默认值。** 把一个变量设成空串（`VAR=`）表示「关闭/无」，会被解释成 `None`。在镜像里你无法把变量「取消设置」来回到某个更深层的代码默认值——镜像的 `ENV` 就是默认值。例：`STREAM_IDLE_TIMEOUT_SECS` 的代码内置默认是 300 秒，但镜像发的是 600 秒；想要 300 就显式写 `STREAM_IDLE_TIMEOUT_SECS=300`，而不是把它留空。

**2. 列表型变量用逗号分隔的字符串。** `CORS_ORIGINS`、`CORS_HEADERS`、`CORS_METHODS`、`CORS_EXPOSE_HEADERS`、`FALLBACK_DNS_SERVERS`、`TRUSTED_PROXIES` 都接受 `a,b,c` 形式的字符串（会自动按逗号拆分、去空格、丢空项），也接受真数组形式。其余变量都是单值。

## 监听与请求限制 {#listen-and-request-limits}

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `HOST` | `0.0.0.0` | 监听地址；`127.0.0.1` 仅本机 |
| `PORT` | `7890` | 监听端口 |
| `MAX_REQUEST_SIZE_MB` | `50` | 最大请求体（MB），所有路由生效 |
| `MAX_RESPONSE_BODY_MB` | `25` | 上游响应体上限（MB，仅非流式） |
| `WORKER_THREADS` | `2` | async 运行时工作线程数。**2 是 0.25 vCPU 的安全下限，不是推荐值**——按可用 CPU 调大（如 4 vCPU 设 4） |
| `REQUEST_TIMEOUT_SECS` | 空（=不限） | 单次请求总时长上限；空表示不强制 |
| `GRACEFUL_SHUTDOWN_TIMEOUT_SECS` | 空 | 收到信号后优雅关停的等待上限 |
| `PRE_STOP_DELAY_SECS` | 空 | 关停前的延迟，留给负载均衡器摘流 |
| `DATA_PLANE_MAX_CONNECTIONS` | `0` | 数据面连接预算：代理路由允许的最大在途请求数，超限的新请求**在落盘前**立即 429 + Retry-After；控制台/健康检查/监控不受影响。`0` = 启动时按 fd（RLIMIT_NOFILE）上限自动推导，实际生效值打印在启动日志。fd 上限本身建议抬高，见 [docker 单机教程的 fd 一节](/zh-CN/quickstart/docker-single-node.md#raise-fd-limit) |
| `CONNECTION_BUDGET_RESERVE_FDS` | `0` | 自动推导连接预算时，为启动后才会惰性打开的连接（PG 各池、Redis、日志队列）预留的 fd 数；`0` = 自动枚举 |

## 存储与多实例 {#storage-multi-instance}

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `STORAGE_MODE` | `postgresql` | 持久化后端：`sqlite`（单机）/ `postgresql`（多实例共享）。镜像默认 postgresql + 空 `POSTGRES_URL`，零配置路径靠**未授权时 license 层强制改回 sqlite** 才跑通 |
| `SQLITE_PATH` | `/var/lib/protoflux/stats.sqlite` | SQLite 文件路径（单机模式） |
| `POSTGRES_URL` | 空 | PostgreSQL 连接串；`sslmode` 查询参数控制 TLS（`disable`/`require`/`verify-full`）。多实例必设 |
| `POSTGRES_POOL_SIZE` | `10` | PG 最大并发连接数 |
| `POSTGRES_CONSOLE_POOL_SIZE` | `8` | 控制台（登录/用户/审计）查询的独立连接池，与数据面隔离——数据面把主池打满时管理入口仍然可达 |
| `POSTGRES_WAIT_TIMEOUT_SECS` | `10` | 连接池获取超时；`0` 永久等待 |
| `POSTGRES_STATEMENT_TIMEOUT_SECS` | `30` | stats 池服务端单语句超时；`0` 无限 |
| `REDIS_URL` | 空 | Redis 连接串（多实例：会话/限流/日志广播/IP 封禁共享）；不设则全内存 |
| `REDIS_KEY_PREFIX` | `protoflux:` | Redis 键前缀，多套网关共用同一 Redis 时区分 |
| `ENCRYPTION_KEY` | 空 | 静态敏感数据（访问密钥、上游 API key）加密密钥；用 `openssl rand -hex 32` 生成 |

::: danger 关于 ENCRYPTION_KEY
- 只通过 `-e` / secret 注入，**不要**写进镜像层或 compose 明文
- **务必备份**这把密钥：丢失 = 已加密数据**永久不可读**
- 多实例部署时，所有实例**必须**用同一把密钥
- 空值时网关能启动，但一旦要读写加密密钥就会 fail-closes 报错——配上游 API key 前先设好
:::

## 控制台 {#console}

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `CONSOLE_ENABLED` | `true` | 是否启用控制台；`false` 则不建管理员、`/console` 不可达 |
| `CONSOLE_PASSWORD` | 空 | 控制台管理员 `protoflux` 的初始密码，**仅在首次启动（控制台用户表为空）时生效**，之后修改无效；空时首启自动生成强随机密码并打印一次到日志 |
| `CONSOLE_SECRET_KEY` | 空 | 控制台 API 的 Bearer token（长期有效，供脚本/CI 直调 `/console/api/*`，命中即管理员权限）。**空 = 不启用该认证方式，不会自动生成**（会自动生成的是 `CONSOLE_PASSWORD` 的管理员初始密码）。非空时须 ≥12 字符 |
| `CONSOLE_ALLOW_REMOTE` | `true` | 是否允许远程访问控制台。设 `false` 后 Docker Desktop/桥接网络下从宿主机访问会 403 |
| `CONSOLE_MAX_FAILURES` | `20` | 连续登录失败上限，达到后封禁来源 IP |
| `CONSOLE_BAN_DURATION` | `300` | 登录封禁时长（秒） |
| `CONSOLE_IP_BAN_ENABLED` | `true` | 是否启用 IP 封禁 |
| `CONSOLE_SESSION_AUTO_RENEW` | `true` | 会话是否自动续期 |

## 跨域与网络信任

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `CORS_ORIGINS` | 空 | 允许的跨域来源列表（逗号分隔）；空 = 浏览器 CORS 保持关闭 |
| `CORS_HEADERS` | 空 | 允许的请求头列表（逗号分隔）。始终与网关自带的 `Content-Type`、`Authorization`、`z-client`、`x-gatellm-client` 并集 |
| `CORS_METHODS` | 空 | 允许的方法列表（逗号分隔） |
| `CORS_EXPOSE_HEADERS` | 空 | 允许前端读取的响应头列表（逗号分隔）。网关自带的 `z-gateway` 与 `z-request-id` 无论此列表如何都会跨源暴露 |
| `CORS_MAX_AGE` | `7200` | 预检结果缓存秒数 |
| `CORS_CREDENTIALS` | `false` | 是否允许携带凭证 |
| `TRUSTED_PROXIES` | 空 | 信任的代理 IP/CIDR 列表（逗号分隔，如 `10.0.0.0/8,192.168.0.0/16`）；设了才会信任 `X-Forwarded-For` 解析客户端真实 IP |
| `IP_RATE_LIMIT_RPM` | 空（=不限） | 按 IP 的全局每分钟请求上限；空表示不按 IP 限流 |
| `METRICS_AUTH_TOKEN` | 空 | `/metrics` 端点的鉴权 token；设了后需 `Authorization: Bearer <token>` |

## 上游连接重试与路由亲和 {#upstream-retry-affinity}

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `UPSTREAM_IDLE_CONNECTIONS` | `16` | 每上游主机的连接池大小 |
| `UPSTREAM_SEND_TIMEOUT_SECS` | `180` | 发请求体 + 等响应头（TTFB）超时 |
| `UPSTREAM_READ_TIMEOUT_SECS` | `600` | 单次响应块读超时（每块重置）；需大于 `STREAM_IDLE_TIMEOUT_SECS` |
| `UPSTREAM_USER_AGENT` | `Protoflux` | 对上游默认用的 User-Agent |
| `GATEWAY_IDENTITY` | *(OEM 品牌)* | `z-gateway` 响应头中的品牌名（`<brand>/<version>`）；默认取镜像的 OEM 品牌（缺省 `Protoflux`）；空 = 不发该头 |
| `FALLBACK_DNS_SERVERS` | `1.1.1.1,8.8.8.8,119.29.29.29,223.5.5.5` | 未单独配 DNS 的上游的回退 DNS 池（逗号分隔）；空 `=` 禁用回退 |
| `MAX_UPSTREAM_RETRIES` | `3` | 单请求最大上游重试次数（首字节前） |
| `MAX_KEY_ROTATIONS` | `0` | 单请求最大密钥轮换次数（同上游多 key 时）；`0` = 不限（轮换到所有可用 key） |
| `LB_AFFINITY_TTL_SECS` | `300` | 负载均衡亲和绑定存活秒数；`0` = 永久绑定 |
| `LOAD_WINDOW_SECS` | `60` | 计算上游负载的窗口秒数 |
| `KEY_BINDING_TTL_SECS` | `1800` | API key 到上游的自动绑定存活秒数 |
| `UPSTREAM_BAD_LINK_BUDGET` | 空（=自动） | 单上游的**坏死链接预算**：该上游上超过阈值仍未收到首响应的在途请求，最多允许堆积多少个。达到预算后该上游被排除出候选；全部候选都饱和时请求立即 503 + Retry-After，不再排队拖垮健康流量。空 = 自动取初始通行证数的一半；`0` = 禁用。纯实时在途计数——请求一结束（任何方式）立即释放名额，上游恢复即满血可用，无任何失败记忆 |
| `UPSTREAM_BAD_LINK_THRESHOLD_SECS` | `60` | 坏死链接判定阈值（秒）：在途请求超过该时长仍未收到任何首响应，即计为该上游的一个坏死链接 |
| `UPSTREAM_DISPATCH_DEADLINE_SECS` | `300` | 单个请求**整个出站尝试链**（重试 + 密钥轮换 + 连接池恢复）的总时长预算。超时即终止尝试链并返回 504 + Retry-After——挂死请求占用的通行证在有限时间内必然释放。`0` = 禁用（仅作逃生门） |

## 流式 {#streaming}

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `STREAMING_KEEPALIVE_SECONDS` | `15` | SSE `:keep-alive` 注释间隔；需短于反代空闲超时（Nginx 默认 60s，故 15s 安全） |
| `STREAMING_BOOTSTRAP_RETRIES` | `1` | 首字节前重试次数；首块发出后不再重试 |
| `STREAMING_LOG_TIMEOUT_SECS` | `3600` | SSE 流日志后台任务超时（释放"sender 丢失"的僵尸任务） |
| `STREAM_IDLE_TIMEOUT_SECS` | `600` | 两个数据块之间的最大空闲间隔；超过即断流 |
| `MAX_STREAM_DURATION_SECS` | `3600` | 单条 SSE 流的总时长硬上限 |

## 日志留存与磁盘 {#log-retention}

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `LOG_LEVEL` | `info` | 日志级别（`error`/`warn`/`info`/`debug`/`trace`） |
| `LOG_FORMAT` | `json` | 日志格式（`json`/`plain`） |
| `LOG_BODY_TO_TERMINAL` | `false` | 是否把日志体也打到标准输出 |
| `LOG_PERSIST_REQUEST_LOGS` | `false` | 是否把请求日志持久化到磁盘/数据库 |
| `LOG_RETENTION_DAYS` | `7` | 请求日志保留天数；`0` = 禁用自动清理 |
| `OMS_RETENTION_DAYS` | `30` | OpenAI 消息存储保留天数；`0` = 禁用自动清理 |
| `OTEL_ENDPOINT` | 空 | OpenTelemetry 导出端点；空表示不上报 |
| `OTEL_SERVICE_NAME` | `protoflux` | 上报给 OTEL 的服务名 |
| `SENTRY_DSN` | 空 | **后端** Sentry 项目 DSN（Rust panic/error）；空表示后端不上报（默认编译进二进制）。镜像构建期通过 `--build-arg SENTRY_DSN` 注入（CI 用 `secrets.SENTRY_DSN`），运行时 `-e` 可覆盖 |
| `SENTRY_FRONTEND_DSN` | 空 | **前端** Sentry 项目 DSN（浏览器 JS error）；空则 `client-config` 回落到 `SENTRY_DSN`（前后端同一 project），设为不同 project 以隔离前端噪音 |
| `SENTRY_ENVIRONMENT` | `production` | Sentry 环境标签 |
| `SENTRY_RELEASE` | 空 | Sentry 版本标识；空时运行时默认 `protoflux@<版本号>` |
| `SENTRY_TRACES_SAMPLE_RATE` | `0` | Sentry 性能采样率（0.0–1.0）；`0` = 仅错误/崩溃 |
| `LOG_QUEUE_DIR` | 空 | 日志队列磁盘目录；空 = 回退到系统临时目录。设了 `REDIS_URL` 时此项被忽略（多实例走 Redis Pub/Sub） |
| `LOG_QUEUE_SEGMENT_SIZE_MB` | `64` | 日志队列单段文件大小（MB） |
| `LOG_QUEUE_MAX_DISK_SIZE_MB` | `1024` | 日志队列磁盘总量上限（MB） |
| `LOG_STREAM_BODY_MAX_DISK_MB` | `1024` | SSE 流日志体磁盘溢出上限（MB） |
| `LOG_REQUEST_BODY_MAX_DISK_MB` | `1024` | 请求体磁盘存储上限（MB） |
| `LOG_ACCUMULATOR_MAX_DISK_MB` | `256` | 日志累加器磁盘上限（MB） |
| `LOG_STREAM_BODY_BATCH_SIZE_KB` | `64` | SSE 流日志体落盘批大小（KB） |
| `LOG_STREAM_BODY_LINGER_MS` | `5` | SSE 流日志体逗留毫秒 |
| `LOG_STREAM_BODY_CHANNEL_CAPACITY_CHUNKS` | `2048` | SSE 流日志体通道容量（块数） |

## 内存准入与溢写 {#memory-admission-spill}

网关在内存压力下用准入控制 + 磁盘溢写保护进程不被打爆。多数场景默认值即可，仅在突发负载或容器 OOM 时调。

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `MEMORY_SOFT_LIMIT_MB` | `0` | 软内存上限（MB）；`0` = 不设软上限（由容器 cgroup 兜底） |
| `MEMORY_QUEUE_TIMEOUT_SECS` | `300` | 内存队列项等待准入的超时 |
| `MEMORY_QUEUE_MAX_DEPTH` | 空（=自动） | 慢路径**等位队列**深度上限（计的是等待处理通行证的请求数，不是并发处理数，四态）：空 = 自动取初始通行证数 × 4（有界安全默认）；`-1` = 不限（显式逃生口）；`0` = 零等待（不允许排队，拿不到通行证的请求立即被拒）；`n` = 上限 n。超限的新请求立即 429 + Retry-After（reason `queue_full`），不等 `MEMORY_QUEUE_TIMEOUT_SECS`。并发处理能力由处理通行证决定，不受此项限制 |
| `SPILL_WRITE_CONCURRENCY` | `32` | 磁盘溢写的并发写入数 |
| `SPILL_WRITE_STALL_TIMEOUT_SECS` | `10` | 入口落盘写停顿判定超时（秒）；槽位等待或写入零进展达到该值即以 503 + Retry-After 拒绝（挂死卷防御），范围 1–300 |
| `MEMORY_HOLD_SECS` | `5` | 内存项的持有秒数 |
| `MEMORY_RATE_ESCALATE_MB` | `40` | 内存速率升级阈值（MB） |
| `MEMORY_ADMISSION_ENABLED` | `true` | 是否启用准入控制。前馈准入做逐请求结构性决策（admit / 落盘排队），堵死「一批请求都读到陈旧 Normal RSS 后走零开销直通」的突发盲区 |
| `MEMORY_FORCE_SPILL_BODY_MB` | `8` | 强制溢写体阈值（MB）：body 超此值无条件落盘——nginx `client_body_buffer_size` 的对应物，body 常驻内存与请求大小上限解耦。`0` = 不强制 |

> 注：`MEMORY_ADMISSION_HANDLER_PERMIT`（单 handler 每槽 MB 预算）已移除——并发不再由静态预算派生，而是由预测式 `ConcurrencyController`（`target = budget / 实测每请求成本`）驱动。如需手动钉死并发，用 TOML `memory_admission_handler_permits`（>0 禁用 controller）。

## 脚本沙箱

脚本沙箱限制说明见 [脚本运行限制与配置](/zh-CN/reference/scripting-config.md)。

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `SCRIPT_MAX_OPERATIONS` | `2000` | 每次 QuickJS 脚本执行的最大操作数 |
| `SCRIPT_MEMORY_LIMIT_MB` | `64` | 解释器堆上限（MB） |
| `SCRIPT_LAZY_BODY` | `true` | 是否对请求/响应体做懒投影 |

## 镜像级变量 {#image-level-vars}

这几个由进程直接读取，不在配置字段表里。

| 变量 | 默认值 | 作用与取值 |
|------|--------|------|
| `LICENSE_KEY` | 空 | 授权密钥（Ed25519）。空 = 未授权：内存上限 512MB、禁用 Redis/PostgreSQL。激活后解锁多实例与更高内存上限 |
| `RESET_ADMIN` | 空 | 忘记控制台密码时的一次性重置：设成新密码后重启，把管理员 `protoflux` 密码改成该值。**只生效一次**（进程写一次性标记），详见 [Docker 单机跑通](/zh-CN/quickstart/docker-single-node.md) |
| `TZ` | `UTC` | 容器时区 |
| `MALLOC_CONF` | `background_thread:true,narenas:1,...` | jemalloc 配置（后台线程、arena 数、脏页回收、profiling） |
| `DEV` | 空 | 仅存在性生效（设了即开，值无关）。把 `/console/*` 代理到 Vite 开发服务器——仅本地开发用 |

## 仅配置文件可配的字段 {#config-file-only-fields}

下面几个字段**只能经 `server.toml` 配置**，官方镜像未把它们暴露为环境变量，因此不在上面的环境变量表里。用配置文件部署（非镜像环境变量路径）时可设：

| 字段（TOML） | 默认值 | 作用 |
|------|--------|------|
| `server.max_global_concurrency` | `1000` | 全部代理路由的全局并发请求上限，达上限返回 503 `overloaded_error`；设 `0` 关闭 |
| `streaming.max_concurrent_streams` | `200` | SSE 流式请求的全局并发上限，达上限返回 503 |
| `server.script_pool_size` | `4` | 脚本并发执行槽数，每槽堆内存受 `SCRIPT_MEMORY_LIMIT_MB` 约束 |
| `sso_credentials.refresh_enabled` | `true` | 上游 SSO 凭证后台续期任务总开关；每个扫描周期热读取，可不停机关停 |
| `sso_credentials.refresh_interval_secs` | `60` | 续期扫描间隔秒数（下限 5） |
| `sso_credentials.refresh_lead_secs` | `300` | 提前续期窗口：在此秒数内到期的凭证立即续期 |
| `sso_credentials.refresh_max_parallel` | `4` | 每次扫描的全局并发续期调用上限（单个凭证并发恒为 1，不可配） |

这几个并发/槽位上限**不是固定值**；503 的来源见 [错误码 → 503 的来源](/zh-CN/reference/error-codes.md#source-of-503)。

## 各分区的配置参考页

- **日志**：[日志与日志体存储](/zh-CN/reference/logs-and-body-storage.md) — 日志体捕获、SSE 磁盘溢出
- **脚本**：[脚本运行限制与配置](/zh-CN/reference/scripting-config.md) — 脚本沙箱限制、错误模式、脚本测试入口
- **安全**：[审计与安全配置](/zh-CN/reference/audit-and-security-config.md) — IP 封禁、密码策略、安全响应头
- **定价**：[定价与计费字段](/zh-CN/reference/pricing-and-billing-config.md) — 定价基线、价格快照语义、月度账单字段
- **MCP**：[MCP 配置](/zh-CN/reference/mcp-config.md) — MCP 配置在控制台/数据库侧
- **负载均衡**：[负载均衡字段](/zh-CN/reference/load-balancing-fields.md) — 重试字段
- **SSO 企业登录**：[配置 SSO 企业登录](/zh-CN/howto/configure-sso.md) — 控制台配置；不需要环境变量

## 常见问题

**Q：想改一个不在本页的更深的参数怎么办？**
本页是镜像开放的全部环境变量。另有少数字段只能经 `server.toml` 配置（见[仅配置文件可配的字段](#config-file-only-fields)）；其余更深的调优项未开放，有需要请联系支持。

**Q：怎么改配置后生效？**
改完环境变量后 `docker restart <容器名>` 即可。控制台侧的业务配置（上游、模型、密钥等）改完即时生效。

**Q：启动报配置错误怎么办？**
镜像启动时会自检配置，错误会在日志里给出具体字段与原因。按提示改对应环境变量，再 `docker restart <容器名>` 即可。

**下一步**：[日志与日志体存储](/zh-CN/reference/logs-and-body-storage.md) / [脚本运行限制与配置](/zh-CN/reference/scripting-config.md) / [审计与安全配置](/zh-CN/reference/audit-and-security-config.md) / [定价与计费字段](/zh-CN/reference/pricing-and-billing-config.md) / [MCP 配置](/zh-CN/reference/mcp-config.md) / [负载均衡字段](/zh-CN/reference/load-balancing-fields.md) 看具体配置参考。
