API7 Gateway Release Notes
3.10.4
Release Date: 2026-07-27
Upgrade Notes
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.
The Prometheus 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.
Features
Plugins
- OpenID Connect
- Added
set_raw_id_token_header. When enabled, the raw ID token issued by the identity provider is forwarded to the upstream service in theX-Raw-ID-Tokenheader, so the upstream can verify the token signature itself instead of trusting the gateway's decoded claims.
- Added
- Proxy Rewrite
headers.setandheaders.addnow accept an array of values for a single header name, which is sent upstream as separate header lines rather than one comma-joined line. This matters for upstream services that read only the first occurrence of a header name, or that parse repeated headers differently from a single comma-joined value.
- Kafka Logger
- Added
tlsfor connecting to Kafka brokers over TLS, withtls.verifycontrolling broker certificate verification. Previously the plugin could only connect in plaintext, so a broker listening on a TLS port received no logs at all.
- Added
- Rate-limiting and cache plugins
- Added Redis connection keepalive settings (
redis_keepalive_timeoutandredis_keepalive_pool) to the Redis and Redis Cluster policies in Limit Conn, Limit Req, and AI Cache, so operators can tune idle timeout and connection pool size.
- Added Redis connection keepalive settings (
- Body-buffering plugins
- Added
max_req_body_sizeandmax_resp_body_sizeto bound how much of a request or response body a plugin reads into memory, defaulting to 67108864 bytes (64 MiB). A request body larger than the limit is rejected, and a response body is truncated at the limit — except in Proxy Cache, where an oversized response is streamed through without being cached — so one large body cannot exhaust worker memory. Available in AI Proxy, AI Proxy Multi, AI Request Rewrite, the AI prompt plugins, Request Validation, OAS Validator, Body Transformer, Response Rewrite, Proxy Cache, gRPC Transcode, SOAP, and other plugins that buffer bodies.
- Added
- Logger plugins
- Added
max_req_body_bytesandmax_resp_body_bytesto the remaining logger schemas — ClickHouse Logger, Elasticsearch Logger, File Logger, Loggly, Loki Logger, SkyWalking Logger, Alibaba Cloud Logging (SLS), and Syslog — so the body size limits are validated at configuration time and shown in the Dashboard (see Upgrade Notes).
- Added
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-Pluginsdebug response header now lists the plugins that actually ran, in execution order and annotated with the phase each ran in (for examplelimit-count#access,response-rewrite#header_filter), instead of an unordered list of configured plugins.
Control Plane
- The
/api/fe-configendpoint is now served by the Control Plane binary and driven byconsole.*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 absolutehttp/httpsURL 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 /fooandPOST /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 withoutmethodsstill matches all methods. - Added an opt-in SSRF guard for outbound connections to user-configured endpoints. When
security.ssrf_protection.enableis 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 Control Plane version is now shown in the top bar next to the organization menu and can be copied with a single click, instead of only being available at the bottom of the organization dropdown.
- Resources synchronized by ADC (which carry the
managed-by=adclabel) are now marked with a Managed by ADC tag and a banner on their detail pages, and edit, delete, and create actions on them warn that the next ADC sync overwrites manual changes and removes manually added children. Resources under a managed service or consumer are covered even when the child itself carries no label. The warnings are advisory and do not block the operation. - The conflict dialog for services and routes now shows a Methods column, so it is clear on which methods two routes collide. Routes without
methodsare shown as All.
Fixes
Plugins
- AI Proxy, AI Proxy Multi, and AI Request Rewrite
- 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_timeand$llm_time_to_first_tokenwere logged in seconds (for example0.240) or as0, while successful responses were logged in milliseconds. Error-path values are now reported in milliseconds, consistent with the success path.
- Fixed issue: The requests the gateway sent to the LLM provider carried the client's own headers, including
- OpenID Connect
- 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 theX-Userinfoheader on requests served from an existing session, so upstream services that parse the field as an array failed. Empty arrays now stay arrays.
- 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
- wolf-rbac
- Fixed issue: When the authentication service returned success without
userInfo, the client's ownX-UserId,X-Username, andX-Nicknameheaders 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.
- Fixed issue: When the authentication service returned success without
- Limit Count
- Fixed issue: With
window_type: slidingand 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.
- Fixed issue: With
- Limit Conn and Limit Req
- 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
- 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.
Data Plane
- Fixed issue: With the
least_connload 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.envswhose 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:
PATCH /apisix/admin/routesapplied the patch without validating the result against the route schema, so a patch could persist a route that exceeded a schema limit — for example more than 64 paths — which laterPUTrequests andadc syncthen rejected. The merged result is now validated before it is stored. - 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 validateandadc synclater 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.
3.10.3
Release Date: 2026-07-14
Upgrade Notes
Several Data Plane shared memory (lua_shared_dict) defaults were raised, so a 3.10.3 gateway reserves about 365 MiB more shared memory at startup than 3.10.2 with the default configuration:
| Shared dictionary | 3.10.2 default | 3.10.3 default |
|---|---|---|
prometheus-metrics (advanced metrics) | 15 MiB | 128 MiB |
kubernetes, nacos, nacos-stream, consul (service discovery) | 20 MiB each | 64 MiB each |
tracing_buffer (SkyWalking) | 10 MiB | 32 MiB |
api-calls-for-portal | 10 MiB | 64 MiB |
These dictionaries are allocated when the gateway starts, whether or not the corresponding feature is in use, so the increase applies to every 3.10.3 gateway. Before upgrading, raise the gateway container's memory requests and limits (in Kubernetes, also review node memory-pressure and eviction thresholds) so the gateway is not OOM-killed. If you do not use a given feature — for example a service discovery type you have not configured — you can lower its dictionary back toward the previous value through the gateway configuration, or through the shared-dict values in the Helm chart.
Built-in Dashboard users are now temporarily locked out after repeated failed password attempts. The default policy is enabled, bans the user and source IP after 5 consecutive failures, and lasts 15 minutes. Administrators can tune or disable it through the new login failure restriction system setting. New passwords and passwords changed after upgrade must also be at least 12 characters and still satisfy the existing complexity requirements. Existing passwords are not revalidated at sign-in.
If a built-in user enables two-factor authentication, HTTP Basic Auth for that user is rejected because Basic Auth cannot provide a second factor. Use access tokens for programmatic integrations instead; token authentication is sent with the X-API-KEY header and does not require a 2FA verification code.
The gateway now uses apisix.trusted_addresses to decide whether client-supplied X-Forwarded-* and RFC 7239 Forwarded headers are trusted. When trusted_addresses is not configured, or when the request comes from an untrusted address, the gateway overwrites X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-Port with gateway-observed values and clears the Forwarded header before proxying upstream. If an upstream application depends on the original forwarded protocol, host, or port sent by a trusted load balancer or reverse proxy, configure that proxy's IP or CIDR in trusted_addresses.
The openid-connect plugin no longer sets refresh_session_interval to 900 seconds by default. Periodic silent re-authentication now happens only when refresh_session_interval is explicitly configured. If your deployment relied on the previous 900-second refresh behavior, set refresh_session_interval: 900 before or during the upgrade.
For SQL Server-backed deployments, the Control Plane now creates and prepares the database before other components connect, and enables READ_COMMITTED_SNAPSHOT to prevent gateway configuration reads from blocking behind writes. On an existing SQL Server database where this setting is still disabled, the first startup applies the database-level change with ROLLBACK IMMEDIATE; in-flight database transactions and sessions can be disconnected once, after which connection pools reconnect and later starts are no-ops.
The Control Plane now encrypts additional credential-bearing plugin fields at rest. Because API7 EE upgrades the Control Plane before the Data Plane, during the upgrade window a 3.10.3 Control Plane encrypts these fields while an older 3.10.2 Data Plane cannot decrypt them, which can cause the affected plugins to fail until the Data Plane is also upgraded.
The newly encrypted fields, by plugin, are:
- ai-cache:
semantic.embedding.openai.api_key,semantic.embedding.azure_openai.api_key
If you use any of these plugins with the listed fields, upgrade the Data Plane to 3.10.3 promptly after the Control Plane, and avoid editing those plugins until both sides are on 3.10.3.
Features
Plugins
- AI Cache
- Added a semantic (L2) cache layer that matches prompts by embedding similarity through RediSearch, in addition to exact-match caching. Streaming LLM responses can now be cached and replayed instead of being bypassed. New Prometheus metrics report cache hits, misses, bypasses, and embedding latency.
- AI Aliyun Content Moderation
- Added
request_check_rolesto choose which request roles are moderated (user,tool, and/orsystem).userandtoolcontent followrequest_check_mode(lastorall, defaultlast), whilesystemcontent is checked on every request when selected. Long content is now split into chunks in linear time and is multi-byte (UTF-8) safe.
- Added
- AI AWS Content Moderation
- Request moderation now runs after AI protocol detection and checks the decoded prompt content that the upstream LLM sees, rather than the raw HTTP JSON envelope. Deny responses are returned in a provider-compatible format, with configurable
check_request,deny_code, anddeny_message.
- Request moderation now runs after AI protocol detection and checks the decoded prompt content that the upstream LLM sees, rather than the raw HTTP JSON envelope. Deny responses are returned in a provider-compatible format, with configurable
- IP Restriction
- Added a configurable
response_code(either403or404, default403) returned when a request is blocked, so operators can respond with404to avoid revealing whether a resource exists.
- Added a configurable
- File Logger
- The log file
pathcan now be set once in the plugin metadata and shared across routes, instead of being required in every route's plugin configuration. Apathset in the plugin configuration still takes precedence over the metadata value.
- The log file
- Logger plugins
- Added
max_req_body_bytesandmax_resp_body_bytesto the schemas of HTTP Logger, RocketMQ Logger, TCP Logger, Tencent Cloud CLS, and UDP Logger, so the request/response body size limits (default 524288 bytes) are validated at configuration time and shown in the Dashboard.
- Added
- Rate-limiting plugins
- Added Redis connection keepalive settings (
redis_keepalive_timeoutandredis_keepalive_pool) for the Redis and Redis Cluster policies in Limit Count, Limit Count Advanced, GraphQL Limit Count, and AI Rate Limiting, so operators can tune idle timeout and connection pool size.
- Added Redis connection keepalive settings (
Data Plane
- Added
apisix.trusted_addresses, which controls whether the gateway trusts client-suppliedX-Forwarded-*andForwardedheaders based on the resolved client address. - Added
apisix.match_uri_encoded_slash. When enabled, an encoded slash (%2F) stays encoded during route matching so it can be treated as part of a path parameter instead of a path separator. - In standalone YAML mode, environment-variable placeholders are now resolved before YAML parsing. Unquoted placeholders can become native booleans or numbers, while quoted placeholders stay strings, preserving large IDs and token values exactly.
Control Plane
- Added TOTP two-factor authentication for built-in Dashboard users. Users can enroll, enable, disable, and recover 2FA from their account settings, and administrators can reset a user's 2FA state.
- Added login failure restriction for built-in users. Repeated failed logins temporarily lock the user and source IP, emit an audit event, and return a clear lockout message.
- Added the Dashboard UI for two-factor authentication: account setup with QR code and recovery codes, an OTP step during login, and an administrator action to reset a user's 2FA state.
- Updated password forms and generated passwords to use the new 12-character minimum length.
Developer Portal
- Added an option to require two-factor authentication. When 2FA is enabled and
twoFactor.requiredis set, developers must complete 2FA enrollment before they can access protected pages, and the requirement is enforced both at sign-in and at the proxy layer.
Fixes
Plugins
- AI Proxy and AI Proxy Multi
- Fixed issue: Anthropic Messages requests that contained tool results mixed with other content could be converted into an invalid OpenAI Chat message order, causing OpenAI-compatible upstreams to reject every later request in the session. Tool messages are now emitted immediately after the assistant tool-call message, and adjacent text or media is preserved in a following user message. Several other Anthropic-to-OpenAI conversion details were aligned with LiteLLM-compatible behavior, including tool name sanitization, long tool-name collision handling, adaptive thinking effort, structured-output schema extraction, empty array encoding, and content block shaping.
- Fixed issue: Structured chat content could reach downstream AI plugins as a table and cause request processing errors. Protocol adapters now flatten text content consistently before AI guard and cache plugins consume it.
- AI Lakera Guard
- Fixed issue: In streaming responses with
action: alertandfail_open: false, a Lakera API error or timeout could let the streamed response through instead of failing closed. Lakera errors are now handled according tofail_open, so a strict configuration blocks the response.
- Fixed issue: In streaming responses with
- AI AWS Content Moderation and AI Aliyun Content Moderation
- Fixed issue:
deny_codeaccepted arbitrary numbers. It is now validated as an integer HTTP status code in the200–599range (default200), so an out-of-range value is rejected at configuration time.
- Fixed issue:
- AI Aliyun Content Moderation
- Fixed issue: Returning
ngx.OKfrom the body filter could interrupt later body-filter processing. The plugin now returns normally so other filters can continue.
- Fixed issue: Returning
- AI Rate Limiting
- Fixed issue: Some configured Redis fields were dropped and replaced with defaults —
redis_username/redis_passwordfor the redis-sentinel policy andredis_keepalive_timeout/redis_keepalive_poolfor the redis and redis-cluster policies. All configured Redis fields are now forwarded.
- Fixed issue: Some configured Redis fields were dropped and replaced with defaults —
- gRPC Transcode
- Fixed issue: Empty protobuf
repeatedfields were encoded as{}instead of JSON arrays ([]). Empty repeated fields now appear as arrays, including nested and descriptor-set based messages.
- Fixed issue: Empty protobuf
- Key Auth and other consumer authentication plugins
- Fixed issue: If a consumer credential referenced a secret that could not be resolved, the Data Plane could still index the unresolved literal value and authenticate requests with it. Consumer authentication now fails closed when a referenced secret cannot be resolved.
- Secret references
- Fixed issue: Updates or deletions of
/secretsconfiguration did not invalidate the secret LRU cache, so old secret values could continue to be used until cache expiry or indefinitely. Secret references are now re-resolved after secret configuration changes.
- Fixed issue: Updates or deletions of
- Proxy Rewrite
- Fixed issue: With
use_real_request_uri_unsafeanduriconfigured together, the request query string was dropped during URI rewriting. The original query string is now preserved and correctly merged when the rewritten URI already contains a query.
- Fixed issue: With
- Loggly
- Fixed issue: Batched Loggly entries from different routes could use the wrong token or tags because the async handler reused the latest route configuration. Each batch processor now keeps its own route configuration.
- Datadog
- Fixed issue: Large coalesced DogStatsD datagrams could exceed the common 8192-byte agent buffer and be silently truncated. The plugin now coalesces only when the payload fits, otherwise it falls back to one datagram per metric.
- Zipkin
- Fixed issue: Requests explicitly marked as unsampled still built full span tag tables and access-phase child spans. Unsampled requests now skip that extra tracing work while preserving trace propagation.
- OpenTelemetry
- Fixed issue: Updating OpenTelemetry plugin metadata at runtime did not rebuild the tracer used for injected core spans, so those spans could keep using stale collector or resource settings until worker recycle. The tracer now refreshes when metadata changes.
- Fixed issue:
additional_attributeswere evaluated before log-phase variables were populated, so attributes that depend on final request state could be missing or stale. They are now evaluated in the log phase. - Fixed issue: With
trace_id_sourceset tox-request-id, anX-Request-Idvalue that is not valid hexadecimal (for example a UUID), or a duplicated header, could returnHTTP 500. The value is now validated and the plugin falls back to a random valid trace ID when it cannot be used. - Fixed issue: The plugin metadata schema accepted non-scalar values for
resourceattributes andcollector.request_headers, which were then silently dropped at runtime. Such values are now rejected at configuration time.
- OpenID Connect
- Fixed issue:
refresh_session_intervalincorrectly defaulted to900, enabling silent re-authentication even when users did not configure it. The default is removed (see Upgrade Notes).
- Fixed issue:
- MQTT Proxy
- Fixed issue:
protocol_namewas required even though the standard default isMQTT. It is now optional and defaults toMQTT.
- Fixed issue:
- Forward Auth
- Fixed issue: When
request_methodwasPOST, the authorization request could forward the client'sTransfer-Encoding,Content-Length, andExpectheaders after the body had already been buffered, producing inconsistent request framing. These client framing headers are no longer copied to the auth service.
- Fixed issue: When
- Authz CASBIN
- Fixed issue: Routes using different Casbin model or policy shapes could trigger
casbin enforce error/invalid request sizeafter switching between them. The bundledlua-casbindependency is updated to include the enforcement fixes.
- Fixed issue: Routes using different Casbin model or policy shapes could trigger
- Request ID
- Fixed issue: Configuring
algorithm: range_idwithout the optional range object could returnHTTP 500. The range object now has a default value.
- Fixed issue: Configuring
- Workflow
- Fixed issue: Workflow action plugins could run
_meta.pre_functionhooks before workflow skipped or executed the action, changing behavior even for actions that should not run. Workflow now decides whether to run the action before executing those meta hooks.
- Fixed issue: Workflow action plugins could run
- Request Validation
- Fixed issue: A form body whose
Content-Typecarried a charset parameter or different casing (for exampleapplication/x-www-form-urlencoded; charset=utf-8) was not recognized as form-urlencoded, so it was parsed as JSON and rejected withHTTP 400. Such content types are now recognized and validated as form bodies.
- Fixed issue: A form body whose
Data Plane
- Fixed issue: Log rotation could leave some log files open after only part of the file set was rotated. The gateway now reopens logs correctly after partial rotation.
- Fixed issue: When the configuration source reported a smaller revision than the gateway had already seen (for example after the Control Plane database was restored to an earlier state), the gateway could keep serving stale configuration until a worker restart. It now forces a full configuration resync when a smaller revision is observed.
- Fixed issue: A plugin attached through a global rule or a consumer (such as
ai-proxy-multi) could fail withHTTP 5xxbecause the gateway could not fetch the plugin's parent configuration at request time. All plugin-bearing resource types are now resolved correctly. - Fixed issue: With
client-controlconfigured withmax_body_size: 0(no limit), a chunked request body could still be rejected withHTTP 413. Amax_body_sizeof0now correctly disables the size check for chunked requests.
Control Plane
- Fixed issue: Gateway instances could be marked OutOfSync for a short window immediately after a configuration revision changed, before the next heartbeat reported the new revision. A grace period now keeps recently heartbeating instances Healthy during that normal synchronization window.
- Fixed issue: Compatibility reports were capped at 200 unordered items, so large reports could hide errors and differ between gateway instances. The Control Plane now stores the full sorted report and computes compatibility from all items.
- Fixed issue: A secret provider request body could match a different provider type than the provider named in the URL path, causing the Data Plane to discard or misread the stored secret. The Control Plane now validates the body against the selected provider type.
- Fixed issue: The Control Plane accepted some core resource configurations that the Data Plane silently discarded, including invalid TLS, filter, and IP match configurations. These are now rejected by validation before publishing.
- Fixed issue: Plugin configurations on the runtime services endpoint were not validated, so a service with an invalid plugin configuration was accepted and then silently discarded by the Data Plane. Service plugin configurations are now validated and rejected with
HTTP 400when invalid. - Fixed issue: IPv6 upstream node hosts were rejected by the OpenAPI schema. IPv6 hosts are now accepted.
- Fixed issue: Batch
sslsvalidation used the SNI schema instead of the SSL schema, so entries without required certificate fields could pass validation. Batch SSL validation now uses the correct schema. - Fixed issue: Audit log export returned only the first 256 rows. Exports now include all matching audit logs.
- Fixed issue: Credential lookup indexes could be dropped by components that do not run database migration, and some startup schema repairs rebuilt current indexes unnecessarily. Schema repair now keeps current indexes and repairs only stale shapes.
- Fixed issue: Heartbeat and instance status queries could become slow on large deployments. Additional indexes and Go-side status computation improve these lookups.
3.10.2
Release Date: 2026-06-29
Upgrade Notes
The Control Plane now encrypts additional credential-bearing plugin fields at rest. Because API7 EE upgrades the Control Plane before the Data Plane, during the upgrade window a 3.10.2 Control Plane encrypts these fields while an older 3.10.1 Data Plane cannot decrypt them, which can cause the affected plugins to fail until the Data Plane is also upgraded.
The newly encrypted fields, by plugin, are:
- limit-count, limit-count-advanced, and graphql-limit-count:
redis_password,sentinel_password - limit-conn:
redis_password - limit-req:
redis_password - ai-rate-limiting:
redis_password,sentinel_password - elasticsearch-logger:
headers(the custom authentication headers) - openid-connect:
session.redis.password - ai-cache:
redis_password - ai-lakera-guard:
api_key
If you use any of these plugins with the listed fields, upgrade the Data Plane to 3.10.2 promptly after the Control Plane, and avoid editing those plugins until both sides are on 3.10.2.
When hmac-auth has validate_request_body enabled, the default max_req_body_size is now 67108864 bytes (64 MiB), aligned with Apache APISIX. In 3.10.1 this default was 524288 bytes (512 KiB), which rejected request bodies between 512 KiB and 64 MiB with HTTP 413. After upgrading, such requests are accepted by default. If you relied on the lower limit, set max_req_body_size explicitly to restore it.
The Developer Portal signup consent is now configured with a single signUpConsentLabel option (an HTML snippet rendered next to the consent checkbox), and the consent check is enforced only when a label is configured. The previous tosURL and beforeSignUpButtonHtml options have been removed. If your portal configuration sets either of those keys, move the content to signUpConsentLabel before upgrading; otherwise the signup consent text will no longer be shown.
Features
Plugins
- AI Cache (new plugin)
- Caches LLM responses so that identical requests are served from a cache instead of calling the upstream model again. Exact-match caching is keyed on the normalized request body and stored in Redis; a cache hit returns the stored response with the headers
X-AI-Cache-Status: HITandX-AI-Cache-Age. Streaming requests are bypassed (X-AI-Cache-Status: BYPASS).
- Caches LLM responses so that identical requests are served from a cache instead of calling the upstream model again. Exact-match caching is keyed on the normalized request body and stored in Redis; a cache hit returns the stored response with the headers
- AI Lakera Guard (new plugin)
- Screens AI traffic through the Lakera Guard API to detect prompt injection and other unsafe content. The
directionoption (input,output, orboth) selects whether the plugin scans the request prompt, the LLM response (including streaming responses), or both. Flagged traffic is blocked with a configurabledeny_code, or only logged whenactionis set toalert.
- Screens AI traffic through the Lakera Guard API to detect prompt injection and other unsafe content. The
- AI Aliyun Content Moderation
- Added
request_check_mode(lastorall, defaultlast) to control how much of a multi-turn conversation is moderated:lastchecks only the final user turn, whileallchecks every user turn. Onlyuser-role content is moderated;systemandassistantcontent is ignored. Long content is now split into chunks in linear time and is multi-byte (UTF-8) safe.
- Added
- AI Proxy
- The structured
llm_summaryobject emitted to logger plugins (whenlogging.summariesis enabled) now includes additional AI observability fields:stream,tool_count,has_tool_calls,end_user_id,cache_read_input_tokens,cache_creation_input_tokens, andreasoning_tokens.
- The structured
- Elasticsearch Logger
- Added support for authenticating to Elasticsearch with custom request headers via the
headersoption (for example, anAuthorization: Bearer <token>or an API-key header), as an alternative to basicauth.
- Added support for authenticating to Elasticsearch with custom request headers via the
- OpenID Connect
- Added Redis as a session storage backend. Set
session.storagetoredisand configuresession.redis(host, port, and related options) to store sessions in Redis instead of in the session cookie;cookieremains the default.
- Added Redis as a session storage backend. Set
Data Plane
- Added
log_format_extra, an additive log format for logger plugins that adds fields to the default rich log format instead of replacing it (aslog_formatdoes). A new variable,$upstream_unresolved_host, records the configured upstream host before DNS resolution.log_format_extracan be set globally through plugin metadata or per route. - Added per-port PROXY protocol control for the stream (L4) TCP proxy. Each
stream_proxy.tcpentry can independently enable receiving the PROXY protocol (proxy_protocol) and sending it to the upstream (proxy_protocol_to_upstream), overriding the global defaults. - Added the
max_post_args_readable_sizeconfiguration option (default 64 MiB) that bounds how much of a request body is read when matchingpost_arg.*route predicates on JSON and multipart requests. Set it to0to disable the limit. - Debug sessions now capture each request's logs as OpenTelemetry span events on the request's root span, so per-request logs are available in the trace without an external log collector.
Control Plane
- Added an RPM install method for the Data Plane, alongside the existing Docker and Helm options. The Dashboard's gateway-group deployment page has a new RPM tab; after the
api7-gatewayRPM is installed on an air-gapped host, the generated offline script provisions the gateway-group client certificate, writes the gateway configuration, and joins the instance to the Control Plane.
Developer Portal
- Disable the API Hub: operators can turn the API Hub off entirely with the
apiHub.enabledconfiguration flag. When disabled, the API Hub link is hidden from the navigation, API Hub pages return not-found, and API Hub URLs are removed from the sitemap. - Require email verification: signup and sign-in can be configured to require developers to verify their email address before authentication completes.
- Custom PostgreSQL schema: the portal can be deployed into a custom PostgreSQL schema instead of
public, applying the schema'ssearch_pathper connection and running schema-scoped migrations. - Organization management for platform admins: the admin Organizations page can now take over an organization (becoming its owner) or delete an organization, in addition to the existing user-management actions.
- Documentation Markdown and LLM endpoints: the in-portal documentation site now exposes Markdown and LLM-friendly text endpoints for use by AI tooling, and individual documentation pages can be excluded from those endpoints while remaining readable in the docs UI.
Fixes
Plugins
- AI Proxy
- Fixed issue: When the upstream returned a tool call whose
argumentswere not valid JSON, the entire response conversion was aborted and the client received nothing. The malformed tool call now falls back to an empty argument object, and the rest of the response (including any text content) is preserved. - Fixed issue: A request that carried
tool_choicebut had no usabletools(for example, only a built-in tool that is dropped during conversion) was forwarded with the orphantool_choiceand rejected by the upstream. Such atool_choice(andparallel_tool_calls) is now removed. Separately, a streaming Anthropic request whose upstream omitted the final completion chunk no longer hangs the client until timeout — the stream is now terminated correctly. - Fixed issue: When the upstream LLM returned an error status (such as
HTTP 429or5xx), the error response body was discarded and the client received an empty body. The upstream error body and content type are now preserved, including acrossai-proxy-multifallback.
- Fixed issue: When the upstream returned a tool call whose
- AI Proxy Multi
- Fixed issue: A failure while constructing the working instance pool could raise a Lua error and destructively clear pool state. The failure path is now nil-safe and non-destructive.
- Limit Count
- Fixed issue: When
countortime_windowwas resolved from a variable, an invalid value (non-integer, zero or negative, or beyond the safe integer range) was silently ignored, which could disable the rate limit entirely. Such values are now validated and rejected, closing a rate-limit bypass. - Fixed issue: With the Redis policy and sliding-window counting, the check-and-increment was not atomic, so concurrent requests could exceed the configured limit. Counting is now performed atomically with a Redis script.
- Fixed issue: When
- Limit Request
- Fixed issue: The rate-limit counter was keyed so that a limit attached to a shared resource (such as a Consumer) counted each route separately instead of sharing one bucket. The counter is now keyed by the parent resource, so a Consumer-level limit is enforced across all of the Consumer's routes.
- HMAC Auth
- Fixed issue: When
validate_request_bodywas enabled and a request body exceededmax_req_body_size, the request was rejected with a misleadingHTTP 401. It is now rejected withHTTP 413. The defaultmax_req_body_sizewas also raised to 64 MiB (see Upgrade Notes).
- Fixed issue: When
- Attach Consumer Label
- Fixed issue: A client could spoof a configured header when the matched Consumer had no labels, because the plugin overwrote the header only when a label value was present. Configured headers are now always stripped from the client request, even when the Consumer has no matching label.
- Redirect
- Fixed issue:
http_to_httpsonly redirected requests whose scheme was exactlyhttp, so a request arriving with a non-HTTP, non-HTTPS scheme (for example, via a spoofedX-Forwarded-Proto) was not redirected. It now redirects every non-HTTPS scheme.
- Fixed issue:
- Response Rewrite
- Fixed issue: When the upstream response was compressed (gzip or brotli),
filterswere applied to the compressed bytes and did not match, producing a corrupted body. The response is now decoded before filters run.
- Fixed issue: When the upstream response was compressed (gzip or brotli),
- Batch Requests
- Fixed issue: When a pipelined sub-request timed out, the response array could contain more entries than there were sub-requests (an extra empty object). The number of sub-responses now always matches the number of sub-requests.
- Loki Logger
- Fixed issue: Log labels resolved from variables were written back to the shared plugin configuration, so the first request's values were frozen and reused for all subsequent requests. Labels are now resolved per request.
- Tencent Cloud CLS
- Fixed issue: With
include_req_bodyenabled, the request body was not captured because it was not read in the access phase. The body is now read so that it is included in the uploaded logs.
- Fixed issue: With
- Authz Keycloak
- Fixed issue: With
lazy_load_pathsenabled, the request query string was included when resolving the Keycloak resource by URI, so requests with query parameters failed to match a resource and were denied. The query string is now stripped before resolution.
- Fixed issue: With
- CAS Auth
- Fixed issue: The CAS single-logout (SLO) callback
POSTwas proxied to the upstream instead of being handled by the plugin. The callback is now terminated by the plugin and is no longer forwarded upstream.
- Fixed issue: The CAS single-logout (SLO) callback
- gRPC Web
- Fixed issue: A debug log statement wrote the decoded request body to the error log. The statement has been removed so request payloads are no longer leaked into logs.
Data Plane
- Fixed issue: Resolving a dotted context variable in a log format (such as
$consumer.usernameor$llm_summary.model) raised an error and dropped the log line when the parent object was absent (for example, an unauthenticated request that has no consumer). The missing value is now handled gracefully. - Fixed issue: After a transient DNS or service-discovery failure, a domain-name upstream could remain stuck returning
HTTP 503even after the name resolved again. The upstream now recovers once resolution succeeds. - Fixed issue: When a node's health changed, the consistent-hash (chash) ring was rebuilt against the healthy subset, remapping keys that belonged to healthy nodes. The ring is now kept stable so that only keys of the failed node are remapped.
- Fixed issue: Environment-variable substitution in configuration directives could match the wrong variable when one variable name was a prefix of another. Names are now resolved exactly.
- Fixed issue: Upgraded the Prometheus metrics library (
nginx-lua-prometheus-api7to0.20260623) to remove duplicate metric series that could cause a scrape to be rejected.
Control Plane
- Fixed issue: A Data Plane running the latest gateway version was marked Incompatible when its configuration report contained errors. A latest-version Data Plane now stays Compatible, and the configuration errors are still surfaced in the compatibility report summary.
Developer Portal
- Fixed issue: Enabling or disabling two-factor authentication did not actually verify the account password, the backup-codes dialog could appear empty, and entering a wrong TOTP code at sign-in navigated to the home page instead of showing an error. Password verification, backup-code display, and TOTP error handling now work correctly.
- Fixed issue: An email-domain SSO policy was enforced only in the UI, so direct calls to the authentication endpoints could bypass it. The policy is now enforced on the server, so password sign-in, magic-link, and password-reset requests are rejected for domains that are required to use SSO.
3.10.1
Release Date: 2026-06-15
Breaking Changes
Plugins
-
Upgrade note
jwt-authnow verifies the token'sexp(expiration) andnbf(not-before) claims by default. Previously, a consumer that did not setclaims_to_verify(or set it to an empty list) accepted any correctly signed token, including expired ones. After the Data Plane is upgraded, such tokens are rejected withHTTP 401.If you relied on expired tokens being accepted, account for this behavior change before upgrading. To keep verifying only specific claims, set
claims_to_verifyexplicitly in the consumer configuration. -
Batch Requests
Upgrade noteThe
batch-requestsplugin now bounds the size of a batch. The number of pipelined sub-requests is limited by a newmax_pipeline_itemsplugin-metadata option (default1000); a batch that exceeds the limit is rejected withHTTP 400. Pipeline entries that contain fields other than the documented ones are now rejected, and the per-batchtimeoutmust be at least1millisecond.If you send batches larger than 1000 sub-requests, raise
max_pipeline_itemsin the plugin metadata. If your clients send entries with undocumented fields, remove them before upgrading.
Upgrade Notes
The Control Plane now encrypts additional credential-bearing plugin fields at rest. Because API7 EE upgrades the Control Plane before the Data Plane, during the upgrade window a 3.10.1 Control Plane encrypts these fields while an older 3.10.0 Data Plane cannot decrypt them, which can cause the affected plugins to fail until the Data Plane is also upgraded.
The newly encrypted fields, by plugin, are:
- http-logger:
auth_header - kafka-logger:
brokers.sasl_config.password - splunk-hec-logging:
endpoint.token - loggly:
customer_token - openfunction:
authorization.service_token - azure-functions:
authorization.apikey, and the plugin-metadatamaster_apikey - ai-aws-content-moderation:
comprehend.secret_access_key - openid-connect:
session.secret - error-log-logger (plugin metadata):
kafka.brokers.sasl_config.password
If you use any of these plugins with the listed fields, upgrade the Data Plane to 3.10.1 promptly after the Control Plane, and avoid editing those plugins until both sides are on 3.10.1.
All features that were previously exclusive to limit-count-advanced — the redis-sentinel policy, sliding-window counting, multiple independent limits in one configuration (rules), dynamic count and time_window from NGINX variables, and delayed Redis synchronization (sync_interval) — are now built into the limit-count plugin. Existing limit-count-advanced configurations continue to work unchanged (the plugin is retained as a thin wrapper), so no configuration migration is required.
One upgrade-time effect: for Redis-backed policies, the counter storage format changed and counter keys are now versioned. Existing counters are not migrated — they expire on their own TTL — so rate-limit counters reset once at the moment of upgrade. (The local policy already resets on restart.) Expect a brief counter-reset window; no action is required.
To bound memory usage, several plugins now reject over-large request bodies before buffering them:
- hmac-auth: when
validate_request_bodyis enabled, the body is limited by a newmax_req_body_sizeoption (default524288bytes, i.e. 512 KiB). - forward-auth, ai-proxy, ai-proxy-multi: the body is limited by a new
max_req_body_sizeoption (default67108864bytes, i.e. 64 MiB); requests over the limit are rejected withHTTP 413.
These defaults are above the NGINX default client_max_body_size (1 MiB), so most deployments are unaffected. If you legitimately handle larger bodies on routes that use these plugins (and have raised client_max_body_size accordingly), raise max_req_body_size to match.
In the Developer Portal, the key-auth key and basic-auth password of a credential are now returned only once, when the credential is created or regenerated. They are no longer included in credential read or list responses, matching the existing behavior of the OAuth client_secret. Basic-auth usernames remain visible.
Copy and store the secret when it is first shown. If a secret is lost, regenerate the credential to obtain a new value. Any integration that read these secrets back from the credential read or list API must be updated to capture them at creation time.
The standalone apisix_llm_ttft metric has been replaced by apisix_llm_latency{type="ttft"}, mirroring the structure of apisix_http_latency. If you have Prometheus queries or Grafana dashboards that reference apisix_llm_ttft, update them to select the ttft value of the type label on apisix_llm_latency.
Features
Plugins
- Limit Count
- The advanced rate-limiting capabilities formerly provided only by
limit-count-advancedare now available directly inlimit-count: theredis-sentinelpolicy, sliding-window counting (window_typeset tosliding), multiple independent limits in a single configuration (rules),countandtime_windowdriven by NGINX variables (for example, per-consumer dynamic quotas), and delayed Redis synchronization (sync_interval).
- The advanced rate-limiting capabilities formerly provided only by
- AI content security plugins (
ai-aliyun-content-moderation,ai-aws-content-moderation,ai-prompt-guard)- Added a
fail_modeoption (skip,warn, orerror; defaultskip) that controls how the plugin handles non-AI or non-JSON requests when it is bound at the Consumer level. Withskip, such requests pass through unchecked;warnadditionally logs a warning;errorrejects them. This avoids errors when a Consumer-bound moderation plugin receives ordinary, non-AI traffic.
- Added a
- AI Proxy
- Added built-in NGINX variables that describe each LLM request, for use in
access_logformats and logger plugins:$llm_total_tokens,$llm_stream,$llm_has_tool_calls,$llm_tool_count,$llm_end_user_id,$llm_cache_read_input_tokens,$llm_cache_creation_input_tokens, and$llm_reasoning_tokens. Values are mapped for OpenAI (Chat and Responses), Anthropic, and DeepSeek.
- Added built-in NGINX variables that describe each LLM request, for use in
- Prometheus
- Added LLM observability metrics: per-request prompt-token and completion-token distribution histograms (
apisix_llm_prompt_tokens_dist,apisix_llm_completion_tokens_dist), a total-latency histogram, and time-to-first-token exposed asapisix_llm_latency{type="ttft"}for streaming requests. Histogram buckets are configurable viaplugin_attr.prometheus. - Added
mcp_request_typeandmcp_tool_namelabels to thehttp_status,http_latency, andbandwidthmetrics, so MCP (Model Context Protocol) traffic can be broken down by request type (tools/listortools/call) and by tool name. Both labels can be turned off viadisabled_labels. - Reduced per-request overhead in the metrics logging phase by caching the disabled-labels map instead of rebuilding it on every request.
- Added LLM observability metrics: per-request prompt-token and completion-token distribution histograms (
- AI Proxy Multi
- Added
max_retriesandretry_on_failure_within_msto the fallback mechanism.max_retriescaps how many additional instances a request retries after a failure;retry_on_failure_within_msonly falls back when the upstream failed within the given time, so slow failures are returned to the client instead of being retried (avoiding doubled latency on long-running LLM requests).
- Added
- OpenID Connect
- Exposed the
lua-resty-sessionoptions undersession(cookie name, path, domain,secure,http_only,same_site, and the idling, rolling, and absolute timeouts), so you can customize the session cookie name and set session lifetimes that take effect. The legacysession.cookie.lifetimeis deprecated but still honored (mapped toabsolute_timeout). client_secretis now optional for flows that only verify tokens locally and never call the identity provider (bearer_onlywithpublic_keyoruse_jwks,private_key_jwt, and public-client PKCE). It remains required for session/callback and introspection flows.
- Exposed the
- Kafka Logger
- Added an
api_versionoption (0,1, or2; default1) for the Kafka produce protocol. Setapi_versionto2so that brokers record the real message timestamp (Kafka 0.10 and later); the default1preserves wire compatibility.
- Added an
Developer Portal
- Two-factor authentication: developers can secure their account with a TOTP authenticator app. 2FA is enabled from the account security settings (confirm password, scan the QR code) and can be disabled there; sign-in then prompts for a six-digit code.
- Dark mode: the portal supports light, dark, and system themes with a toggle in the header, applied across the UI, the API-usage chart, and the docs site, and persisted across visits.
- Platform-admin console: an admin area (restricted to the configured admin users) adds a Users page to list, search, and paginate portal users, change a user's role, ban or unban, and delete users; the Organizations page can be filtered by user membership.
- Approvals in the Developer Portal: platform admins can review and act on developer-registration and API-product subscription requests from a new Approvals page in the portal. Approvals share a single source of truth with the Dashboard, and the acting admin's identity is recorded with each decision (the Dashboard's Approvals list now shows the real admin name for decisions made in the portal).
- Policy-based SSO sign-in: sign-in routes each user to the correct method (credentials, magic link, or SSO) based on the email entered. Administrators can map email domains to SSO providers, including anchored, case-insensitive regular-expression patterns, so one rule can cover many domains.
- In-portal documentation site: the portal now hosts a Markdown documentation site at
/docs, themed to match the portal, with a collapsible sidebar, table of contents, code-copy buttons, a "Copy page" menu, and built-in full-text search with highlighted result snippets. - Signup Terms of Service: new users must accept a Terms of Service agreement to complete signup; the ToS URL is configurable.
- Simplified organization creation: the "Create organization" dialog no longer asks for a slug — the identifier is assigned automatically from the name.
Fixes
Plugins
- JWT Auth
- Fixed issue: A token whose signature was malformed (wrong length or not valid base64url) caused the verifier to raise an error and return
HTTP 500instead of rejecting the request. Malformed signatures are now rejected withHTTP 401.
- Fixed issue: A token whose signature was malformed (wrong length or not valid base64url) caused the verifier to raise an error and return
- AI Proxy
- Fixed issue: In passthrough mode, the upstream request was always sent as
POSTand the client's query string was dropped, breaking providers that require query parameters on other methods (for example, Azure OpenAI's?api-version=). The client's method and query string are now forwarded. - Fixed issue: A timeout while reaching an upstream LLM (such as a DNS-resolution timeout) returned
HTTP 500instead ofHTTP 504. Upstream LLM timeouts now correctly returnHTTP 504.
- Fixed issue: In passthrough mode, the upstream request was always sent as
- AI Proxy Multi
- Fixed issue: After per-instance health checkers were created, the cached server picker was not rebuilt, so a worker could keep routing part of its traffic to instances already marked unhealthy; active health-check probes could also corrupt the probe path for instances configured with
auth.queryorauth.header. Both issues are fixed, so failover and health checking work as configured.
- Fixed issue: After per-instance health checkers were created, the cached server picker was not rebuilt, so a worker could keep routing part of its traffic to instances already marked unhealthy; active health-check probes could also corrupt the probe path for instances configured with
- Graphql Proxy Cache
- Fixed issue: After
Varysupport was added to the in-memory cache, aPURGErequest cleared only the legacy cache slot and left the per-Varyvariants cached, so stale responses were still served until their TTL expired.PURGEnow clears all variants.
- Fixed issue: After
- Body Transformer
- Fixed issue: The XML-to-JSON transform intermittently dropped namespaced keys on some worker processes, causing SOAP transforms to fail with
attempt to index field 'Body' (a nil value). Namespaced keys are now always preserved. Malformed multipart input now returnsHTTP 400instead ofHTTP 500.
- Fixed issue: The XML-to-JSON transform intermittently dropped namespaced keys on some worker processes, causing SOAP transforms to fail with
- Kafka Logger
- Fixed issue: Messages were stored by brokers with no usable timestamp (rendered as 1970-01-01) because the Kafka produce API version could not be configured. Setting the new
api_versionto2makes brokers record the real timestamp.
- Fixed issue: Messages were stored by brokers with no usable timestamp (rendered as 1970-01-01) because the Kafka produce API version could not be configured. Setting the new
- Elasticsearch Logger
- Fixed issue: A dynamic index name using certain
{time_format}placeholders could produce a corrupted index name. Invalid time formats now fall back to an empty value (with a log message) instead of corrupting the index name.
- Fixed issue: A dynamic index name using certain
- AWS Lambda
- Fixed issue: With IAM (SigV4) authentication, requests whose query string contained characters that need escaping, multiple values, or valueless keys produced a signature mismatch (
InvalidSignatureException). The canonical query string is now built per the SigV4 specification.
- Fixed issue: With IAM (SigV4) authentication, requests whose query string contained characters that need escaping, multiple values, or valueless keys produced a signature mismatch (
- Proxy Mirror
- Fixed issue: Mirroring a gRPC request sent the internal location name as the request path, so the mirror backend rejected every call with
UNIMPLEMENTED. The original gRPC method path is now used for the mirrored request.
- Fixed issue: Mirroring a gRPC request sent the internal location name as the request path, so the mirror backend rejected every call with
- Request ID
- Fixed issue: With
algorithmset tonanoid, the generator produced a high rate of duplicate and malformed IDs and leaked a file descriptor per ID. It is replaced with a CSPRNG-based generator; the ID format is unchanged and IDs are now unique and well-formed.
- Fixed issue: With
- OPA
- Fixed issue: With
send_headers_upstreamconfigured, a header that the OPA server did not return was left with the client-supplied value on the upstream request instead of being cleared. Such headers are now cleared.
- Fixed issue: With
- SAML Auth
- Fixed issue: Debug logging was unintentionally left enabled, and an authentication failure could pass the request through instead of returning an error. Debug mode is now off, and authentication failures return an explicit error.
- CORS
- Fixed issue: A request with no
Originheader returnedHTTP 500whenallow_origins_by_regexwas configured. Such requests are now handled without error.
- Fixed issue: A request with no
- Multi Auth
- Fixed issue: When an underlying auth plugin returned a status with no error message, the plugin returned
HTTP 500instead ofHTTP 401. It now returnsHTTP 401.
- Fixed issue: When an underlying auth plugin returned a status with no error message, the plugin returned
- DingTalk Auth
- Fixed issue: A client could supply a forged
X-Userinfoheader that was forwarded upstream. The plugin now clears any client-suppliedX-Userinfoheader before authentication, so upstream services only receive plugin-verified identity information.
- Fixed issue: A client could supply a forged
- Authz Casdoor
- Fixed issue: The login session was not bound to the Casdoor token's lifetime and fell back to the session library default, so sessions could outlive their tokens. Sessions now expire when the Casdoor token expires.
- CAS Auth
- Fixed issue: A single-logout
POSTwith an empty body returnedHTTP 500instead ofHTTP 400. Empty-body logout requests are now rejected withHTTP 400.
- Fixed issue: A single-logout
- Limit Conn
- Fixed issue: A dynamic
burstvalue (from an NGINX variable) that resolved to0was rejected withHTTP 500, even though a staticburstof0is valid. A variableburstof0is now accepted.
- Fixed issue: A dynamic
- Limit Req
- Fixed issue: Under concurrent load, the Redis-backed rate limit could be exceeded because the read and write were not atomic. Enforcement now uses a single atomic operation.
Data Plane
- Fixed issue: Redis connections were pooled by address only, so two plugin configurations pointing at the same Redis server with different databases, credentials, or TLS settings could reuse each other's connections — for example,
limit-count,limit-req, orlimit-conncounters could be written to the wrong Redis database. Connections are now isolated by address, database, credentials, and TLS settings, including theredis-sentinelpolicy. - Fixed issue: After
set_headerwas called with a different letter case than an existing header, the cached header table kept both entries, so plugins that iterate cached headers (such asext-plugin-post-resp) could forward the stale value upstream. Cached header keys are now normalized. - Fixed issue: When the
workflowplugin ran in a global rule and a route plugin completed the request during the rewrite phase (for example, a CORS preflight), the log phase logged an error on every such request. The log phase now exits cleanly when its context is absent. - Fixed issue: Enabling Nacos service discovery with the stream subsystem aborted the stream worker at startup because a required shared dictionary was not declared in the stream subsystem; it is now declared. A separate error-handling bug that turned a Nacos registry-creation failure into a worker crash is also fixed.
- Fixed issue: With Consul service discovery, a single malformed node entry caused the remaining nodes of the service to be discarded (and could drop the entire service, producing "no valid upstream node"). Only the invalid node is now skipped.
- Fixed issue: Resolving an AWS Secrets Manager reference failed when the secret name contained a slash, because the lookup split the path at the first slash. Secret names containing slashes now resolve correctly.
- Fixed issue: An Admin API
PATCHre-encrypted already-encrypted plugin fields, so aPATCHon any field could corrupt stored secrets (such askey-authkeys). Encrypted fields are now decrypted before the merge and re-encrypted exactly once.
Developer Portal
- Fixed issue: Organization invitations did not honor the email-verification setting. Invitations now follow the configured email-verification behavior.
- Fixed issue: For a developer who belongs to multiple organizations, the portal could use the wrong organization context. The developer identity is now resolved from the active organization.
- Fixed issue: Several dialog widths were inconsistent across the portal; they are now unified.
3.10.0
Release Date: 2026-06-01
Breaking Changes
Plugins
-
Upgrade note
The
hmac-authplugin now defaultssigned_headersto["date"]. After the Data Plane is upgraded, anyhmac-authconfiguration that does not explicitly setsigned_headersrequires the client signature to cover theDateheader. Clients that were not signingDatewill start receivingHTTP 401withclient request can't be validated.Before upgrading, ensure your clients sign the
Dateheader, or setsigned_headersexplicitly in the plugin configuration to match what your clients sign.
Control Plane
-
Service Templates and API Publishing removed
Upgrade noteThe Service Template / Service Hub model has been removed. Services are now owned directly by gateway groups and managed through the APISIX Admin API — there is no longer a separate template, publish, version, rollback, or runtime-configuration layer. In the Console, the Service Hub section is replaced by a Services list under each gateway group.
When the Control Plane is upgraded to 3.10.0, existing service templates and published services are automatically migrated to the new direct-service model. The original data is preserved (not dropped), so the upgrade can be rolled back. IAM permission policies that referenced service-template or published-service ARNs are backed up and rewritten automatically.
Developer Portal
-
Developer authentication is enforced only for published API products
Upgrade notePreviously, API products in draft state also had their developer authentication rules synced to the gateway, so routes belonging to a draft product required developer authentication. Starting in 3.10.0, only published API products contribute developer-authentication rules to the data plane.
After upgrading, routes that belong to a draft (unpublished) API product no longer require developer authentication until the product is published. If you relied on draft products being protected, publish them or restrict access another way.
Upgrade Notes
The Data Plane gateway runtime has been upgraded from OpenResty 1.21.4.4 to OpenResty 1.29.2.4, and now builds on the open-source apisix-runtime base. The primary motivation is to pick up the latest upstream security fixes in the NGINX core, OpenSSL, LuaJIT, and the bundled libraries by moving across several major versions.
Most deployments require no action. One visible consequence is that HTTP/2 is now enabled with the NGINX 1.25+ http2 on; directive instead of the per-listen http2 parameter; HTTP/2 over cleartext (h2c) and over TLS continues to work as before. If you maintain custom NGINX configuration snippets, verify they are compatible with the newer NGINX before upgrading.
The Control Plane now encrypts additional credential-bearing plugin fields at rest. Because API7 EE upgrades the Control Plane before the Data Plane, during the upgrade window a 3.10.0 Control Plane encrypts these fields while an older 3.9.13 Data Plane cannot decrypt them, which can cause the affected plugins to fail until the Data Plane is also upgraded.
The newly encrypted fields, by plugin, are:
- feishu-auth:
secret_fallbacks - dingtalk-auth:
secret_fallbacks
If you use any of these plugins with the listed fields, upgrade the Data Plane to 3.10.0 promptly after the Control Plane, and avoid editing those plugins until both sides are on 3.10.0.
Features
Plugins
- AI Proxy
- The upstream LLM request body is now JSON-encoded with sorted keys. This produces a stable, byte-identical body for equivalent requests, which improves prompt-cache hit rates on LLM providers that cache by exact request payload.
- AI Proxy Multi
- When an LLM endpoint domain resolves to multiple A records, the plugin now resolves all of them and builds a multi-node upstream, selecting a node per request for better load distribution and failover. The
Hostheader and TLS SNI keep the original domain name rather than the resolved IP address.
- When an LLM endpoint domain resolves to multiple A records, the plugin now resolves all of them and builds a multi-node upstream, selecting a node per request for better load distribution and failover. The
- Proxy Cache
- The in-memory cache strategy now honors the
Varyresponse header. Responses are cached per variant computed from the headers listed inVary, and responses withVary: *are not cached.
- The in-memory cache strategy now honors the
- CAS Auth
cas_callback_urinow accepts an absolute URL, which is used as-is as the CAS service URL. This is useful when the gateway sits behind a proxy and the externally visible callback URL differs from the request path.
Console (Dashboard)
- Added a visual editor for permission policy statements. You can now build IAM policies by selecting resource types, actions (annotated with their access level), and conditions, without hand-writing the policy JSON.
- Configuration compatibility warnings now show each affected resource with its full business hierarchy path (for example, gateway group → service → route) instead of a bare resource ID, making it easier to locate the configuration that needs attention.
Fixes
Plugins
- Error Page
- Fixed issue: The plugin decided whether to render a custom error page based on the upstream status variable, which could replace error responses that actually came from the upstream service. It now classifies the response source — custom error pages are rendered only for errors generated by the gateway or plugins (such as upstream connection failures or plugin-rejected requests), while genuine error responses returned by the upstream are passed through unchanged so their original body is preserved.
- Feishu Auth
- Fixed issue: A client could supply a forged
X-Userinfoheader that was forwarded upstream. The plugin now clears any client-suppliedX-Userinfoheader before authentication, so upstream services only receive plugin-verified identity information.
- Fixed issue: A client could supply a forged
- CAS Auth
- Fixed issue: The login callback did not validate the signed initiation cookie, allowing a crafted callback request to drive the post-login redirect (CSRF / open-redirect). The callback now requires a valid signed initiation cookie and is rejected with
HTTP 401otherwise.
- Fixed issue: The login callback did not validate the signed initiation cookie, allowing a crafted callback request to drive the post-login redirect (CSRF / open-redirect). The callback now requires a valid signed initiation cookie and is rejected with
- DingTalk Auth
- Fixed issue: Authentication failures and transient upstream failures were not distinguished. The plugin now returns
HTTP 401for authentication errors andHTTP 503for transient DingTalk or upstream failures, with clearer error messages.
- Fixed issue: Authentication failures and transient upstream failures were not distinguished. The plugin now returns
- Authz Casdoor
- Fixed issue: The session cookie name was shared across Casdoor clients, so sessions for different clients could collide. The session cookie is now scoped per client (derived from
client_id).
- Fixed issue: The session cookie name was shared across Casdoor clients, so sessions for different clients could collide. The session cookie is now scoped per client (derived from
- Authz Keycloak
- Fixed issue: When static
permissionswere combined withhttp_method_as_scope, the derived method scope was written back to the reused plugin configuration, causing scopes to accumulate across requests. The permission list is now cloned before the method scope is appended.
- Fixed issue: When static
- GraphQL Limit Count
- Fixed issue: The query nesting depth was miscalculated, making depth-based rate limiting inaccurate. Depth is now computed from the true maximum nesting depth with fragment expansion. Content-Type matching also tolerates a charset parameter (such as
application/json; charset=utf-8), which was previously rejected.
- Fixed issue: The query nesting depth was miscalculated, making depth-based rate limiting inaccurate. Depth is now computed from the true maximum nesting depth with fragment expansion. Content-Type matching also tolerates a charset parameter (such as
- GraphQL Proxy Cache
- Fixed issue: Requests whose
Content-Typeincluded a charset parameter were not recognized as GraphQL requests. Content-Type matching is now charset-tolerant, along with additional nil guards, clearer error messages, and corrected log levels.
- Fixed issue: Requests whose
Data Plane
- Fixed issue: When multiple logging plugins captured the response body on the same request (for example,
http-loggerandfile-loggerboth withinclude_resp_bodyenabled), their response-body buffers could interfere with each other, producing truncated or mixed log output. Each logger now uses an isolated buffer.
3.9.17
Release Date: 2026-07-28
Upgrade Notes
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.
The Prometheus 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.
deny_code are validated more strictlyTwo more schemas now reject values that earlier versions accepted:
- OpenTelemetry plugin metadata accepts only scalar values — strings, numbers, and booleans — for
resourceattributes and forcollector.request_headers. Arrays and objects, which earlier versions accepted and then silently dropped at runtime, are now rejected withHTTP 400. - AI Aliyun Content Moderation requires
deny_codeto be an integer HTTP status code in the200–599range. AI AWS Content Moderation was already validated this way by the Control Plane; this release brings the Data Plane in line.
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
- OpenID Connect
- Added
set_raw_id_token_header. When enabled, the raw ID token issued by the identity provider is forwarded to the upstream service in theX-Raw-ID-Tokenheader, so the upstream can verify the token signature itself instead of trusting the gateway's decoded claims.
- Added
- Proxy Rewrite
headers.setandheaders.addnow accept an array of values for a single header name, which is sent upstream as separate header lines rather than one comma-joined line. This matters for upstream services that read only the first occurrence of a header name, or that parse repeated headers differently from a single comma-joined value.
- Kafka Logger
- Added
tlsfor connecting to Kafka brokers over TLS, withtls.verifycontrolling broker certificate verification. Previously the plugin could only connect in plaintext, so a broker listening on a TLS port received no logs at all.
- Added
- Rate-limiting and cache plugins
- Added Redis connection keepalive settings (
redis_keepalive_timeoutandredis_keepalive_pool) to the Redis and Redis Cluster policies in Limit Conn, Limit Req, and AI Cache, so operators can tune idle timeout and connection pool size.
- Added Redis connection keepalive settings (
- Body-buffering plugins
- Added
max_req_body_sizeandmax_resp_body_sizeto bound how much of a request or response body a plugin reads into memory, defaulting to 67108864 bytes (64 MiB). A request body larger than the limit is rejected, and a response body is truncated at the limit — except in Proxy Cache, where an oversized response is streamed through without being cached — so one large body cannot exhaust worker memory. Available in AI Proxy, AI Proxy Multi, AI Request Rewrite, the AI prompt plugins, Request Validation, OAS Validator, Body Transformer, Response Rewrite, Proxy Cache, gRPC Transcode, SOAP, and other plugins that buffer bodies.
- Added
- Logger plugins
- Added
max_req_body_bytesandmax_resp_body_bytesto the remaining logger schemas — ClickHouse Logger, Elasticsearch Logger, File Logger, Loggly, Loki Logger, SkyWalking Logger, Alibaba Cloud Logging (SLS), and Syslog — so the body size limits are validated at configuration time and shown in the Dashboard (see Upgrade Notes).
- Added
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-Pluginsdebug response header now lists the plugins that actually ran, in execution order and annotated with the phase each ran in (for examplelimit-count#access,response-rewrite#header_filter), instead of an unordered list of configured plugins.
Control Plane
- The
/api/fe-configendpoint is now served by the Control Plane binary and driven byconsole.*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 absolutehttp/httpsURL 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 /fooandPOST /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 withoutmethodsstill matches all methods. - Added an opt-in SSRF guard for outbound connections to user-configured endpoints. When
security.ssrf_protection.enableis 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
methodsare shown as All.
Fixes
Plugins
- AI Proxy, AI Proxy Multi, and AI Request Rewrite
- 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_timeand$llm_time_to_first_tokenwere logged in seconds (for example0.240) or as0, while successful responses were logged in milliseconds. Error-path values are now reported in milliseconds, consistent with the success path.
- Fixed issue: The requests the gateway sent to the LLM provider carried the client's own headers, including
- AI AWS Content Moderation and AI Aliyun Content Moderation
- Fixed issue:
deny_codeaccepted arbitrary numbers. It is now validated as an integer HTTP status code in the200–599range (default200), so an out-of-range value is rejected at configuration time. Forai-aws-content-moderationthe Control Plane already applied this validation, so this release brings the Data Plane in line; forai-aliyun-content-moderationit now applies on both sides.
- Fixed issue:
- OpenID Connect
- 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 theX-Userinfoheader on requests served from an existing session, so upstream services that parse the field as an array failed. Empty arrays now stay arrays.
- 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
- wolf-rbac
- Fixed issue: When the authentication service returned success without
userInfo, the client's ownX-UserId,X-Username, andX-Nicknameheaders 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.
- Fixed issue: When the authentication service returned success without
- Limit Count
- Fixed issue: With
window_type: slidingand 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.
- Fixed issue: With
- Limit Conn and Limit Req
- 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
- 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
- Fixed issue: The plugin metadata schema accepted non-scalar values for
resourceattributes andcollector.request_headers, which were then silently dropped at runtime. Such values are now rejected at configuration time.
- Fixed issue: The plugin metadata schema accepted non-scalar values for
Data Plane
- Fixed issue: With the
least_connload 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.envswhose 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 validateandadc synclater 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.
3.9.16
Release Date: 2026-07-10