PowerShell Script: Automatic Certificate Creation & Renewal

A script that can create and also renew certificate from start to finish via DNS challenge with optional code that you can run if the certificate gets created or renewed.
Intro
The idea is that you can use this script as a template for any certificate creation and renewal via DNS challenge. I also recommend setting it up on a schedule so you can forget all about it (don’t forget to set alerts for expiring certs). Some services are easy to work with when it comes to certificates, while others can be a pain and require manual intervention, the latter vendors have a lot of catching up to do.
With this script template my goal was to have it auto create/renew a certificate and then run any additional tasks that may be needed. These tasks could include uploading cert to IIS/any web server or something a bit more complex such as converting the files to base64, uploading to a service via REST API, and then perhaps restarting service on remote server.
The script uses Posh-ACME module which makes it effortless to work with ACME protocol.
Script Overview
The comments in the script should guide you on what the script does. When you run the script, it will check if Posh-ACME module is installed, it will install it for you.
Then the script checks if server configuration already exists. By default, Posh-ACME uses Let’s Encrypt but you can change it out for ZeroSSL or another Certificate Authority. It will then pick the right environment (Staging or Production based on the variable set).
Now, we proceed to the certificate part. The script checks if the certificate already exists under Posh-ACME. If the certificate doesn’t exist, it will run a DNS challenge with your provider and try to obtain it. If a certificate already exists, it will simply issue the renewal command for it.
If the certificate is obtained for the first time or renewed, script will trigger Invoke-PostRenewalTask function which you can use to run post renewal code/tasks.
Prerequisites
There are few modifications to be made before you run the script. You’ll need to modify the list of variables below:
-
$hostname
: The FQDN you’re trying to get the certificate for. -
$email
: Your email for requesting the certificate. -
$PAenv
: By default Posh-ACME uses Let’s Encrypt (LE). You can either set it toLE_PROD
(production) orLE_STAGE
(staging). I highly recommend setting it toLE_STAGE
until you get the script working before you switch to production. Otherwise, you can easily run into rate limiting on the production environment. The staging environment is more relaxed when it comes to rate limiting and the certificate issued isnt trusted by browsers. -
$plugin
: This depends on your DNS provider (CloudFlare, GoDaddy, Route53, etc.) Full list of providers is documented here. -
$pArgs
: Again, this depends on your DNS provider. See the plugin documentation linked above.
Additionally, modify Invoke-PostRenewalTask
function to run your own code in case of new certificate or renewal.
Script
<#
Use Posh-ACME module to renew a certificate using DNS provider's API. Also perform specific set of tasks for services which do not properly support cert auto renewal.
Pseudocode:
- Create/Set server to right Posh-ACME account (Prod or Stage)
- Create new account under current ACME server if one doesnt exist already
- If certificate already exists, try to renew it if it's within 30 days of expiry. If renewed, run Invoke-PostRenewalTask.
- If certificate doesnt exist, try to obtain it and then run Invoke-PostRenewalTask
Invoke-PostRenewalTask: Your code/script that will run if certificate gets created or renewed.
#>
$hostname = "host1.example.com"
$email = "[email protected]"
$PAenv = "LE_PROD" # Use LE_STAGE for testing, LE_PROD for live environment
# CloudFlare API - More supported providers here: https://poshac.me/docs/v4/Plugins/
$plugin = "CloudFlare"
$pArgs = @{
CFToken = 'your-api-token-here'
}
############### POST RENEWAL TASK FUNCTION #####################################################
# This function will only run if the cert is renewed or created
function Invoke-PostRenewalTask
{
Write-Host "This function was executed as a certificate creation or a valid renewal was detected."
### PLACE YOUR OWN SCRIPT/CODE HERE
### EXAMPLE: Update certificate on IIS server
### EXAMPLE: Update certificate for Nginx and reload service
}
############### END OF POST RENEWAL TASK FUNCTION ##############################################
# Download Posh-ACME PowerShell module if it isn't installed already
if (Get-Module -ListAvailable -Name Posh-ACME)
{
Write-Host "Posh-ACME Powershell module exists. Continuing..." -ForegroundColor Green
}
else
{
Write-Host "Posh-ACME Powershell module does not exist. Installing it now (THIS WILL TAKE A FEW MINUTES)." -ForegroundColor Yellow
Install-Module Posh-ACME -Scope CurrentUser -Force -Confirm:$false
}
Import-Module Posh-ACME
# Ensure the server is configured and also set the right PA account (prod or staging)
if (-not ($PAServer = Get-PAServer $PAenv))
{
try
{
Write-Host "No ACME server found, setting it up now."
Set-PAServer $PAenv
Write-Host "PAServer environment: $PAenv"
}
catch
{
Write-Host "Error setting up PA server."
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
}
}
else
{
Set-PAServer $PAenv
Write-Host "PAServer environment: $PAenv"
}
# Configure account under Posh-ACME if this step isnt already done
$accountCheck = Get-PAAccount
if (-not $accountCheck)
{
Write-Host "No account found, creating new account."
$accountCheck = New-PAAccount -AcceptTOS -Contact $email
}
# Check if cert already exists
$cert = Get-PACertificate -Name $hostname
if (-not $cert) # Get new cert if it doesnt exist, then run post renewal task
{
Write-Host "$hostname doesnt exist! Requesting a new cert. This can take a few minutes."
try
{
$newRequest = New-PACertificate $hostname -Plugin $plugin -AcceptTOS -PluginArgs $pArgs -Contact $email
$newRequest
Invoke-PostRenewalTask
}
catch
{
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
}
}
else
{
Write-Host "Cert order already exists for $hostname"
}
# Renew cert if expiring in next 30 days
$cert = Get-PACertificate -Name $hostname
$renewalThreshhold = (Get-Date).AddDays(30)
if ($cert.NotAfter -le $renewalThreshhold)
{
Write-Host "$hostname cert expires in the next 30 days $($cert.NotAfter), Starting renewal process."
Submit-Renewal -MainDomain $hostname -Verbose
# If renewed, perform the following post-renewal tasks
Invoke-PostRenewalTask
}
else
{
$daysLeft = ($cert.NotAfter - (Get-Date)).Days
Write-Host "$hostname cert renewal not needed. $daysLeft days still remaining for existing cert."
}