This check flags any API Gateway custom domain whose SSL/TLS certificate expires within 30 days. An expired cert breaks every client that talks to your API over HTTPS. Move the domain to an ACM-managed certificate so AWS renews it automatically, and add monitoring so you are never caught off guard.
API Gateway custom domains let you serve your APIs from a branded hostname like api.example.com instead of the default d-xxxx.execute-api.region.amazonaws.com endpoint. Behind that domain sits a TLS certificate that proves your identity and encrypts traffic. When that certificate is days away from expiring, you are looking at a hard outage on the horizon.
The apigw_certexpiring check looks at the certificates attached to your API Gateway custom domains and raises a finding when any of them expires within the next 30 days. It is an early warning, not a post-mortem. The whole point is to give you enough runway to act before clients start failing handshakes.
What this check detects
Lensix inspects each API Gateway custom domain in your account and reads the expiry date of the certificate bound to it. If the time remaining until expiration is 30 days or less, the domain is reported as failing.
This applies to both flavors of API Gateway custom domains:
- REST (v1) custom domains managed through
apigateway - HTTP and WebSocket (v2) custom domains managed through
apigatewayv2
The certificate itself can come from AWS Certificate Manager (ACM) or be an imported certificate that you uploaded yourself. The distinction matters a lot for how you fix the problem, which we will get to below.
Note: ACM has two cert types. Certificates that ACM issues and validates are renewed automatically as long as the validation method (DNS or email) still resolves. Certificates you import into ACM are never auto-renewed. ACM does not own the private key generation, so you are on the hook to replace them before expiry.
Why it matters
When a TLS certificate expires, the browser or client cannot complete the handshake. There is no graceful degradation. Every request that reaches your custom domain fails with an error like certificate has expired or SSL_ERROR_EXPIRED_CERT_ALERT.
Here is what that looks like in practice:
- Customer-facing outage. Mobile apps, single-page apps, and partner integrations that hit
api.example.comall break at once. There is no retry that saves you, because the failure is at the transport layer. - Silent partner breakage. B2B integrations rarely have the same monitoring you do. A partner's nightly job hits your API, fails the handshake, and you find out days later through a support ticket.
- Cascading internal failures. If internal services route through the same custom domain, a cert expiry can take down workflows that are several hops away from anything user-facing, making the root cause harder to trace.
- Reputation and trust. An expired certificate is a textbook example of operational neglect. Security-conscious customers notice.
The frustrating part is that certificate expiry is one of the most predictable failures in all of infrastructure. You know the exact date and time it will happen, months in advance. There is no excuse for being surprised by it, yet it remains a top cause of self-inflicted outages.
Warning: Cert expiry tends to hit at the worst time. Certificates issued in a burst during a project launch all expire together a year later, often during a holiday or a weekend when the team that set them up has moved on. Treat clustered expiry dates as a risk in their own right.
How to fix it
The right fix depends on whether the certificate is ACM-managed or imported. Start by identifying the domain and its certificate.
Step 1: Find the affected custom domain and certificate
For a REST (v1) custom domain:
aws apigateway get-domain-names \
--query 'items[].{domain:domainName,cert:certificateArn,regionalCert:regionalCertificateArn}' \
--output table
For an HTTP/WebSocket (v2) custom domain:
aws apigatewayv2 get-domain-names \
--query 'Items[].{domain:DomainName,config:DomainNameConfigurations[].CertificateArn}' \
--output json
Once you have the certificate ARN, check its details and expiry:
aws acm describe-certificate \
--certificate-arn arn:aws:acm:us-east-1:111122223333:certificate/abcd1234 \
--query 'Certificate.{Status:Status,Type:Type,NotAfter:NotAfter,RenewalEligibility:RenewalEligibility,InUseBy:InUseBy}' \
--output json
Look at the Type field. AMAZON_ISSUED means ACM manages renewal. IMPORTED means you own the renewal.
Step 2a: If the certificate is ACM-issued (AMAZON_ISSUED)
ACM should renew these automatically. If one is still expiring, it almost always means validation is broken. For DNS-validated certs, ACM needs the CNAME validation records to still exist in your DNS zone.
Confirm the validation records are present:
aws acm describe-certificate \
--certificate-arn arn:aws:acm:us-east-1:111122223333:certificate/abcd1234 \
--query 'Certificate.DomainValidationOptions[].ResourceRecord' \
--output json
Take the returned Name and Value and make sure that CNAME exists in your hosted zone. If you deleted it after the initial issuance, recreate it:
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "_abc123.api.example.com.",
"Type": "CNAME",
"TTL": 300,
"ResourceRecords": [{ "Value": "_xyz789.acm-validations.aws." }]
}
}]
}'
Once validation resolves, ACM renews the certificate behind the scenes and reissues against the same ARN. No change to the custom domain is needed because the ARN stays the same.
Tip: If you manage DNS with Terraform or CloudFormation, keep the ACM validation records in the same stack as the certificate. That way they cannot drift apart, and nobody can delete the validation record without also noticing the certificate it belongs to.
Step 2b: If the certificate is imported (IMPORTED)
Imported certificates are not renewed by AWS, so you need to obtain a fresh certificate from your CA and replace it. The cleanest long-term answer is to stop importing certificates entirely and switch to an ACM-issued certificate. If you must keep using your own CA, request a new cert, then re-import it.
To re-import over the same ARN (this keeps your custom domain pointed at the same certificate):
aws acm import-certificate \
--certificate-arn arn:aws:acm:us-east-1:111122223333:certificate/abcd1234 \
--certificate fileb://new-cert.pem \
--private-key fileb://private-key.pem \
--certificate-chain fileb://chain.pem
Danger: Re-importing replaces the live certificate served on your production custom domain. Verify the new certificate, chain, and private key match and form a valid bundle before you run this. A malformed chain will break TLS for every client immediately, and there is no staging step.
Step 3: Migrating an imported cert to an ACM-managed one
The better fix is to switch the domain to an ACM-issued certificate so this never recurs. Request and validate a new ACM certificate, then update the custom domain to use it.
# Request a DNS-validated ACM certificate
aws acm request-certificate \
--domain-name api.example.com \
--validation-method DNS \
--query 'CertificateArn' --output text
Add the validation CNAME ACM gives you, wait for the status to become ISSUED, then point the custom domain at the new ARN.
For a v2 (HTTP/WebSocket) domain:
aws apigatewayv2 update-domain-name \
--domain-name api.example.com \
--domain-name-configurations CertificateArn=arn:aws:acm:us-east-1:111122223333:certificate/new5678
For a v1 (REST) regional domain:
aws apigateway update-domain-name \
--domain-name api.example.com \
--patch-operations op=replace,path=/regionalCertificateArn,value=arn:aws:acm:us-east-1:111122223333:certificate/new5678
Note: Edge-optimized REST custom domains require the certificate to live in us-east-1 because they are fronted by CloudFront. Regional custom domains need the certificate in the same region as the API. Requesting the cert in the wrong region is a common reason these updates fail.
How to prevent it from happening again
The fix above stops today's fire. Prevention is what keeps the alert from coming back.
Prefer ACM-issued certificates everywhere
The single most effective prevention is to use ACM-issued, DNS-validated certificates for every custom domain. As long as the DNS validation record stays in place, AWS renews them for you with zero human involvement. Imported certificates should be the rare exception, reserved for cases where your organization mandates a specific CA.
Define custom domains in infrastructure as code
Managing the certificate and the custom domain together in Terraform keeps validation records and certificates from drifting apart:
resource "aws_acm_certificate" "api" {
domain_name = "api.example.com"
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "api_validation" {
for_each = {
for dvo in aws_acm_certificate.api.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
type = dvo.resource_record_type
record = dvo.resource_record_value
}
}
zone_id = var.hosted_zone_id
name = each.value.name
type = each.value.type
records = [each.value.record]
ttl = 300
}
resource "aws_acm_certificate_validation" "api" {
certificate_arn = aws_acm_certificate.api.arn
validation_record_fqdns = [for r in aws_route53_record.api_validation : r.fqdn]
}
resource "aws_apigatewayv2_domain_name" "api" {
domain_name = "api.example.com"
domain_name_configuration {
certificate_arn = aws_acm_certificate_validation.api.certificate_arn
endpoint_type = "REGIONAL"
security_policy = "TLS_1_2"
}
}
Monitor expiry with CloudWatch
ACM publishes a DaysToExpiry metric per certificate. Wire an alarm to it so you get notified well before any check or client does:
aws cloudwatch put-metric-alarm \
--alarm-name "acm-cert-expiry-api-example" \
--namespace AWS/CertificateManager \
--metric-name DaysToExpiry \
--dimensions Name=CertificateArn,Value=arn:aws:acm:us-east-1:111122223333:certificate/abcd1234 \
--statistic Minimum \
--period 86400 \
--evaluation-periods 1 \
--threshold 30 \
--comparison-operator LessThanThreshold \
--alarm-actions arn:aws:sns:us-east-1:111122223333:ops-alerts
Gate it in CI/CD with policy as code
Block imported certificates with short remaining lifetimes from reaching production. A simple guard in your pipeline can scan the planned domain configuration and fail the build if it references an imported cert or one expiring soon. Pair that with a scheduled job that runs the same expiry query daily and opens a ticket automatically.
Tip: Lensix runs the apigw_certexpiring check continuously, so you get a single rolled-up view of expiring certificates across every account and region without standing up per-certificate alarms by hand. Use it as the safety net behind your CloudWatch alarms.
Best practices
- Standardize on ACM DNS validation. It is the only setup that genuinely renews itself without ongoing effort.
- Never delete ACM validation records. They look like dead DNS entries but they are what keeps auto-renewal alive. Tag or comment them clearly.
- Stagger certificate issuance. Avoid creating dozens of certs on the same day. Clustered expiry dates turn one missed renewal into a fleet-wide outage.
- Enforce a modern TLS policy. Set the custom domain security policy to
TLS_1_2or higher so you are not serving deprecated protocols alongside a fresh certificate. - Alert at multiple thresholds. A warning at 30 days and an escalation at 7 days gives you a buffer plus a hard deadline.
- Document who owns each domain. Most expiry incidents trace back to a domain nobody felt responsible for. An ownership tag turns a mystery into a phone call.
Certificate expiry is the rare outage you can schedule a fix for months ahead of time. The teams that never get burned are not lucky, they just made renewal automatic and put a couple of alarms behind it. Move your custom domains to ACM-managed certificates, keep your validation records intact, and let this check fade into a metric you never have to think about.

