Docs

API7 Gateway 3.9.17

max_req_body_bytes and max_resp_body_bytes are now validated as integers of at least 1 in the ClickHouse Logger, Elasticsearch Logger, File Logger, Loggly, Loki Logger, SkyWalking Logger

Release Date: 2026-07-28

Upgrade Notes

Upgrade note — logger body size limits are validated more strictly

max_req_body_bytes and max_resp_body_bytes are now validated as integers of at least 1 in the ClickHouse Logger, Elasticsearch Logger, File Logger, Loggly, Loki Logger, SkyWalking Logger, Alibaba Cloud Logging (SLS), and Syslog plugins. Values that earlier versions accepted — 0, negative numbers, and quoted numbers such as "1024" — are now rejected with HTTP 400.

After the upgrade, existing routes that carry such a value are listed as errors in the gateway group's compatibility report and are not published to the Data Plane; other routes are unaffected. Before upgrading, review these plugins' configurations for max_req_body_bytes or max_resp_body_bytes set to 0, a negative number, or a quoted number, and replace them with a positive integer, or remove the field to fall back to the default of 524288.

Upgrade note — structural Prometheus metric labels can no longer be disabled

The Prometheus (opens in Plugin Hub docs) plugin metadata now rejects disabled_labels entries that would remove a label the metric is built around — for example type on latency, or code on status. Earlier versions accepted these and produced metric series in which distinct measurements collapsed into one.

If your Prometheus plugin metadata disables one of these labels, any update to it after the upgrade is rejected with HTTP 400. Remove the structural entries from disabled_labels before upgrading. Non-structural labels such as route, service, and consumer can still be disabled.

Upgrade note — OpenTelemetry metadata and content moderation deny_code are validated more strictly

Two more schemas now reject values that earlier versions accepted:

After the upgrade, an existing configuration that carries such a value is reported as an error in the gateway group's compatibility report, and any update to it is rejected with HTTP 400. Gateway instances stay Healthy and traffic is not affected. Before upgrading, review your OpenTelemetry plugin metadata and any AI Aliyun Content Moderation configuration, and replace or remove the offending values.

Features

Plugins

