Skip to content

Automating service quota increases for newly vended AWS accounts

16 minute read
Content level: Intermediate
0

How to pre-approve service quotas for newly vended AWS accounts, so teams stop filing the same increase cases. Covers Service Quotas organization request templates (native, no code) and the four limits that decide whether they are enough — then Terraform and boto3 snippets for the API route: the resource, the multi-Region trap that fails silently, global-quota handling, the GetServiceQuota pre-check, IAM, and where it goes in AFT.

Pre-approving service quotas for new AWS accounts: request templates, and what to do when they aren't enough

By Kiran Khambete and Krish Balaraman

The problem: New AWS accounts start with AWS default service quotas, which are deliberately conservative. When a team deploys real workloads into a freshly provisioned account — whether it was created through AWS Control Tower Account Factory for Terraform (AFT), a manual account request, or any other path — they routinely hit limits on VPCs per Region, EC2 vCPUs, ACM certificates, load balancers, and similar quotas within days. Each of these becomes a reactive Support case: file a ticket, wait, then continue building. If your organization provisions AWS accounts on a recurring basis, this pattern repeats on every new account — often the same handful of quotas, every time.

The native fix: AWS Service Quotas has a feature built for exactly this: quota request templates. Configure a template once at the AWS Organizations level, and every new account your organization creates gets those quota increases automatically — no pipeline, no custom code, nothing to maintain.

The gap: Templates aren't a complete answer for every organization. The cap is effectively far smaller than "10 quotas" suggests once you factor in Region (see below), there's no way to opt out of AWS opening a Support case for quotas needing manual review, one template applies the same targets to every account regardless of type, and it can't touch accounts that already exist. For organizations that hit any of these limits, the template is still worth enabling for whatever it covers — but it won't be the whole solution, and this article is upfront about exactly where it stops.

What it requires

RequirementDetail
AWS OrganizationsRequired, with all features enabled — not available if you use consolidated billing only
MembershipOnly applies to new accounts created as members of your organization
Who configures itSet from the management account — template association is an org-level, trusted-access action
Delegated administrationNot supported. Trusted access for Service Quotas can only be enabled from the management account, using Service Quotas itself. You can't hand this to a tooling or automation account
Quota must be adjustableOnly adjustable quotas can be added to a template. Many quotas are fixed — check the Adjustable field in the CLI output shown below before you plan around one
Region supportCommercial AWS Regions only. Not supported in China Regions or opt-in Regions (Regions that require manual activation)

Sources: Using Service Quotas request templates · Service Quotas and AWS Organizations

What it can't do — know these limits before you commit

  • The cap is 10 items, not 10 quotas. This is the limit people misjudge. The user guide phrases it as "up to 10 quotas," which reads as ten distinct quotas — but every template item carries its own Region (PutServiceQuotaIncreaseRequestIntoTemplate requires AwsRegion), and AWS's own Cloud Operations blog states it plainly: "If several limits must be set across several Regions, the request template limit of 10 items will not be sufficient." So the same quota in three Regions consumes three items. Four quotas across three Regions is twelve items — over the cap. If you run multi-Region, plan for roughly ten divided by your Region count distinct quotas, and confirm the count in your own organization as you add them.
  • One template per organization. Every new account gets the same target values — there's no way to give one OU or account type a different set of targets than another.
  • No control over Support-case creation. The template API accepts only a Region, service, quota, and desired value — there is no SupportCaseAllowed equivalent. On the direct RequestServiceQuotaIncrease API that flag defaults to True, and AWS "may create a support case for some quota increase requests." So for any templated quota that needs manual review, expect a Support case per new account, with no way to opt out. Auto-approved quotas are unaffected.
  • Not retroactive. Updating the template only affects accounts created after the update. Quotas on accounts that already exist are unaffected — you'd need a separate mechanism (such as re-invoking customizations in AFT, or a manual RequestServiceQuotaIncrease call) to apply new targets to existing accounts.

Setting it up

  1. Sign in to the AWS Management Console from your Organizations management account and open the Service Quotas console.
  2. In the navigation pane, expand Organization, then choose Quota request template.
  3. Under Template association, choose Enable.
  4. Under Added quotas, choose Add quota.
  5. Choose a Region, Service, Quota, and Desired quota value, then choose Add. Repeat for up to 10 items in total — each Region counts as its own item.

Before adding a quota, check two things about it. First, whether it's adjustable at all — fixed quotas can't be templated. Second, whether it's Region-scoped or global. Most quotas apply per Region, but a few are global: set once, and only from us-east-1 (or us-gov-west-1 in AWS GovCloud). Requesting a global quota from any other Region fails with an error stating the service does not exist. Both facts come from the same call:

aws service-quotas list-service-quotas --service-code <service-code> --region <region>

Check Adjustable (must be true) and GlobalQuota (true means global) per quota rather than assuming.

Doing it from the CLI instead

If you're already automating account vending, the whole setup has an API equivalent — which also makes it reviewable in version control rather than clicked into a console:

# Enable template association (this is what enables trusted access)
aws service-quotas associate-service-quota-template

