SSL: automatic renewal
AWS — load balancer, CloudFront, API Gateway
A Lambda function fetches the fresh certificate daily and reimports it into AWS Certificate Manager under the same ARN — ELB and CloudFront follow.
Renews by itself
Use this if your site gets its certificate through AWS: Application or Network Load Balancer, CloudFront, API Gateway, Elastic Beanstalk. The certificate lives in AWS Certificate Manager (ACM). We never ask for keys to your AWS: the function runs in your account and is allowed to update only one certificate.
How it works: ACM lets you import a new certificate under the same ARN, and every service that uses it switches to the new one without reconfiguration. A Lambda function compares the certificate at the link with the one in ACM once a day and, if it is new, imports it together with your key stored in Secrets Manager.
What you need
- The certificate link from your account:
https://api.zrazu.com/ssl-pull/zsp_YOUR_TOKEN. - A private key without a password (RSA 2048 or ECDSA P-256), the one the certificate was ordered with.
- Access to the AWS Console with permission to create Lambda functions, secrets and IAM roles.
- 20 minutes.
Region. For CloudFront the certificate must be in the US East (N. Virginia) us-east-1 region, so create both the secret and the function there. For a load balancer, use the load balancer's region.
Step 1. Put the key in Secrets Manager
- Secrets Manager → Store a new secret.
- Other type of secret → Plaintext tab → delete the example and paste the whole key text, including the
-----BEGIN … PRIVATE KEY-----and-----END … PRIVATE KEY-----lines. - Next → name it
zrazu-ssl-key→ Next → Next → Store. - Open the secret and copy the Secret ARN.
Step 2. First import into ACM (if the certificate is not there yet)
- Certificate Manager → Import a certificate.
- Certificate body — the contents of
https://api.zrazu.com/ssl-pull/zsp_YOUR_TOKEN/cert.pem(open it in a browser and copy all the text); Certificate private key — your key; Certificate chain — the contents ofhttps://api.zrazu.com/ssl-pull/zsp_YOUR_TOKEN/chain.pem. - Import. Open the certificate and copy its ARN.
- Attach the certificate to the service if you have not yet: for a load balancer, Listeners → HTTPS:443 → Edit → Default SSL/TLS certificate; for CloudFront, Distribution → Edit → Custom SSL certificate.
Step 3. The Lambda function
- Lambda → Create function → Author from scratch. Name it
zrazu-ssl-update, set Runtime to Node.js 22.x, then Create function. - On the Code tab open
index.mjsand replace all the text with the code below. Put your values in theCERT_ARN(from step 2) andKEY_SECRET(from step 1) lines. Click Deploy.
// ZRAZU: updates the certificate in AWS Certificate Manager using the personal link. Runs once a day.
import { ACMClient, DescribeCertificateCommand, ImportCertificateCommand } from "@aws-sdk/client-acm";
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
import { X509Certificate, createPrivateKey } from "node:crypto";
const PULL = "https://api.zrazu.com/ssl-pull/zsp_YOUR_TOKEN";
const CERT_ARN = "arn:aws:acm:REGION:ACCOUNT:certificate/…";
const KEY_SECRET = "arn:aws:secretsmanager:REGION:ACCOUNT:secret:zrazu-ssl-key-…";
async function pem(part) {
const res = await fetch(`${PULL}/${part}.pem`);
if (!res.ok) throw new Error(`ZRAZU: ${part}.pem — HTTP ${res.status}`);
const text = await res.text();
if (!text.includes("BEGIN CERTIFICATE")) throw new Error("ZRAZU: the link does not return a certificate");
return text;
}
const serial = (s) => (s ?? "").replace(/:/g, "").replace(/^0+/, "").toUpperCase();
export const handler = async () => {
const acm = new ACMClient({ region: CERT_ARN.split(":")[3] });
const [cert, chain] = await Promise.all([pem("cert"), pem("chain")]);
const leaf = new X509Certificate(cert);
const current = await acm.send(new DescribeCertificateCommand({ CertificateArn: CERT_ARN }));
if (serial(current.Certificate?.Serial) === serial(leaf.serialNumber)) return "ZRAZU: certificate unchanged";
if (new Date(leaf.validTo) <= new Date()) throw new Error("ZRAZU: certificate has already expired");
const secrets = new SecretsManagerClient({ region: KEY_SECRET.split(":")[3] });
const { SecretString: key } = await secrets.send(new GetSecretValueCommand({ SecretId: KEY_SECRET }));
if (!leaf.checkPrivateKey(createPrivateKey(key))) {
throw new Error("ZRAZU: certificate was issued for a different key — put the new key in the secret");
}
await acm.send(new ImportCertificateCommand({
CertificateArn: CERT_ARN,
Certificate: Buffer.from(cert),
CertificateChain: Buffer.from(chain),
PrivateKey: Buffer.from(key),
}));
return `ZRAZU: new certificate installed, valid until ${leaf.validTo}`;
};- Configuration → General configuration → Edit → set Timeout to 30 seconds → Save.
Step 4. Function permissions — only for this certificate and this secret
- Configuration → Permissions → click the role name (IAM opens).
- Add permissions → Create inline policy → JSON tab → paste the text below, putting in your two ARNs.
- Next → name it
zrazu-ssl-update→ Create policy.
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["acm:DescribeCertificate", "acm:ImportCertificate"], "Resource": "CERTIFICATE_ARN" },
{ "Effect": "Allow", "Action": "secretsmanager:GetSecretValue", "Resource": "SECRET_ARN" }
]
}Step 5. Schedule
- In the function, Add trigger → EventBridge (CloudWatch Events).
- Create a new rule → name
zrazu-ssl-daily→ Schedule expression →rate(1 day)→ Add.
Check
- In the function, open the Test tab → Test (any event). The result is "new certificate installed" or "certificate unchanged".
- In Certificate Manager, the certificate shows a new Expires date.
- Site: https://zrazu.com/tools/ssl/example.com/. CloudFront takes a few minutes to spread the new certificate to its nodes.
- The log of each run: Monitor → View CloudWatch logs.
If it did not work
AccessDeniedException— the policy from step 4 has the wrong ARN or the wrong region.HTTP 404— the link is disabled or replaced: create a new one in the certificate card and replace thePULLline.- "certificate was issued for a different key" — the certificate was reissued with a new key: update the secret (Retrieve secret value → Edit).
ValidationExceptionabout the key — the key has a password or is in the wrong format: you need a PEM without a password.- Task timed out — increase Timeout (step 3).