SSL Certificates

Using Certbot

Introduction

This page is now obsolete and just kept for historical purposes.

I first installed SSL certificates on my sites using Certbot version 1.31.0 which was released on October 4, 2022. In November 2023, the certifcating authority, Let's Encrypt, announced that Windows support for Certbot would no longer be available after February 2024. The last version of Certbot that worked on Windows was version 2.9.0 which was released on February 8, 2024.

This last version of Certbot for Windows renewed the certficates in December 2025, but in January 2026, it refused to run properly. This meant I had to look for a new ACME (Automatic Certificate Management Environment) client to get new certficates and then renew them. I am currently looking at using simple-acme.


Using Certbot

I downloaded the latest release and ran the installer, which creates a number of folders for the program. There seems to be a lot of commands for Certbot, but they are not too difficult to understand. There is always the Community Forum if further help is needed.

Cerbot's documentation specifically says that:

Certbot for Windows currently cannot install the certificate in Apache or Nginx for you. As of the most recent release, you will have to edit your web server application's configuration to install the certificate yourself after Certbot has obtained it.

At the time, I didn't realize the full implications of that. Once the certificates have been downloaded, Apache needs its configuration files edited so it can find them.

Certbot is a command line program and Cmd must be run as an administrator for it to work.

There are several methods of obtaining the certificates and I tried several of them while I was learning the program.

Apache Service Stopped: --standalone Option

For this method the Apache service must be stopped as Certbot needs access to port 80. This is because using the --standalone option, Certbot needs access to port 80.

At the command prompt type:

certbot certonly --standalone

Certbot is interactive and will ask you to enter a space or comma delimited list of domains for it to get certificates for. These are downloaded together in fullchain.pem. Following the onscreen instructions, Certbot will download the files and put them in its directories. The files will be in the "live" directory which are symbolic links to the "archive" directory.

The Certbot directoriesr

The Certbot directory structure
The actual certificates are in the archive folder, but there are symlinks to the latest ones in the live folder,
this is where the Apache configuration files should point to.

Apache Service Running : --webroot Option

I thought there should be a method of getting the certificates without stopping the Apache service and there is! Using what the --standalone method showed me I used:

certbot certonly --webroot -w C:\Apache24\htdocs\brisray -d brisray.com -d www.brisray.com -w C:\Apache24\htdocs\hmsgambia -d hmsgambia.org -d www.hmsgambia.org -w C:\Apache24\htdocs\icehouseoffroad -d ihor4x4.com -d www.ihor4x4.com

The command has to be typed on one line and the the -w swtiches are the webroot path and the -d switches are for the associated domain names. Notice I used the domain names with and without the www part in the URL.

If all goes well you will see something like this on the screen:

Successfully received certificate.
Certificate is saved at: C:\Certbot\live\brisray.com\fullchain.pem
Key is saved at:         C:\Certbot\live\brisray.com\privkey.pem
This certificate expires on 2024-06-22.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

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 certificates 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 certificates, 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 certificates all expire at the same time, so checking the certificates weekly seems to be enough for me.

In December 2025, Let's Encrypt announced that the lifetime of their certificates, as well as other CAs, will be reduced. The announcement says that the lifetime will be reduced to 64 days on February 10, 2027, and then further reduced to 45 days on February 16, 2028.


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 certificates" | 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 certificates did not renew. Please check." | Out-File "$PSScriptRoot\renew-certs-task.log" -append
		}
		else {
			"The new certificates will renew in $newDays days" | Out-File "$PSScriptRoot\renew-certs-task.log" -append
		}
	}

What it Does

The command certbot certificates 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 certificates 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
Certbot: Renewing certificates
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