# Startup Configuration Reference

This reference documents the startup configuration file that defines process-level settings such as listeners, resource-source connectivity, TLS, observability, cache and rate-limit backends, and the AISIX Cloud connection.

Models, caller API keys, provider keys, guardrails, cache policies, and observability exporters are not defined in the startup configuration. For an open-source AISIX gateway, a [`resources.yaml` file](#resource-source) or etcd supplies them. For a gateway connected to AISIX Cloud, the control plane supplies them.

AISIX accepts YAML, TOML, or JSON startup configuration files. Common files include:

* [`config.yaml`](#select-a-configuration-file): the local startup configuration file loaded by AISIX.
* [`config.example.yaml`](https://github.com/api7/aisix/blob/v1.3.0/config.example.yaml): a complete store-backed example for a gateway that runs without a control plane. You can copy or mount it as the loaded `config.yaml`.
* [`config.managed.yaml`](https://github.com/api7/aisix/blob/v1.3.0/config.managed.yaml): the gateway bootstrap configuration used when AISIX Cloud supplies resources at runtime.

The examples below use YAML because the packaged example configurations use YAML. TOML and JSON files can define the same startup fields. For the task-oriented setup flow, see [Startup Configuration](https://docs.api7.ai/ai-gateway/deployment/startup-configuration.md).

## Configuration Models[​](#configuration-models "Direct link to Configuration Models")

Every AISIX gateway uses a startup configuration file. The management model determines where its dynamic resources come from and which settings the operator owns directly.

| Gateway configuration                                 | Resource source            | Select with                                                   | How changes arrive                                                                                 |
| ----------------------------------------------------- | -------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| Open-source AISIX gateway using a resources file      | Declarative file           | `resources_file`                                              | Loaded at startup and reloaded on `SIGHUP`.                                                        |
| Open-source AISIX gateway using a configuration store | An etcd cluster you manage | `etcd`                                                        | Initial synchronization followed by etcd watch events.                                             |
| AISIX gateway connected to AISIX Cloud                | AISIX Cloud control plane  | `managed.enabled: true` and the generated connection settings | The gateway receives resources projected by the control plane through its managed etcd connection. |

The proxy, downstream, upstream, cache, rate-limit backend, and local observability settings remain gateway startup settings in every model. AISIX Cloud supplies dynamic resources such as models, keys, policies, and exporters; it does not replace these process-level settings.

For an open-source AISIX gateway, `admin.enabled` defaults to `true`, but the default `admin.addr` value of `127.0.0.1:0` selects an ephemeral port rather than a stable address for operators. To expose the [read-only Admin API](https://docs.api7.ai/ai-gateway/reference/admin-api/.md), set `admin.addr` to a private or loopback address with a fixed port and configure at least one `admin.admin_keys` value. Set `admin.enabled` to `false` to disable the listener. Configure resources through a [resources file](#resources-file) or by writing them directly to [etcd](#etcd-configuration-store). A gateway connected to AISIX Cloud never binds the gateway Admin API.

## Common Startup Configuration[​](#common-startup-configuration "Direct link to Common Startup Configuration")

The following example shows an open-source AISIX gateway that uses etcd, with common startup settings. The gateway can instead load dynamic resources from a `resources_file`; see [Resource Source](#resource-source).

config.yaml

```
etcd:

  endpoints:                          # etcd endpoints used to store dynamic gateway resources.

    - "http://127.0.0.1:2379"

  prefix: "/aisix"                    # Key prefix used by AISIX in etcd.

  # env_id: "ENVIRONMENT_ID"          # Optional environment scope for gateway resources in etcd.

  # user: "aisix"                     # Optional etcd username.

  # password_env: "AISIX_ETCD_PASSWORD" # Environment variable containing the etcd password.

  # dial_timeout_ms: 5000             # Optional bound on the whole dial to etcd — TCP connect, TLS

                                      # handshake, and authentication. Unset and 0 both apply no timeout.

  # request_timeout_ms: 5000          # Optional bound on a single request-response etcd call, including

                                      # the configuration read. Unset and 0 both apply no timeout. See

                                      # "etcd Configuration Store" before setting it.

  # tls:                              # mTLS settings; all three certificate fields are required together.

  #   ca_cert_file: "/etc/aisix/mtls/ca.crt"

  #   client_cert_file: "/etc/aisix/mtls/client.crt"

  #   client_key_file: "/etc/aisix/mtls/client.key"

  #   domain_name: "etcd.example.com" # Optional SNI and certificate-name override.



proxy:

  addr: "0.0.0.0:3000"                # Address for caller-facing proxy APIs.

  # request_body_limit_bytes: 0       # Maximum request body size. 0, the default, applies no cap.

                                      # Set a byte value to bound per-request memory. See

                                      # "Limit Request Body Size" below for rejection behavior.

  # thread_per_core: true             # Serve from independent workers, each with its own listener

                                      # and upstream connection pool. Omitted, it is on for Linux

                                      # and off elsewhere. Set false to serve from one shared

                                      # runtime. Applied at startup; restart to change.

                                      # See Deployment > Thread-per-Core Workers.

  # workers: 4                        # Number of proxy worker threads, in either serving mode.

                                      # Omitted, it follows the parallelism available to the

                                      # process, so a container CPU limit or a taskset affinity

                                      # mask sizes it. Must be at least 1. Applied at startup.

  # tls:                              # HTTPS certificate and key for the single listener addr describes.

  #   cert_file: "/etc/aisix/tls/proxy.crt"

  #   key_file: "/etc/aisix/tls/proxy.key"

  # listeners:                        # Several proxy listeners, each with its own optional TLS — for a

                                      # deployment that answers HTTPS and plaintext HTTP at the same time.

                                      # Set, this is the COMPLETE set of proxy listeners: only these

                                      # addresses are bound, addr above is ignored (it stays required, and

                                      # the gateway logs one line at startup saying so), and tls above must

                                      # be absent. Every listener serves the same routes and configuration.

                                      # See Deployment > TLS and mTLS for the rules and the env-var form.

  #   - addr: "0.0.0.0:3443"

  #     tls:

  #       cert_file: "/etc/aisix/tls/proxy.crt"

  #       key_file: "/etc/aisix/tls/proxy.key"

  #   - addr: "0.0.0.0:3000"

  # real_ip:                          # Caller IP resolution when AISIX runs behind trusted proxies.

  #   trusted_proxies:

  #     - "10.0.0.0/8"

  #   recursive: true

  #   header: "x-forwarded-for"

  # url_rewrites:                     # Entry-level path rewriting before routing (first match wins).

  #   - name: per-server-mcp-compat   # Optional label used in gateway logs.

  #     match: "^/mcp-servers/([^/]+)/mcp$"  # Regex on the raw request path.

  #     rewrite: "/mcp/$1"            # Replaces the matched portion; broken rules fail startup.

  #                                   # See Deployment > URL Rewriting for full semantics.



admin:

  enabled: false



observability:

  service_name: "aisix"               # Service name used in telemetry.

  log_level: "info"                   # Process log level.

  metrics:

    prometheus:

      enabled: true                   # Whether to expose Prometheus metrics.

      path: "/metrics"                # Metrics endpoint path.

      addr: "0.0.0.0:9090"            # Dedicated metrics/status listener address.



# managed:                            # Enable when this gateway uses the AISIX Cloud control plane.

#   enabled: true



# cache:                              # Redis connection used by cache policies that select Redis.

#   redis:

#     mode: "single"

#     url: "redis://127.0.0.1:6379"

#     # nodes: ["redis://10.0.0.1:6379"]        # Cluster mode seed nodes.

#     # sentinels: ["redis://10.0.0.1:26379"]   # Sentinel mode nodes.

#     # master_name: "mymaster"                  # Sentinel mode master group.

#     # username: "default"                      # Cluster or Sentinel data-node ACL user.

#     # password: "replace-me"                   # Cluster or Sentinel data-node ACL password.

#     # database: 0                              # Sentinel master database index.

#     # timeout_secs: 5                          # Bounds one Redis round-trip and one connection attempt. Minimum 1.

#     # For single mode, put credentials in the Redis URL.



ratelimit:

  backend: "memory"                   # Rate-limit counter backend. Use redis for shared counters across replicas.

  # redis:

  #   mode: "single"

  #   url: "redis://127.0.0.1:6379"

  #   # nodes: ["redis://10.0.0.1:6379"]        # Cluster mode seed nodes.

  #   # sentinels: ["redis://10.0.0.1:26379"]   # Sentinel mode nodes.

  #   # master_name: "mymaster"                  # Sentinel mode master group.

  #   # username: "default"                      # Cluster or Sentinel data-node ACL user.

  #   # password: "replace-me"                   # Cluster or Sentinel data-node ACL password.

  #   # database: 0                              # Sentinel master database index.

  #   # timeout_secs: 5                          # Bounds one Redis round-trip and one connection attempt. Minimum 1.

  #   # For single mode, put credentials in the Redis URL.

  # concurrency_ttl_secs: 300          # Redis backend only. Reclaims stale concurrency slots.



upstream:                             # Outbound calls the gateway makes. Values shown are the defaults.

  pool_idle_timeout_secs: 30          # Keep below the shortest idle timeout between the gateway and the provider.

  # timeout_ms: 6000000               # Default request deadline (6000 s) when neither the model nor its group/router sets `timeout`. 0 disables the backstop.

  # stream_timeout_ms: 0              # Default streaming chunk-gap deadline. 0 falls back to `timeout_ms`.

  # retries: 2                        # Attempts after a retryable failure, when neither the model nor its group/router sets `retries`. 0 disables retrying.

  # connect_timeout_ms: 5000          # Budget for DNS, TCP, and TLS. On /v1/realtime it also

                                      # covers the WebSocket upgrade handshake. 0 disables.

  # tcp_keepalive_secs: 60            # Idle time before the first keepalive probe. 0 disables.

  # tcp_keepalive_interval_secs: 30   # Interval between keepalive probes.

  # tcp_keepalive_retries: 5          # Unacknowledged probes before the connection is dropped.

  # pool_max_idle_per_host: 32        # Cap on idle connections per upstream host. Unset means

                                      # unbounded. Applies per worker to worker-local pools.

  # tls:                              # Which certificates the gateway trusts when it calls out.

  #   ca_file: "/etc/aisix/tls/private-ca.pem"    # Trusted in addition to the platform's own authorities.

  #   client_cert_file: "/etc/aisix/tls/client.crt"  # For supported HTTP upstreams that require mutual TLS.

  #   client_key_file: "/etc/aisix/tls/client.key"   # Required together with client_cert_file.

  #   verify: true                    # false disables verification where supported. Test environments only.



downstream:                           # Connection layer for inbound calls from clients. Values shown are the defaults.

  idle_timeout_secs: 0                # Close a connection idle between requests. 0 never closes.

  # sse_keepalive_interval_secs: 15   # Heartbeat interval on a silent streaming response. 0 disables.



# Optional deployment-wide override for AWS Bedrock guardrail traffic.

# bedrock_endpoint_url: "https://bedrock-runtime.us-east-1.amazonaws.com"
```

Changes to the startup configuration take effect after restarting the gateway.

### Limit Request Body Size[​](#limit-request-body-size "Direct link to Limit Request Body Size")

`proxy.request_body_limit_bytes` defaults to `0`, which applies no gateway-side cap. Providers accept larger requests than any single fixed default would allow, so setting a limit can reject a request the selected provider would otherwise serve. Earlier AISIX releases defaulted to `10485760` bytes (10 MiB).

Set a byte value when the gateway accepts requests directly from untrusted clients and must bound per-request memory. Alternatively, enforce a body limit at the load balancer or ingress in front of the gateway.

AISIX rejects an over-limit request with `413 Content Too Large`, but the caller is not guaranteed to receive that response. For a request that declares an over-limit `Content-Length`, AISIX first drains the body so it can return the `413` on the same connection. That drain is bounded by bytes and time. If the caller does not finish sending within those bounds, the gateway stops reading and the caller usually sees a closed or reset connection instead. A chunked body is rejected while the handler reads it and does not use the same drain path.

On the Content-Length path, the `aisix::body_limit` log entry and [`aisix_proxy_request_body_limit_rejections_total`](https://docs.api7.ai/ai-gateway/reference/metrics.md#request-metrics) distinguish a completed drain from a cap, timeout, or client read error. See [Access Logs and Request Correlation](https://docs.api7.ai/ai-gateway/observability/access-logs-and-request-correlation.md#diagnose-incomplete-or-refused-requests) for the fields and correlation workflow.

### Tune the Upstream Connection Layer[​](#tune-the-upstream-connection-layer "Direct link to Tune the Upstream Connection Layer")

The `upstream` block controls how the gateway opens and reuses connections when it calls out. The defaults suit a gateway that reaches providers directly over the internet. Tune them when the gateway sits behind a load balancer, NAT gateway, corporate proxy, or service mesh.

Its scope is wider than provider HTTP calls. The same settings govern the AWS SDK clients the gateway builds for Amazon Bedrock — both the provider path and the [Amazon Bedrock guardrail](https://docs.api7.ai/ai-gateway/traffic-controls/guardrails/aws-bedrock.md) — the Realtime WebSocket the gateway opens for `/v1/realtime`, and the object-store exporters that ship telemetry to S3, Google Cloud Storage, and Azure Blob Storage. An on-premises MinIO or an internal S3-compatible host sits behind the same private certificate authority and the same load-balancer and NAT hops as the rest of the gateway's outbound traffic, so it is configured from the same block. Not every setting reaches every stack, though, and a setting that does not reach one is silently inert there rather than rejected. See [Which Outbound Paths Each Setting Reaches](#which-outbound-paths-each-setting-reaches) below before tuning for a specific one.

`pool_idle_timeout_secs` is the setting to review first. The gateway reuses pooled connections, so this value must stay **below** the shortest idle timeout anywhere on the path to the provider. If an intermediate hop closes an idle connection sooner than the gateway expires it, the pool eventually hands out a connection the far end has already closed, and the request fails with a transport error against an otherwise healthy provider.

TCP keepalive keeps the connection visible to those same hops while a slow model produces its first token. A NAT or load-balancer idle timer can otherwise reap a connection that is legitimately waiting on a long-running request.

In thread-per-core serving, the default on Linux, normal proxy dispatches use worker-local connection pools, and `pool_max_idle_per_host` applies per worker. The idle connections a process holds toward one host can therefore reach `proxy.workers` times this cap. Request paths with dedicated clients, such as Provider Keys with custom TLS settings, keep separate process-wide pools and are not included in that multiplication. See [Thread-per-Core Workers](https://docs.api7.ai/ai-gateway/deployment/thread-per-core-workers.md) for the serving modes and their sizing consequences.

`timeout_ms` and `stream_timeout_ms` are deployment-wide defaults for the per-model `timeout` and `stream_timeout` fields. A request resolves its deadline from the first level that sets one: the target model, then the routing model or semantic router it was addressed through, then these defaults. The default of 6000 seconds is a backstop, not a responsiveness target — it exists so that an upstream that accepted the connection and then goes silent forever cannot hold a request open indefinitely, while never cutting a legitimate long request (deep-reasoning calls can run past ten minutes; set a per-model `timeout` to enforce anything tighter). A model opts out of the backstop with `timeout: 0`; setting `timeout_ms: 0` removes the default deployment-wide.

Zero-value behavior is field-specific. `upstream.timeout_ms: 0` removes the deployment-wide request deadline, and `timeout: 0` on a model stops that request-timeout fallback chain. In contrast, `upstream.stream_timeout_ms: 0` falls back to `upstream.timeout_ms`, while `stream_timeout: 0` on a model defers to the remaining stream-timeout and request-timeout chain. A zero stream-timeout value can therefore leave an effective streaming deadline in place.

#### Which Outbound Paths Each Setting Reaches[​](#which-outbound-paths-each-setting-reaches "Direct link to Which Outbound Paths Each Setting Reaches")

The gateway builds its outbound clients on more than one HTTP stack, and the stacks do not all expose the same knobs. A setting with no entry point on a given stack is not applied there and produces no error.

| Setting                                                                      | Provider HTTP calls | Amazon Bedrock (provider and guardrail)                                    | Realtime WebSocket (`/v1/realtime`)                                          | Object-store exporters (S3, GCS, Azure)                 |
| ---------------------------------------------------------------------------- | ------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------- |
| `connect_timeout_ms`                                                         | Applies             | Applies                                                                    | Applies, and covers more of the dial — see below                             | Applies                                                 |
| `pool_idle_timeout_secs`                                                     | Applies             | Applies                                                                    | Not applicable — each session dials its own connection and nothing is pooled | Applies, except the value `0` — see below               |
| `pool_max_idle_per_host`                                                     | Applies             | Not applied — the AWS SDK connection builder has no equivalent             | Not applicable — same reason                                                 | Applies                                                 |
| `tcp_keepalive_secs`, `tcp_keepalive_interval_secs`, `tcp_keepalive_retries` | Applies             | Not applied — same reason                                                  | Not applied — the WebSocket dial sets no socket options                      | Not applied — the object-store client has no equivalent |
| `timeout_ms`, `stream_timeout_ms`                                            | Applies             | Applies (enforced by the gateway, not the transport)                       | Applies as the session idle cap between events, not as a request deadline    | Not applicable — these bound a model request            |
| `tls.ca_file`                                                                | Applies             | Applies                                                                    | Applies                                                                      | Applies                                                 |
| `tls.client_cert_file`, `tls.client_key_file`                                | Applies             | Not applied — logged at `WARN` on startup                                  | Not applied — the WebSocket client presents no certificate                   | Not applied                                             |
| `tls.verify`                                                                 | Applies             | Not applied — certificates are still verified; logged at `WARN` on startup | Applies                                                                      | Applies                                                 |

Three rows deserve more than a table cell, because the obvious expectation is wrong in each:

* **`connect_timeout_ms` covers more of the Realtime dial than it does anywhere else.** On every other path the budget ends with TLS, before a request is sent. On `/v1/realtime` it also covers the HTTP 101 upgrade exchange, because that dial has no other deadline — the session idle cap only starts once the socket is up. An upstream that completes TLS and then never answers the upgrade therefore fails at `connect_timeout_ms` rather than holding the client's connection open until the kernel gives up. The failure takes the same branch an unreachable Realtime upstream already took: an `error` event of type `upstream_error` to the client, a `1011` close, and a `502` in the access log and the usage record.
* **`tcp_keepalive_*` does not reach the object-store exporters.** If you are tuning keepalive so that exports to an in-network MinIO survive a NAT or load-balancer idle timer, the settings have no effect there and nothing warns you. Tune the hop, or the exporter's own batching interval, instead.
* **`pool_idle_timeout_secs: 0` does not mean the same thing on both paths.** On provider HTTP calls, `0` means a pooled connection never expires. The object-store client has no way to express that, so `0` leaves it at the client default of 90 seconds. The direction is the conservative one — connections are still recycled — but it is a divergence, so do not read `0` as "never expire" when reasoning about an exporter.

The two `tls` rows for Bedrock are the only cells the gateway warns about, because disabling verification or configuring mutual TLS and silently not getting it is a security-relevant surprise. Everything else here is quiet.

`upstream.tls` supplies deployment-wide TLS settings for outbound connections. `ca_file` applies to HTTP request paths, the Realtime WebSocket, Amazon Bedrock, and object-store exports. The client-certificate and verification settings have narrower transport support.

Set `ca_file` when a private or enterprise certificate authority signed the certificate an upstream presents. Those certificates are trusted in addition to the platform's, so public providers stay reachable. See [TLS and mTLS](https://docs.api7.ai/ai-gateway/deployment/tls-and-mtls.md#trust-an-upstream-behind-a-private-certificate-authority) for the support matrix, per-endpoint trust, mutual TLS, and the settings for a Redis backend.

### Tune the Downstream Connection Layer[​](#tune-the-downstream-connection-layer "Direct link to Tune the Downstream Connection Layer")

The `downstream` block is the mirror of `upstream`: it controls the connections the gateway accepts from clients, or from a gateway placed in front of it.

`idle_timeout_secs` closes a connection that sits idle **between** requests — the response has been fully written and no next request has started. A request in flight is never interrupted, however long the model takes, and neither is a streaming response. The same deadline also bounds how long a freshly accepted connection may take to send its first request line and headers, so keep it comfortably above the round-trip time of the slowest client you serve.

It defaults to `0`, which never closes an idle connection and leaves that decision to the peer. That default is deliberate. Whatever sits in front of the gateway pools its own connections, and the node that closes first is the one that hands its peer a connection the peer still considers usable — the same failure `pool_idle_timeout_secs` avoids in the outbound direction. If you set `idle_timeout_secs`, keep it **above** the pool idle timeout of the node in front, and treat reclaiming idle connections as the reason to set it.

`sse_keepalive_interval_secs` emits an SSE comment on a streaming response while the model has produced nothing. Without it, a model that is slow to its first token looks like an abandoned connection to a proxy between the client and the gateway. The comment is ignored by every conforming SSE client, and applies to every streaming endpoint.

Both settings apply to the proxy listener. `idle_timeout_secs` applies to HTTP/1.1 connections.

### How the Timeouts Relate[​](#how-the-timeouts-relate "Direct link to How the Timeouts Relate")

Each setting below bounds a different phase of a request. They are not interchangeable.

| Setting                           | Where          | What it bounds                                                                                                                                                                                                                                                                        |
| --------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `upstream.connect_timeout_ms`     | Startup config | DNS, TCP, and TLS to the provider, before a request is sent. On `/v1/realtime`, the HTTP 101 upgrade handshake as well.                                                                                                                                                               |
| `upstream.timeout_ms`             | Startup config | Default for `timeout` when neither the model nor the routing model / semantic router it was addressed through sets one.                                                                                                                                                               |
| `upstream.stream_timeout_ms`      | Startup config | Default for `stream_timeout`; `0` falls back to `upstream.timeout_ms`.                                                                                                                                                                                                                |
| `upstream.pool_idle_timeout_secs` | Startup config | How long an unused connection to the provider stays in the pool, after a response completes.                                                                                                                                                                                          |
| `downstream.idle_timeout_secs`    | Startup config | How long an accepted client connection stays open with no request on it.                                                                                                                                                                                                              |
| `timeout`                         | Model          | The whole upstream call, from send to the last byte. Resolves model → routing model / semantic router → `upstream.timeout_ms`; `0` on the model disables it.                                                                                                                          |
| `stream_timeout`                  | Model          | The gap between two chunks of a streaming response — the wait for the first chunk and every gap after it, reset on each chunk. Not a cap on total stream duration. `0` or absent falls back to the group/router `stream_timeout`, then to `timeout`, then to the deployment defaults. |

The two pool settings manage connection *reuse*; the model settings bound a request that is *in flight*. TCP keepalive is a network-layer liveness probe and bounds nothing at the request layer.

Readers coming from a general-purpose proxy usually look for a connect / send (write) / read timeout trio. The mapping: `connect_timeout_ms` is the connect timeout; `stream_timeout` is the read timeout for streaming responses (same inter-chunk semantics), while non-streaming responses get the stricter end-to-end `timeout` instead; there is no separate send timeout because a stalled request upload is already bounded by the same end-to-end or streaming budget, and unacknowledged sends are cut earlier still at the TCP layer. A general-purpose proxy needs the trio because it has no per-request deadline concept — the gateway does, so the trio is covered with fewer knobs.

For a chain of gateways, the rule at every hop is the same: a node's client-side idle timeout must stay below the next node's server-side idle timeout, with margin. A violation of that ordering is what produces intermittent transport errors against an otherwise healthy path.

## Resource Source[​](#resource-source "Direct link to Resource Source")

Configure exactly one resource source. The selection affects how resources are loaded, but does not change the caller-facing [Proxy API](https://docs.api7.ai/ai-gateway/reference/proxy-api.md).

### Resources File[​](#resources-file "Direct link to Resources File")

An open-source AISIX gateway reads its dynamic resources from exactly one source. Set `resources_file` to load them from a declarative resources file:

config.yaml

```
resources_file: /etc/aisix/resources.yaml
```

`resources_file` and the `etcd` section are mutually exclusive — configuring both fails at startup. `resources_file` also cannot be combined with `managed.enabled: true`, because a gateway connected to AISIX Cloud receives resources from the control plane.

With `resources_file` set, the gateway loads the file at startup and exits if the initial load fails. It reloads the file on `SIGHUP`. See the [Resources File Reference](https://docs.api7.ai/ai-gateway/reference/resources-file.md) for every resource kind and field. Use the [Open-Source AISIX Gateway Quickstart](https://docs.api7.ai/ai-gateway/getting-started/gateway-quickstart.md) for the workflow and the [CLI Reference](https://docs.api7.ai/ai-gateway/reference/cli.md) for validation or etcd export. [Configuration Status](https://docs.api7.ai/ai-gateway/reference/config-status.md) shows what a running gateway loaded.

### etcd Configuration Store[​](#etcd-configuration-store "Direct link to etcd Configuration Store")

Set `etcd.endpoints` when configuration automation writes resources directly to etcd:

config.yaml

```
etcd:

  endpoints:

    - "https://etcd.example.com:2379"

  prefix: "/aisix"

  tls:

    ca_cert_file: "/etc/aisix/mtls/ca.crt"

    client_cert_file: "/etc/aisix/mtls/client.crt"

    client_key_file: "/etc/aisix/mtls/client.key"

    domain_name: "etcd.example.com"
```

To recover the last accepted configuration across a process restart, enable the on-disk snapshot cache and follow [Restart from Cached Configuration](https://docs.api7.ai/ai-gateway/cloud/offline-resilience.md#restart-from-cached-configuration). Configuring a cache path alone does not enable recovery.

| Field                     | Required | Description                                                                                                                                                                                                                                   |
| ------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `etcd.endpoints`          | Yes      | One or more etcd endpoints. An open-source AISIX gateway requires this field when `resources_file` is not set.                                                                                                                                |
| `etcd.prefix`             | No       | Key prefix containing AISIX resources. Defaults to `/aisix`; gateways sharing resources must use the same prefix.                                                                                                                             |
| `etcd.env_id`             | No       | Environment scope included in the key layout when the writer uses environment-scoped keys.                                                                                                                                                    |
| `etcd.user`               | No       | etcd username.                                                                                                                                                                                                                                |
| `etcd.password_env`       | No       | Name of the environment variable that contains the etcd password.                                                                                                                                                                             |
| `etcd.dial_timeout_ms`    | No       | Optional bound on the whole dial to an etcd endpoint, in milliseconds: the TCP connect, the TLS handshake above it, and the authentication exchange performed when `etcd.user` is set. Unset by default; unset and `0` both apply no timeout. |
| `etcd.request_timeout_ms` | No       | Optional bound on a single request-response etcd call, in milliseconds. Unset by default; unset and `0` both apply no timeout. Read the note below before setting it.                                                                         |
| `etcd.tls`                | No       | Client CA, certificate, key, and optional server-name override for an mTLS connection. The three certificate-file fields are required together.                                                                                               |

#### Configure etcd Timeouts[​](#configure-etcd-timeouts "Direct link to Configure etcd Timeouts")

Both timeout fields are unset by default, and `0` also means unbounded. Unlike model timeouts, these fields have no fallback chain.

| Setting              | Bounded Work                                                                                                                                          | Not Bounded                                    |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `dial_timeout_ms`    | Every attempt to connect: TCP, TLS, and authentication                                                                                                | etcd calls after the connection is established |
| `request_timeout_ms` | One request-response call, including the full-prefix configuration read, watch-creation handshake, Admin API reads, and a dial initiated by that call | The established watch stream                   |

The established watch stream is intentionally unbounded, so a quiet period does not force a reconnect. `request_timeout_ms` instead covers the handshake that creates the watch. If that handshake stalls, the gateway can keep serving its first snapshot without receiving later changes, even while `/status/config` reports the source as connected.

A `dial_timeout_ms` expiry is treated as an unreachable endpoint and enters the retry path. Credentials that etcd refuses on the initial connection are instead fatal. See [The Gateway Runs but the Proxy Port Refuses Connections](https://docs.api7.ai/ai-gateway/deployment/troubleshooting.md#the-gateway-runs-but-the-proxy-port-refuses-connections) for the observable difference.

Set `request_timeout_ms` only when a slow etcd call should fail fast. The configuration read grows with the resource set, so a value that is too short has different effects depending on when it expires:

| When the Read Expires                                  | Gateway Behavior                                                                            |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| Before the first configuration, with no snapshot cache | The gateway retries, but the proxy listener does not bind.                                  |
| Before the first live read, with a restored snapshot   | The proxy listener serves the snapshot, but configuration remains behind the store.         |
| During a read after watch reconnection                 | The gateway keeps serving its applied configuration while new store changes remain pending. |

In the last case, the log warns that the range read exceeded `etcd.request_timeout_ms`. `/status/config` reports `source.connected: false` and `last_failure.last_error_kind: fetch`, while `state` can remain `synced` because it describes the applied snapshot. Alert on `aisix_config_reload_failures_total{reason="fetch"}`. Unless you need a finite failure bound, leave the timeout unset.

#### Recover etcd Authentication[​](#recover-etcd-authentication "Direct link to Recover etcd Authentication")

When `etcd.user` is set, AISIX reuses the token returned during authentication. On a cluster that issues JWT tokens, an auth-store change or expiry at `--auth-token-ttl` can invalidate that token even when the configured username and password remain correct.

AISIX recognizes `invalid auth token`, `revision of auth store is old`, and `user name is empty` as stale-token responses. It discards the connection, authenticates again, and retries the call once. A credential failure such as `authentication failed, invalid user ID or password` is not retried as a stale token.

When `request_timeout_ms` is set, allow for this recovery path. The original attempt and retry each have separate dial and call budgets, so the operation can take up to four times the configured value.

Use [`aisix export`](https://docs.api7.ai/ai-gateway/reference/cli.md#export-resources-from-etcd) when migrating an existing store to a resources file.

### AISIX Cloud[​](#aisix-cloud "Direct link to AISIX Cloud")

Set `managed.enabled` to `true` when the gateway receives resources from AISIX Cloud. Use the generated gateway installation snippet rather than composing the bootstrap values manually; it supplies the correct control-plane endpoints and certificate material for the selected environment.

config.yaml

```
managed:

  enabled: true

  cp_base_url: "https://aisix.example.com"

  cp_etcd_endpoint: "aisix.example.com:443"

  mtls_dir: "/var/lib/aisix/mtls"

  dp_id_file: "/var/lib/aisix/dp_id"

  snapshot_cache_enabled: false

  snapshot_cache_path: "/var/lib/aisix/config_cache.json"

  heartbeat_interval_secs: 15
```

The connection certificate, private key, and CA can be supplied as one complete inline triplet (`cp_cert_pem`, `cp_key_pem`, and `cp_ca_pem`) or one complete file-path triplet (`cp_cert_file`, `cp_key_file`, and `cp_ca_file`). Do not mix inline and file forms within the same bundle. Keep credential material in environment variables or mounted secret files rather than committing it to the startup configuration.

| Field                             | Required | Description                                                                                                                                                                                                                                               |
| --------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `managed.enabled`                 | Yes      | Enables the AISIX Cloud connection and disables the gateway Admin API listener.                                                                                                                                                                           |
| `managed.cp_base_url`             | Yes      | AISIX Cloud control-plane origin used for heartbeat, telemetry, and budget checks.                                                                                                                                                                        |
| `managed.cp_etcd_endpoint`        | No       | Explicit managed etcd endpoint as `host:port`. When omitted, AISIX derives it from `cp_base_url`.                                                                                                                                                         |
| `managed.cp_ca_cert_file`         | No       | Additional CA bundle for the control-plane HTTP and etcd TLS connections, commonly needed with a private CA for an On-Premises control plane.                                                                                                             |
| `managed.mtls_dir`                | No       | Directory where AISIX persists the materialized mTLS bundle. Defaults to `/var/lib/aisix/mtls`.                                                                                                                                                           |
| `managed.dp_id_file`              | No       | File where AISIX persists the gateway ID. Defaults to `/var/lib/aisix/dp_id`.                                                                                                                                                                             |
| `managed.snapshot_cache_enabled`  | No       | Enable on-disk configuration recovery across restarts. Defaults to `false` whether the gateway receives resources from AISIX Cloud or reads them directly from etcd. Running gateways retain accepted configuration in memory regardless of this setting. |
| `managed.snapshot_cache_path`     | No       | Cache file location when persistence is enabled. Omitted or null uses `/var/lib/aisix/config_cache.json`; an empty string disables persistence. A path alone does not enable the cache.                                                                   |
| `managed.heartbeat_interval_secs` | No       | Heartbeat interval in seconds. Defaults to `15` and is clamped to the range `5`–`300`.                                                                                                                                                                    |

The managed connection appears as `source.type: "etcd"` in [Configuration Status](https://docs.api7.ai/ai-gateway/reference/config-status.md), because the gateway consumes the control plane's projected resources through its managed store connection. For the certificate issuance and installation workflow, see [Connect an AISIX Gateway](https://docs.api7.ai/ai-gateway/cloud/connect-a-gateway.md). For environment-variable forms of these fields, see [Environment Variables](https://docs.api7.ai/ai-gateway/reference/environment-variables.md#aisix-cloud-connection-variables).

## Select a Configuration File[​](#select-a-configuration-file "Direct link to Select a Configuration File")

When running the binary directly, provide a config path with `--config`.

```
aisix --config config.yaml
```

When running the official container image, mount the config file at `/etc/aisix/config.yaml`, or set `AISIX_CONFIG_PATH` to another path inside the container.

The following example mounts a production config file and asks the entrypoint to load it:

```
docker run \

  -v "$(pwd)/config.prod.yaml:/etc/aisix/config.prod.yaml:ro" \

  -e AISIX_CONFIG_PATH="/etc/aisix/config.prod.yaml" \

  ghcr.io/api7/aisix:1.3.0
```

If `AISIX_CONFIG_PATH` is unset, the entrypoint uses `/etc/aisix/config.yaml`.

## Loading Order[​](#loading-order "Direct link to Loading Order")

AISIX loads startup configuration in the following order:

1. Built-in default values.
2. File contents from the path selected by `--config`.
3. Environment-variable overrides with the `AISIX_` prefix.

Environment-variable overrides apply only to startup configuration fields. For override syntax and AISIX Cloud connection variables, see [Environment Variables](https://docs.api7.ai/ai-gateway/reference/environment-variables.md).