# Add an entry — note --aws-region is required per entry; whether the same
# quota in multiple regions counts once or multiple times against the cap
# of 10 is unconfirmed (see note above) — verify before relying on it
aws service-quotas put-service-quota-increase-request-into-template \
  --service-code vpc \
  --quota-code L-F678F1CE \
  --aws-region eu-west-1 \
  --desired-value 50

# Review what's in the template
aws service-quotas list-service-quota-increase-requests-in-template

# Confirm the template is associated with your organization
aws service-quotas get-association-for-service-quota-template

Three errors map directly onto the requirements above, and are worth knowing before you debug blindly:

  • TemplatesNotAvailableInRegionException — you're in a Region where templates aren't supported.
  • NoAvailableOrganizationException — the calling account isn't a member of an organization.
  • AWSServiceAccessNotEnabledException — trusted access for Service Quotas isn't enabled yet.

Backing it out

Disabling is safe and non-destructive. From the same Quota request template page choose Disable (or call disassociate-service-quota-template). New accounts then receive AWS default quota values for all quotas, and disabling the association doesn't delete the entries from the template — you can re-enable later, or keep editing the entries while it's disabled.

Deciding if this is enough on its own

Your situationRecommendation
10 or fewer quota-and-Region items cover most of your reactive case volume, and the same targets work for every accountThe template is your complete solution — no further tooling needed
More than 10 items once you multiply quotas by Regions, or you need different targets per account type or OUThe template covers whatever fits under the cap; you'll need a supplementary mechanism for the rest
Your priority quotas mostly require manual AWS review rather than auto-approvalThe template still helps — the requests are raised predictably at account creation instead of mid-deployment — but it will not eliminate the Support cases
You need to retroactively apply new quota targets to accounts that already existThe template can't do this — you'll need a separate mechanism regardless of entry count
You're not using AWS OrganizationsThe template is unavailable to you entirely

When the template isn't enough

If you're over the item cap, need different targets per account type, or need to reach accounts that already exist, the underlying mechanism is the same: call the Service Quotas API yourself, triggered by whatever provisions your accounts. You don't need to invent the pattern — AWS documents it, and publishes working implementations.

The pattern:

  1. GetServiceQuota first. Compare the applied value against your target and skip if it's already there. This avoids pointless requests and avoids burning your allowance of quota-increase requests — exceeding that allowance raises QuotaExceededException, which is easy to misread as "already at target."
  2. RequestServiceQuotaIncrease for the gap. Decide SupportCaseAllowed deliberately, per quota. True (the default) lets AWS open a Support case for quotas needing manual review. False means AWS "does not open a support case and updates the request status to Not approved" — quieter, but someone has to notice and follow up, so build that step into your process rather than assuming it.
  3. Adjustable quotas only, and one owner per quota — if the org template and your own automation both request the same quota, expect a conflict rather than a merge.

Two AWS-published starting points, so you're not writing this from scratch:

If you run AFT specifically: the same API calls belong in a global customizations module, which AFT applies automatically after provisioning to every account it vends. AFT is also how you solve retroactivity — the aft-invoke-customizations Step Function can re-run customizations against accounts that already exist, with bypass_steps: ["provisioning_bootstrap"] to skip provisioning and run customizations only.

Building it yourself: the parts that matter

Why you'd build this at all. Four capability gaps push teams off the template. Work out which of them actually applies to you before writing any code — the answer decides how much you need to build:

  • The cap binds sooner than it looks. Ten items spread across a three-Region estate is three or four distinct quotas. Most platform teams have more than that on their recurring-case list.
  • One template means one set of targets. A data/AI OU that needs high Bedrock throughput and a general-purpose OU that needs none get identical treatment. If your account types genuinely differ, only your own automation can express that.
  • Accounts you've already vended stay at defaults. The template is new-accounts-only by design. For most organizations the existing estate is the larger number, and nothing about the template will ever reach it — this is usually the reason teams end up here.
  • You can't suppress the Support case. The template API exposes no SupportCaseAllowed control, so a quota needing manual review generates a case per new account. Calling the API yourself lets you set it to False and follow up on your own schedule instead.

There's a fifth, softer reason: quotas become reviewable code alongside the IAM roles, guardrails, and networking you already provision, rather than console state that leaves with the person who configured it.

If none of the four applies to you, stop at the template. Custom automation is a maintenance liability you don't need when a five-minute console setup already covers your case — and every line below is something you'll own and have to keep working.

What follows is deliberately not a complete module. These are fragments — no variables, no outputs, no backend configuration, no error handling around the edges. Each one exists to show a single decision you'll have to make, and the traps that aren't obvious until you hit them. They're drawn from AWS documentation and the Terraform provider's own reference rather than from a running deployment, so treat them as a starting point: assemble your own version and validate it in a sandbox account before it goes anywhere near a vending pipeline.

1. The resource, at its simplest. The Terraform AWS provider can request a quota increase declaratively:

resource "aws_servicequotas_service_quota" "vpcs_per_region" {
  service_code = "vpc"
  quota_code   = "L-F678F1CE"  # always confirm with list-service-quotas
  value        = 25            # your number, derived from your own usage
}

