serverless/serverless · error · ServerlessError

API_GATEWAY_CUSTOM_DOMAIN_DELETION_FAILED

API_GATEWAY_CUSTOM_DOMAIN_DELETION_FAILED

Error message

V1 - Failed to delete custom domain '${domain.givenDomainName}':\n${err.message}

What it means

Thrown by APIGatewayV1Wrapper.deleteCustomDomain when DeleteDomainNameCommand is rejected. AWS refuses to delete a domain while it still has base path mappings, so this error frequently surfaces during teardown or domain migration. The wrapper preserves the AWS-side rejection message.

Source

Thrown at packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v1-wrapper.js:128

          `V1 - Unable to fetch information about '${domain.givenDomainName}':\n${err.message}`,
          ServerlessErrorCodes.domains.API_GATEWAY_CUSTOM_DOMAIN_FETCH_FAILED,
          { originalMessage: err.message },
        )
      }
      Logging.logWarning(`V1 - '${domain.givenDomainName}' does not exist.`)
    }
  }

  async deleteCustomDomain(domain) {
    // Make API call
    try {
      await this.apiGateway.send(
        new DeleteDomainNameCommand({
          domainName: domain.givenDomainName,
        }),
      )
    } catch (err) {
      throw new ServerlessError(
        `V1 - Failed to delete custom domain '${domain.givenDomainName}':\n${err.message}`,
        ServerlessErrorCodes.domains.API_GATEWAY_CUSTOM_DOMAIN_DELETION_FAILED,
        { originalMessage: err.message },
      )
    }
  }

  async updateCustomDomain(domain) {
    const patchOperations = []

    if (
      domain.hasSecurityPolicyConfigured &&
      domain.domainInfo?.securityPolicy !== domain.securityPolicy
    ) {
      patchOperations.push({
        op: 'replace',
        path: '/securityPolicy',
        value: domain.securityPolicy,

View on GitHub (pinned to b9d7ea51c8)

Solutions

  1. Manually remove any base path mappings first: aws apigateway get-base-path-mapping --domain-name <dn> --base-path <bp>, then delete-base-path-mapping.
  2. If the domain is already gone (404), the error is safe to ignore - clear it from customDomain config or run sls remove again.
  3. Confirm apigateway:DELETE IAM permission is present.
  4. Re-run sls remove after the mappings clear; do not retry the same command unchanged.

Example fix

# manual cleanup before re-running sls remove
aws apigateway get-base-path-mappings --domain-name api.example.com
aws apigateway delete-base-path-mapping --domain-name api.example.com --base-path '(none)'
# then
sls remove
Defensive patterns

Strategy: validation

Validate before calling

// Before deleteCustomDomain, drain base path mappings first
async function safeDeleteDomain(wrapper, domain) {
  let mappings = []
  try { mappings = await wrapper.getBasePathMappings(domain) } catch {}
  for (const m of mappings) {
    domain.apiMapping = m
    await wrapper.deleteBasePathMapping(domain)
  }
  await wrapper.deleteCustomDomain(domain)
}

Try / catch

try {
  await wrapper.deleteCustomDomain(domain)
} catch (err) {
  const msg = err.cause?.originalMessage || err.message
  if (err.code === 'API_GATEWAY_CUSTOM_DOMAIN_DELETION_FAILED') {
    if (/NotFound|not found/i.test(msg)) return // already gone - benign
    if (/mappings|Conflict|still/i.test(msg))
      throw new Error('drain base path mappings before delete')
    throw err
  }
  throw err
}

Prevention

When it happens

Trigger: DeleteDomainNameCommand against a domain that does not exist (404); a domain that still has base path mappings attached (400 TooManyRequests / ConflictException); missing apigateway:DELETE permission; concurrent delete from another deploy.

Common situations: sls remove ran out of order - base path mappings were not removed first; partial failed deploy left dangling mappings; CI re-running teardown against an already-deleted domain; cross-stack references keep the domain alive.

Related errors


AI-assisted analysis of serverless/serverless@b9d7ea51c8 (2026-08-13). Data as JSON: /api/errors/70765b7c9ff637ee. Report an issue: GitHub.