Introduction
I have been using Certbot and Let's Encrypt for my SSL Certificates. For all the time I have been using these, I still have not found a way of installing the certificates without stopping the server. What I would do is manually run a PowerShell script to shut down the server's Apache service, run Certbot to obtain the certifcates and restart the Apache service.
New-Variable -Name serviceName -Value 'Apache2.4' -Option Constant
Write-Output "Stopping Apache"
Stop-Service -name $serviceName
$service = Get-Service -Name $serviceName
$service.WaitForStatus('Stopped')
Write-Output "Apache is stopped"
# Line below works and waits for certbot to finsh before restarting Apache!
# Uncomment any 1 of the 3 following lines - they all work
# certbot.exe renew
# Invoke-expression -command "certbot.exe renew"
Start-Process -FilePath "certbot.exe" -ArgumentList "renew" -wait
Write-Output "Apache is restarting"
Start-Service -Name $serviceName
$service.WaitForStatus('Running')
Write-Output "Apache has restarted"
Exit
The above PowerShell script is what I was using
On 22 January 2025, Let's Encrypt announced that from 4 June 2025 they will no longer be sending expiration notification emails to remind people that their certificates will need to be renewed soon.
In February 2025, I decided to write a PowerShell script to run once a week via Windows Task Scheduler and check when the certificates will expire and if there is 30 days to go to that, renew the certifcates, and log what the script did.
Certificate Lifetime and Renewal
Let's Encrypt is a Certificate Authority and their SSL Certificates have a lifetime of 90 days. The certificates are able to be renewed within 30 days of their end of life date. I use the command line program Certbot to manage the certificates.
Some people recommend running the certificate renewal twice a day but this seems overkill to me. My certifcates all expire at the same time, so checking the certificates weekly seems to be enough for me.
My Logic
The output of the Certbot Certificates command is:
- - - - - - - - - -
Found the following certs:
Certificate Name: brisray.com
Serial Number: xxxxxxxxxxx
Key Type: ECDSA
Domains: brisray.com bristolgunners.org hmsgambia.org ihor4x4.com www.brisray.com www.bristolgunners.org www.hmsgambia.org www.ihor4x4.com
Expiry Date: 2025-03-16 00:19:10+00:00 (VALID: 35 days)
Certificate Path: xxxxxxxxxx\fullchain.pem
Private Key Path: xxxxxxxxxxx\privkey.pem
- - - - - - - - - -
If the certificates are not eligiable to be renewed, the output of the Certbot --renew command is:
- - - - - - - - - -
Certificate not yet due for renewal
- - - - - - - - - -
The following certificates are not due for renewal yet:
xxxxxxxxxxx\fullchain.pem expires on 2025-03-16 (skipped)
No renewals were attempted.
- - - - - - - - - -
My idea is to write a PowerShell script that is run weekly to:
Run the certbot certificates command
Save and parse the output of that for the number of days the certificate has left before it expires
Write the number of days left to a log file
If the number of days is more than 30 write a message to the log file and exit the program
If it is less than 30:
Stop the Apache server
Run the certbot --renew command
Start the Apache server
Check the output of that command for success by looking for the line "No renewals were attempted." or by rerunning the Certbot Certificates command.
Dates and Date Calculations
There are several other methods of getting when SSL certificates are valid until. They involve reading the certificaate and getting the NotAfter: property. Using:
openssl x509 -enddate -noout -in xxxxxxxxx\fullchain.pem
gives:
notAfter=Mar 16 00:19:10 2025 GMT
There are 2,592,000 seconds in 30 days, so the command:
openssl x509 --checkend 2592000 -noout -in xxxxxxxxxx\fullchain.pem
This command will return either "Certificate will not expire" if it will not expire within the 30 days or "Certificate will expire" if it will expire within the 30 days.
The command
certutil xxxxxxxxxx\fullchain.pem
Will give, along with a lot of other information:
NotAfter: 3/15/2025 7:19 PM
Using these methods just gets the expiration date of the certificate. The current date still has to be found, convert either or both to the same system - local time, GMT, universal time etc. then do a claculation on them to get the remaining number of days. It just seems easier to use the Certbot output and use that.
For example to use Powershell's get-date command and convert that to GMT then:
[System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($(Get-Date), [System.TimeZoneInfo]::Local.Id, 'GMT Standard Time')
can be used.
The Script
function getDaysLeft {
$progOutput = cmd.exe /c "C:\Certbot\bin\certbot certificates"
foreach ($line in $progOutput) {
$line | Select-String -Pattern "VALID" | out-null
$posn = $line.IndexOf("VALID")
if ($posn -gt -1) {
$newLine = $line.toString()
$calcDays = $newLine.Substring($posn + 7, 2)
}
}
$calcDays = [int]$calcDays
$script:calcDays = $calcDays
return $calcDays | out-null
}
New-Variable -Name apacheService -Value 'Apache2.4' -Option Constant
"Certbot SSL Certificate Renew Log" | Out-File "$PSScriptRoot\renew-certs-task.log"
"" | Out-File "$PSScriptRoot\renew-certs-task.log" -append
$now = Get-Date
"renew-certs-task.ps1 was run at $now" | Out-File "$PSScriptRoot\renew-certs-task.log" -append
getDaysLeft
$days = $calcDays
"The number of days left before the SSL certificates expire is $days" | Out-File "$PSScriptRoot\renew-certs-task.log" -append
if ($days -ge 30) {
"The number of days until SSL certificate expiry is greater than or equal than 30. Nothing done, exiting program" | Out-File "$PSScriptRoot\renew-certs-task.log" -append
exit
}
else {
"The number of days until SSL certificate expiry is less than than 30. Attempting to update SSL certifcates" | Out-File "$PSScriptRoot\renew-certs-task.log" -append
# Stop Apache server
Write-Output "Stopping $($apacheService) service"
Stop-Service -Name $apacheService
$service = Get-Service -Name $apacheService
$service.WaitForStatus('Stopped')
# Run Certbot to update the certificates
cmd.exe /c "C:\Certbot\bin\certbot renew"
# Start Apache server
Write-Output "Restarting Apache service"
Start-Service -Name $apacheService
$service.WaitForStatus('Running')
Write-Output "$apacheService has restarted"
getDaysLeft
$newDays = $calcDays
if ($days -le $newDays) {
"The certifcates did not renew. Please check." | Out-File "$PSScriptRoot\renew-certs-task.log" -append
}
else {
"The new certifcates will renew in $newDays days" | Out-File "$PSScriptRoot\renew-certs-task.log" -append
}
}
What it Does
The command certbot certifcates is run and the lines of text returned from that is searched for the word VALID and its position noted. The number of days the certificates have left can then be found. This is because that string contains the number of days the certificates are valid for among:
- - - - - - - - - -
Found the following certs:
Certificate Name: brisray.com
Serial Number: xxxxxxxxxxx
Key Type: ECDSA
Domains: brisray.com bristolgunners.org hmsgambia.org ihor4x4.com www.brisray.com www.bristolgunners.org www.hmsgambia.org www.ihor4x4.com
Expiry Date: 2025-03-16 00:19:10+00:00 (VALID: 35 days)
Certificate Path: xxxxxxxxxx\fullchain.pem
Private Key Path: xxxxxxxxxxx\privkey.pem
- - - - - - - - - -
If the number is less than or equal to thirty, the Apache service is stopped, the certificates renewed and the Apache service started again. The certbot certifcates command is run again and the number of days left for the new certificates written to the log file.
The " | out-null" pipes are to redirect the normal output of the program to null - that is, they do not appear.
Sources and Resources
Best practices for setting a cron job for Let's Encrypt (Certbot) renewal? - Server Fault
Certbot
Certbot Documentation
Certbot Latest Releases
Certbot on Windows: Automation is Possible - Matt Zaske's experience with Certbot with Apache on Windows
Electronic Frontier Foundation
HTTPS Checker - Checks a HTTPS site for insecure content. The online checker is not free but the downloadable one is
Ionos SSL Certificate Checker - checks that the certificates are installed properly
JitBit SSL Check - Checks a HTTPS site for insecure content
Let's Encrypt
Let's Encypt Community Forum
Let's Encrypt no longer sending expiration notification emails
Missing Padlock SSL Checker - Checks a HTTPS site for insecure content
Qualys SSL Server Test - checks that the certificates are installed properly
SSL Certificate Formats (Tutorials Teacher)
Will requesting to renew an SSL certificate too many times when it expires be blocked? - Certbot