Three behaviors worth knowing before you rely on it. A request is submitted only when your value is higher than the current applied value — a lower value is a silent no-op, not an error. The resource adopts an existing quota on create without an explicit import. And a destroy does not lower the quota; quota decreases, where supported at all, are a separate Support-routed request.

2. Multi-Region — this is the trap. Quotas are per-Region for most services, so you'll want to fan out. The mistake is computing a Region and then not passing it to the resource, which sends every request to the provider's default Region while looking correct:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"  # per-resource `region` requires v6.0+
    }
  }
}

resource "aws_servicequotas_service_quota" "regional" {
  for_each = {
    for pair in setproduct(keys(var.quota_targets), var.regions) :
    "${pair[0]}_${pair[1]}" => { quota = var.quota_targets[pair[0]], region = pair[1] }
  }

  region       = each.value.region  # ← omit this and the fan-out silently collapses
  service_code = each.value.quota.service_code
  quota_code   = each.value.quota.quota_code
  value        = each.value.quota.target_value
}

The region argument on a resource arrived in AWS provider v6.0. On v5 it does not exist, so a multi-Region fan-out needs explicit provider aliases instead — and aliases can't be generated from a variable, so your Region list stops being data and becomes code. Check your pinned version before designing around either approach.

3. Global quotas must not be looped. A few quotas are global: GlobalQuota: true in list-service-quotas output. These are set once and accepted only in us-east-1 (or us-gov-west-1 in GovCloud); requesting one in any other Region returns "The request failed because the specified service does not exist." Keep these in a separate resource block with no Region fan-out, and check the flag per quota rather than assuming from the service name — CloudFront's distributions-per-account quota, for instance, is account-level rather than per-Region.

4. Always pre-check before requesting. Whether you script this or wrap it in Terraform, read the current value first:

current = sq.get_service_quota(
    ServiceCode=service_code, QuotaCode=quota_code
)["Quota"]["Value"]

if current < desired_value:
    sq.request_service_quota_increase(
        ServiceCode=service_code,
        QuotaCode=quota_code,
        DesiredValue=desired_value,
        SupportCaseAllowed=False,  # decide this per quota, deliberately
    )

Handle ResourceAlreadyExistsException (an identical request is already pending — likely if an org template covers the same quota) and TooManyRequestsException (back off). Do not treat QuotaExceededException as "already at target" — it means you've exhausted your allowance of quota-increase requests, and swallowing it will hide real failures.

5. IAM. The role running this needs at minimum:

servicequotas:GetServiceQuota
servicequotas:ListServiceQuotas
servicequotas:RequestServiceQuotaIncrease

If you leave SupportCaseAllowed at its default of True, a missing Support-side dependency surfaces as DependencyAccessDeniedException rather than a plain access error — worth recognizing, because it sends people debugging the wrong permission.

6. Where it goes. In AFT, place Terraform under the terraform/ folder of your global customizations repository (separate from the account customizations repository — confirm the exact name in your own deployment) and Python under api_helpers/python/. Global customizations run automatically after the provisioning stage on every account AFT vends.

What's deliberately missing here: target values, a full quota-code reference table, variable and output definitions, and any script entry point. Target values should come from your own p95 usage, not from an article. Quota codes go stale, so generate your own list with list-service-quotas. And the AFT invocation contract for api_helpers scripts — how the Region and vended account ID reach your code — is something to confirm in your own deployment rather than copy from anyone's example.

Verifying it worked

  • In the Service Quotas console, under Organization → Quota request template, confirm Template association shows Enabled.
  • Provision (or wait for) a new account in your organization.
  • In that account's Service Quotas console, check the Applied quota value for each quota you added to the template — it should already reflect your target, with no manual request needed.
  • If a quota required manual AWS review rather than auto-approval, it will show as pending in that account until AWS completes the review.

A note on guardrails

Default quotas are, in practice, the last blunt limit on blast radius in a brand-new account — a leaked credential or a runaway loop can only do so much damage inside a 5-vCPU ceiling. Raising quotas org-wide on every account gives some of that away. It's usually the right trade, but pair it with controls that aren't quota-shaped: AWS Budgets with alerts, cost anomaly detection, and SCPs on the OUs where the raised targets apply. Set template values to what teams genuinely need on day one, not to the largest number you can imagine ever needing.

Summary

Before building custom automation for quota pre-approval, check whether the native Service Quotas org template already covers your case — it's a five-minute console setup, or a handful of CLI calls, with nothing to maintain afterwards.

Be precise about what it changes, though. For quotas that auto-approve, it removes the reactive Support case entirely. For quotas that need manual AWS review, it doesn't remove the case — it moves it, so the request is raised predictably at account creation instead of being discovered when a deployment fails. Both are worth having, but only the first is a reduction in case volume, and the template gives you no way to suppress the second.

And size the cap correctly before you plan around it. AWS documents "up to 10 quotas," but every template item carries its own Region, so ten items across a three-Region estate is closer to three distinct quotas than ten.