One of the recurring hassles I had was the renewal of the SSL certificate used for the techbyk.com wildcard domain. The underlying magic of SSL certificate update is happening with the certbot tool.
In this setup, I’m using Route53 and the underlying ec2 instances has the necessary IAM roles required to update the acme-domain challenge. The issue was certbot updating has never worked in the first attempt. It always asks to add an _acme-challenge and immediately after it is added, again requests to add another record in addition to the previous. This second challenge causes an error and have to restart. The second time I execute the same command and update the _acme-challenge, it properly works and update the SSL certificate properly…!
Since the above was a manual process, I came up with the following bash scripts to automate the SSL cert renewal. In this article I’ll share the scripts and procedure that worked for me.
Since this might not be a common problem, I would not go much into too much details and dive into the solution directly.
- As the first step, we have to create a renew_cert.sh script.
#!/bin/bash
DOMAIN="<your_domain>" # Replace this with your domain. eg: techbyk.com
EMAIL="<your_email>" # Replace this with your email
HOSTED_ZONE_ID="<route53_hosted_zone>" # Replace this with your hosted zone ID. Can get from aws route53 list-hosted-zones
# Step 1: Request certificate via DNS challenge
certbot certonly --manual --preferred-challenges=dns --email "$EMAIL" --server https://acme-v02.api.letsencrypt.org/directory -d "$DOMAIN" -d "*.$DOMAIN" --agree-tos --manual-public-ip-logging-ok --manual-auth-hook /etc/certbot/authenticator.sh --non-interactive
sleep 2;
# View the updated SSL cert expiry
openssl x509 -in /home/ec2-user/apache-proxy/fullchain.pem -text -noout |grep -i -B1 -A3 validity
# Step 2: Restart Apache to apply the new certificate
systemctl restart httpd
Make sure to make the above file executable with the following command.
chmod +x renew_cert.sh
2. Then, we can create the /etc/certbot/authenticator.sh file with below content. Please update the values as required.
Here, within the apache proxy, I’ve referred the certificate and private key locations as /home/ec2-user/apache-proxy/fullchain.com and /home/ec2-user/apache-proxy/privkey.pem and they have the following symlinks (symbolic links).
fullchain.pem -> /etc/letsencrypt/live/techbyk.com/fullchain.pem
privkey.pem -> /etc/letsencrypt/live/techbyk.com/privkey.pem
Thanks to the above symlinks we don’t have to manually update the new certificate location. Hence, restarting of the httpd automatically utilize the latest SSL cert.
Just make sure /etc/certbot directory exists, if not, create the directory with mkdir -p /etc/certbot command.
Below are the content of the /etc/certbot/authenticator.sh.
#!/bin/bash
# Values set dynamically by certbot
DOMAIN="_acme-challenge.techbyk.com" # Update this based on your site. For this site techbyk.com, the above is the domain.
VALUE="$CERTBOT_VALIDATION" # Here, this is an environment variable that has the validation string. (source: https://eff-certbot.readthedocs.io/en/stable/using.html#pre-and-post-validation-hooks)
TTL=60
HOSTED_ZONE_ID="<hosted_zone_id>" # The correct hosted zone ID
# Log information for debugging
echo "Updating DNS record for $DOMAIN with validation value $VALUE in hosted zone $HOSTED_ZONE_ID"
# Step to update the DNS TXT record in Route 53
aws route53 change-resource-record-sets --hosted-zone-id "$HOSTED_ZONE_ID" --change-batch "{
\"Changes\": [{
\"Action\": \"UPSERT\",
\"ResourceRecordSet\": {
\"Name\": \"$DOMAIN\",
\"Type\": \"TXT\",
\"TTL\": $TTL,
\"ResourceRecords\": [{\"Value\": \"\\\"$VALUE\\\"\"}]
}
}]
}"
echo "Waiting for DNS propagation..."
sleep 120 # Optional, adjust sleep based on DNS propagation time
Can make the script executable with the following command
chmod +x /etc/certbot/authenticator.sh
3. Finally, I added a cronjob as follows. The reason why there are two cronjobs is every time the first attempt fails. Therefore the renewal actually works with the second cronjob.
# Mock update of the ssl cert. Since the first time it fails
0 0 1 */2 * /home/ec2-user/cert-update/renew_cert.sh >> /var/log/ssl_renewal.log 2>&1
# The actual update of the ssl cert.
5 0 1 */2 * /home/ec2-user/cert-update/renew_cert.sh >> /var/log/ssl_renewal.log 2>&1
As you can observe, the above solution may not be the most elegant solution. But, the important thing is it works as expected and now I don’t have to worry about renewing the SSL certificate manually every three months.