Data Plane

  • Added nginx_config.stream.real_ip_from, which lists the addresses trusted to send a PROXY protocol header on stream (TCP/UDP) ports. On a connection from a trusted address, the client address is taken from the PROXY protocol header instead of the directly connected peer, so stream logs and address-based plugins see the real client rather than the load balancer in front of the gateway. It is empty by default and only takes effect on ports that accept the PROXY protocol.
  • Upgraded the health check engine. Check targets are now reconciled incrementally instead of being destroyed and rebuilt, so scaling an upstream up or down no longer leaves a window in which no node is being checked, and the accumulated health state and failure counters of unchanged nodes are preserved instead of being reset.
  • The Apisix-Plugins debug response header now lists the plugins that actually ran, in execution order and annotated with the phase each ran in (for example limit-count#access, response-rewrite#header_filter), instead of an unordered list of configured plugins.

Control Plane

  • The /api/fe-config endpoint is now served by the Control Plane binary and driven by console.* configuration keys, so the Dashboard's hybrid mode, browser error reporting (Sentry), and a custom sidebar group of external links can be configured through the Control Plane configuration or the Helm chart. Menu items that do not have a name and an absolute http/https URL are dropped instead of being served to the Dashboard.
  • Service conflict checks now take route methods into account. Routes on the same host and path that serve disjoint method sets (for example GET /foo and POST /foo) are no longer reported as conflicting; an identical method scope is reported as duplicate, and a partially shared scope is reported as overlapping. A route without methods still matches all methods.
  • Added an opt-in SSRF guard for outbound connections to user-configured endpoints. When security.ssrf_protection.enable is set, the Control Plane refuses to connect to loopback, private, link-local, and carrier-grade NAT addresses — including host names that resolve to them — which prevents features such as service registries and SMTP settings from being used to probe internal services or cloud metadata endpoints.

Console (Dashboard)

  • The conflict dialog for services and routes now shows a Methods column, so it is clear on which methods two routes collide. Routes without methods are shown as All.

Fixes

Plugins

  • AI Proxy (opens in Plugin Hub docs), AI Proxy Multi (opens in Plugin Hub docs), and AI Request Rewrite (opens in Plugin Hub docs)
    • Fixed issue: The requests the gateway sent to the LLM provider carried the client's own headers, including Cookie, Authorization, and arbitrary custom headers, exposing end-user credentials to the upstream provider. The gateway now sends only the headers the plugin itself sets.
    • Fixed issue: On an error response from the LLM, $apisix_upstream_response_time and $llm_time_to_first_token were logged in seconds (for example 0.240) or as 0, while successful responses were logged in milliseconds. Error-path values are now reported in milliseconds, consistent with the success path.
  • AI AWS Content Moderation (opens in Plugin Hub docs) and AI Aliyun Content Moderation (opens in Plugin Hub docs)
    • Fixed issue: deny_code accepted arbitrary numbers. It is now validated as an integer HTTP status code in the 200–599 range (default 200), so an out-of-range value is rejected at configuration time. For ai-aws-content-moderation the Control Plane already applied this validation, so this release brings the Data Plane in line; for ai-aliyun-content-moderation it now applies on both sides.
  • OpenID Connect (opens in Plugin Hub docs)
    • Fixed issue: Delivering an authorization callback whose state no longer matched the session — for example after starting a second login flow in the same browser — returned HTTP 500. The gateway now redirects to the originally requested page instead.
    • Fixed issue: An empty JSON array in the identity provider's user info (for example "roles": []) was re-encoded as an empty object ({}) in the X-Userinfo header on requests served from an existing session, so upstream services that parse the field as an array failed. Empty arrays now stay arrays.
  • wolf-rbac
    • Fixed issue: When the authentication service returned success without userInfo, the client's own X-UserId, X-Username, and X-Nickname headers were forwarded to the upstream service, letting a caller present any identity it chose. These headers are now always cleared before the request is proxied.
  • Limit Count (opens in Plugin Hub docs)
    • Fixed issue: With window_type: sliding and delayed synchronization, the remaining quota was reported without the sliding-window weighting, so the gateway admitted noticeably more requests than configured around window boundaries. The remaining count is now window-weighted.
  • Limit Conn (opens in Plugin Hub docs) and Limit Req (opens in Plugin Hub docs)
    • Fixed issue: Redis connections were not returned to the keepalive pool, so every request opened a new Redis connection and the configured keepalive settings had no effect. Connections are now pooled and reused.
  • Prometheus (opens in Plugin Hub docs)
    • Fixed issue: When the shared dictionary used for metrics filled up, the gateway could enter a loop that held a worker at 100% CPU and did not recover after traffic stopped. A full dictionary now degrades gracefully, logging that reported metric data may be incomplete.
  • OpenTelemetry (opens in Plugin Hub docs)
    • Fixed issue: The plugin metadata schema accepted non-scalar values for resource attributes and collector.request_headers, which were then silently dropped at runtime. Such values are now rejected at configuration time.

Data Plane

  • Fixed issue: With the least_conn load balancer, adding or removing an upstream node discarded the tracked connection counts, so the balancer degraded to round robin and sent new requests to nodes that were already holding long-lived connections. Load state is now preserved across upstream scaling.
  • Fixed issue: When a plugin field referenced a secret that could not be resolved — an unset environment variable, or an error from the secret manager — the failure was silent and the unresolved reference was used as the literal value. The gateway now logs an error identifying the reference and the field it appears in.
  • Fixed issue: The gateway read its host name by executing /bin/hostname, so on images that do not ship that binary it reported no host name and gateway instances appeared without a host name in the Dashboard. The host name is now read through a system call.
  • Fixed issue: Entries in nginx_config.envs whose values contained spaces, quotes, or backslashes produced an invalid NGINX configuration and the gateway failed to start. These values are now quoted and escaped correctly.
  • Fixed issue: Stopping and immediately restarting the gateway through the CLI could fail because the previous instance had not finished exiting. The CLI now waits for it to stop before starting the new one.
  • Fixed issue: After a gateway container was killed rather than shut down cleanly, leftover worker event sockets could prevent the next start from binding. They are now removed during startup.
  • Fixed issue: On arm64, a dependency pulled a second copy of the JSON library into the gateway's module path, shadowing the bundled one and encoding empty arrays as invalid JSON. The redundant dependency is removed, so the bundled library is always used.

Control Plane

  • Fixed issue: Two concurrent PATCH requests against the same resource could each read the resource before the other wrote it, so only the last write survived even though both requests reported success. PATCH requests on the same resource are now serialized.
  • Fixed issue: Pooled database connections were reused indefinitely, so after a database failover the Control Plane could keep using connections pinned to a demoted, now read-only primary. Connection lifetime is now bounded at one hour by default and can be tuned with database.max_lifetime.
  • Fixed issue: Importing a license whose certificate chain had expired reported license certificate comes from an invalid issuer, pointing at the signing authority rather than at the expiry. An expired or not-yet-effective certificate chain now reports a dedicated message that includes the relevant timestamp. Certificates from a genuinely unknown authority still report an invalid issuer.
  • Fixed issue: The current data plane core count included instances that had stopped sending heartbeats, which stay in LostConnection for up to two hours before being marked offline, so the Dashboard's core usage remained inflated long after a data plane was scaled down or crashed. Only connected instances in standard running mode are counted now. License accounting, which is computed from heartbeat usage, is unchanged.

Console (Dashboard)

  • Fixed issue: The route paths and service hosts forms allowed adding entries beyond the schema limits of 64 paths and 32 hosts. Creating such a resource failed with a raw server error, and editing one could store a configuration that adc validate and adc sync later rejected. The Add control is now hidden once a list reaches its limit, while lists that are already over the limit still show every entry so the excess can be removed.