Network QoS

MinIO AIStor Quality of Service is enforced at two layers:

  • API QoS — per-bucket / per-prefix / per-API request rate (rps) and concurrency controls, enforced inside the MinIO AIStor server. See Bucket-level Quality of Service.
  • Network QoS — per-bucket / per-prefix and per-access-key bandwidth (bytes per second) and request-rate controls, enforced by minwall, the network-layer component of AIStor that runs in front of (or co-located with) each MinIO AIStor server.

The two layers stack and complement each other. This page covers Network QoS.

Capability API QoS Network QoS (minwall)
Per-API request rate (rps)
Per-API concurrency limit
Per-bucket / per-prefix bandwidth (bytes/sec)
Per-access-key bandwidth (bytes/sec)
Per-access-key request rate
Deny all access for a specific access key
Deny anonymous access at the boundary
Deny all access to a bucket or prefix at the boundary
Load-balance across cluster nodes with local affinity
Enforces before authentication

Most operators throttle requests per second (often referred to as IOPS). That can be enforced either by API QoS or by Network QoS depending on whether you want the request rejected before authentication and EC pipeline setup, or after. Bandwidth (bytes per second) is enforced only by Network QoS.

Why bandwidth and pre-auth limits belong in the network layer

Throttling is only meaningful when it is applied before the cluster commits resources to a request. By the time a request reaches the MinIO AIStor process, the expensive work has already started or is imminent:

  • The TCP connection and TLS session are established
  • The request line and headers have been parsed
  • The S3 signature (HMAC-SHA256, computed over the streamed body for streaming SigV4) is verified
  • For PUT / POST, the body is streaming from the kernel socket buffer into the erasure-coded write pipeline, which fans out to N drives across the EC set
  • For GET / HEAD, the object has been located, enough erasure shards have been decoded to begin streaming, and the response socket is filling

A cap installed after the connection is accepted can only slow the data path that is already running. It cannot recover the CPU spent on TLS and signature verification, the disk I/O that erasure coding has scheduled, or the network buffers that the kernel has already committed. In a clustered deployment, each storage node only sees its own slice of the traffic, so an in-process bandwidth cap cannot honestly enforce a cluster-wide per-bucket budget without inter-node coordination.

Network QoS addresses both problems:

  • It terminates the client connection and reads the API verb and bucket from the request, then paces the body in both directions against a per-bucket token bucket:
    • For uploads, it reads from the client at the configured uploadRatePerSec. The MinIO AIStor server sees only the paced stream — it never has to handle a faster ingress than the rule allows.
    • For downloads, it paces the response bytes it forwards to the client at downloadRatePerSec. TCP back-pressure flows through the proxy pipe and stalls the backend, so the storage node does not keep producing data faster than the client is allowed to consume it.
  • For request-rate rules, it counts the request against a per-bucket, per-API token bucket and returns 429 Too Many Requests before the request reaches MinIO AIStor — before authentication and EC pipeline setup — when the bucket is empty.
  • Network access rules (anonymous: "deny", access: "deny") reject matching traffic at the boundary, so the storage process pays no cost for traffic that was always going to be refused.

Install

minwall release artifacts are published under dl.min.io/aistor/minwall/release/ for the two supported Linux architectures:

Architecture Release directory Latest binary
Linux AMD64 linux-amd64/ minwall
Linux ARM64 linux-arm64/ minwall

Each release directory contains the latest binary, the corresponding RELEASE.<timestamp> build, SHA-256 checksums, and minisign / GPG signatures. A .fips variant is also published for FIPS-validated builds.

Install the latest binary:

Verify the download against minwall.sha256sum from the same release directory before installing on production hosts.

Deployment topology

Network QoS runs as a reverse proxy. Clients connect to it (S3, SFTP, Console, or health endpoints) and it forwards to one or more MinIO AIStor backends using a least-connections strategy with optional local affinity.

The recommended topology is to co-locate minwall with each MinIO AIStor server on the same host. With local affinity set, minwall sends up to affinity concurrent requests to the MinIO AIStor server on the same node before spilling over to peer backends, which keeps short-lived requests on the loopback while preserving cluster-wide failover. In this topology, the byte-pacing for bandwidth rules happens entirely within the local minwall process — the MinIO AIStor process on the same node sees only the paced byte stream.

