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
- Manually remove any base path mappings first: aws apigateway get-base-path-mapping --domain-name <dn> --base-path <bp>, then delete-base-path-mapping.
- If the domain is already gone (404), the error is safe to ignore - clear it from customDomain config or run sls remove again.
- Confirm apigateway:DELETE IAM permission is present.
- 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
- Always remove base path mappings before deleting the domain.
- Make teardown idempotent - tolerate 404 on the domain.
- Grant apigateway:DELETE on /domainnames/*.
- Avoid running two teardowns concurrently on the same domain.
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
- API_GATEWAY_CUSTOM_DOMAIN_CREATION_FAILED
- API_GATEWAY_CUSTOM_DOMAIN_FETCH_FAILED
- API_GATEWAY_CUSTOM_DOMAIN_UPDATE_FAILED
- API_GATEWAY_BASE_PATH_MAPPING_CREATION_FAILED
- API_GATEWAY_BASE_PATH_MAPPING_FETCH_FAILED
AI-assisted analysis of serverless/serverless@b9d7ea51c8 (2026-08-13).
Data as JSON: /api/errors/70765b7c9ff637ee.
Report an issue: GitHub.