TLS termination
When Network QoS is in the data path, terminate client TLS there so it can read the API verb and bucket from the request and apply the correct rule. Re-encrypt to the MinIO AIStor backend using the minio.tls client settings if backend encryption is required.

Rule schema

Network QoS supports two independent rule blocks. Bucket rules gate traffic by object path; access-key rules gate it by authenticated identity. Both are evaluated on every request and a request must pass both. Either block can be used on its own, or they can be combined.

Bucket rules

Bucket rules are declared under minio.rules.buckets[]. Bucket-specific or prefix-specific rules take precedence over the wildcard * rule. If a bucket rule omits a field (for example, bandwidth), the wildcard rule’s value applies.

Field Type Description
bucket string Bucket name, optionally with a prefix. Supports the * wildcard.
label string Human-readable label for the rule.
access string "allow" or "deny". Reject matching requests in the network layer.
anonymous string "allow" or "deny". Override the global anonymous setting for this bucket.
bandwidth.downloadRatePerSec string Maximum bytes/sec served toward the client. Accepts SI/IEC units (10MB, 4MiB, 100KB).
bandwidth.uploadRatePerSec string Maximum bytes/sec accepted from the client. Same unit format.
requests[].api string API name (s3.GetObject, s3.PutObject, sftp.GET, etc.). Supports the * wildcard.
requests[].rate integer Requests per second for that API.
requests[].burst integer Burst above rate allowed by the token bucket.

Access-key rules

Access-key rules are declared under minio.rules.accessKeys[]. They apply per authenticated identity, evaluated in addition to any matching bucket rule. The access key is extracted from the SigV4 Authorization header for signed requests, or from X-Amz-Credential for presigned URLs. Anonymous requests are not affected by access-key rules; control unauthenticated access through rules.anonymous and per-bucket anonymous overrides.

Patterns are matched in the order listed, so place specific entries before wildcards: an exact key for the most specific rule, glob patterns such as team-* for groups of keys, and a literal "*" entry as a catch-all default.

Field Type Description
accessKey string Exact access key, a glob pattern (team-*), or "*" for a catch-all default.
label string Human-readable label for the rule.
access string "allow" or "deny". Set to "deny" to immediately reject every request signed with this key.
bandwidth.downloadRatePerSec string Per-key download cap in bytes/sec. Same unit format as bucket rules.
bandwidth.uploadRatePerSec string Per-key upload cap in bytes/sec.
requests[].api string API name throttled for this key (s3.GetObject, s3.PutObject, etc.).
requests[].rate integer Requests per second for that API and key.
requests[].burst integer Burst above rate allowed by the token bucket.
When a request matches both a bucket bandwidth rule and an access-key bandwidth rule, the body is paced through both token buckets in series and the effective throughput is the more restrictive of the two.

Examples

Per-API request-rate throttling with Network QoS

The most common use case. The following rule caps s3.PutObject on mybucket at 100 rps with a burst of 100, and s3.ListObjectsV2 at 10 rps with a burst of 10. Other APIs are not limited by this rule.

version: "v1"
minio:
  endpoint:
    s3: "http://minio{1...4}:9000"
  rules:
    buckets:
      - bucket: "mybucket"
        label: "rate-limit-mybucket"
        requests:
          - api: "s3.PutObject"
            rate: 100
            burst: 100
          - api: "s3.ListObjectsV2"
            rate: 10
            burst: 10

When the bucket is empty, Network QoS returns 429 Too Many Requests before the request reaches the MinIO AIStor backend.

Bandwidth throttling for a single bucket

Cap both upload and download bandwidth at 4 MiB/s for any request to bucket:

version: "v1"
minio:
  endpoint:
    s3: "http://minio{1...4}:9000"
  rules:
    buckets:
      - bucket: "bucket"
        label: "bandwidth-throttle-bucket"
        bandwidth:
          downloadRatePerSec: "4MiB"
          uploadRatePerSec: "4MiB"

Combined bandwidth and request-rate with a default for everything else

Throttle mybucket* at 10 MB/s and 10 rps per write API, then apply a 1 MB/s and 100 rps default to every other bucket through the * wildcard rule:

version: "v1"
minio:
  endpoint:
    s3: "http://minio{1...4}:9000"
  rules:
    buckets:
      - bucket: "mybucket*"
        label: "throttle-mybucket"
        bandwidth:
          downloadRatePerSec: "10MB"
          uploadRatePerSec: "10MB"
        requests:
          - api: "s3.PutObject"
            rate: 10
            burst: 10
          - api: "s3.ListObjectsV2"
            rate: 10
            burst: 10
      - bucket: "*"
        label: "default-rule"
        bandwidth:
          downloadRatePerSec: "1MB"
          uploadRatePerSec: "1MB"
        requests:
          - api: "s3.GetObject"
            rate: 100
            burst: 100
          - api: "s3.PutObject"
            rate: 100
            burst: 100

Per-access-key bandwidth, request rate, and revocation

Revoke a compromised key at the network boundary, cap an ingest service account at 200 MiB/s upload and 500 s3.PutObject rps, share a 50 MiB/s budget across every key in the team-* group, and apply a 10 MiB/s default to anyone else:

version: "v1"
minio:
  endpoint:
    s3: "http://minio{1...4}:9000"
  rules:
    accessKeys:
      - accessKey: "AKIA-REVOKED"
        label: "deny-compromised-key"
        access: "deny"
      - accessKey: "AKIA-INGEST"
        label: "ingest-key-bandwidth"
        bandwidth:
          uploadRatePerSec:   "200MiB"
          downloadRatePerSec: "50MiB"
        requests:
          - api: "s3.PutObject"
            rate: 500
            burst: 1000
      - accessKey: "team-*"
        label: "team-shared-limits"
        bandwidth:
          uploadRatePerSec:   "50MiB"
          downloadRatePerSec: "50MiB"
      - accessKey: "*"
        label: "default-per-key-limits"
        bandwidth:
          uploadRatePerSec:   "10MiB"
          downloadRatePerSec: "10MiB"

The revoked key is rejected with 403 Access Denied before MinIO AIStor sees the request. The ingest key’s burst write traffic is paced to 200 MiB/s in the proxy regardless of which bucket it targets. Every other authenticated request falls through to the * catch-all.

Access-key rules and bucket rules can be combined in the same config — for example, a per-bucket 100 MB/s download cap with a stricter 10 MB/s per-key cap means individual keys cannot saturate the bucket budget even when several of them target the same data.

Co-located deployment with local affinity

A minimal config that places Network QoS in front of a 4-node cluster, terminates S3 on :8000, and prefers the local MinIO AIStor instance for up to 100 concurrent requests before load-balancing to peer nodes:

version: "v1"
server:
  address:
    s3: ":8000"
    health: ":8080"
minio:
  affinity: 100
  endpoint:
    s3: "http://minio{1...4}:9000"
  health:
    path: "/minio/health/ready"
    interval: "10s"
    timeout: "10s"
  rules:
    buckets:
      - bucket: "*"
        label: "global-defaults"
        bandwidth:
          downloadRatePerSec: "100MiB"
          uploadRatePerSec: "100MiB"
        requests:
          - api: "s3.PutObject"
            rate: 500
            burst: 500
          - api: "s3.GetObject"
            rate: 500
            burst: 500

Point S3 clients at the minwall endpoint (:8000 in this example) rather than at MinIO AIStor directly. External load-balancer health probes should target the :8080 endpoint.

Choosing where to enforce a rule

You want to … Enforce with
Cap bytes per second (upload or download) for a bucket or prefix Network QoS (buckets[].bandwidth) — only available at this layer
Cap bytes per second for a specific access key (or group of keys) Network QoS (accessKeys[].bandwidth) — only available at this layer
Cap requests per second for an API and reject before authentication Network QoS (buckets[].requests)
Cap requests per second for an API on a specific access key Network QoS (accessKeys[].requests)
Cap requests per second for an API after authentication, with bucket/prefix granularity inside the cluster API QoS (rps limit)
Cap concurrent in-flight requests for an API API QoS (concurrency limit)
Revoke a compromised access key at the boundary without re-deploying MinIO AIStor Network QoS (accessKeys[].access: "deny")
Deny anonymous traffic at the boundary Network QoS (anonymous: "deny")
Deny all access to a bucket or prefix at the boundary Network QoS (buckets[].access: "deny")
Load-balance S3 traffic across the cluster with local affinity Network QoS

A typical production deployment uses both layers: Network QoS enforces bandwidth and rejects egregious traffic patterns before they consume backend CPU, while API QoS protects internal resources from contention even when traffic is well-behaved at the network layer.

Further reading