Posh-ACME is a free tool that automatically creates and renews SSL/TLS certificates for your websites and servers. Think of an SSL certificate as the digital padlock that appears in your browser when you visit a secure (https://) website — Posh-ACME handles getting that padlock for you, completely automatically.
Without a tool like Posh-ACME, you would need to manually request a certificate from a certificate authority, install it on your web server, and remember to renew it every 90 days before it expires. Posh-ACME does all of this work for you.
???? Before You Begin
This guide assumes you have already completed the Installation Guide and set up your Posh-ACME configuration. If you have not done that yet, please complete the Installation Guide first before continuing.
This guide will walk you through every step of the following:
Before you issue any certificates, you need to load your Posh-ACME settings in your PowerShell window. You will need to do this every time you open a fresh PowerShell session to work with Posh-ACME.
Open PowerShell and run the following command. This loads all the settings you created during installation (such as your DNS provider credentials and ACME server details).
. "$HOME\.poshacme-config.ps1"
???? What this does
The dot (.) at the beginning of this command tells PowerShell to run the configuration file and load all the settings into your current session. Without this step, Posh-ACME will not know how to connect to your DNS provider.
After loading the configuration, check that Posh-ACME can see your ACME server and account. Run these two commands:
Get-PAServer | Select-Object location, status
Get-PAAccount | Select-Object id, status, contact
You should see output confirming your server address and account status. The status field should show 'valid' for a healthy setup.
Posh-ACME supports three types of certificates. Choose the one that matches your situation:
| Certificate Type | When to Use It |
|---|---|
| Single Domain | You want to secure just one specific address, e.g. demo.example.com |
| Multi-Domain (SAN) | You want to secure several specific addresses with one certificate, e.g. example.com, www.example.com, and mail.example.com |
| Wildcard | You want to secure all sub-domains of a domain at once, e.g. *.example.com covers any.example.com, anything.example.com, etc. |
When you request a certificate, Posh-ACME proves to the certificate authority (Let's Encrypt or ZeroSSL) that you actually own the domain name. It does this using a method called DNS-01 validation:
Use this when you need a certificate for just one specific domain name.
New-PACertificate "demo.example.com" `
-Plugin Cloudflare `
-PluginArgs $pArgs `
-Verbose
What you will see while it runs:
[demo.example.com] Generating new order
[demo.example.com] Creating DNS TXT record for _acme-challenge.demo.example.com
[demo.example.com] Waiting for DNS propagation (30 seconds)
[demo.example.com] DNS propagation complete
[demo.example.com] Validating challenge
[demo.example.com] Finalizing order
[demo.example.com] Certificate issued successfully
[demo.example.com] Saved to: C:\Users\...\Posh-ACME\...\demo.example.com\
The process usually takes between 1 and 3 minutes. The most time is spent waiting for the DNS record to become visible across the internet (this is called 'DNS propagation').
Use this when you want a single certificate to cover several specific domain names. List all the domains you want separated by commas.
New-PACertificate "example.com", "www.example.com", "mail.example.com" `
-Plugin Cloudflare `
-PluginArgs $pArgs `
-Verbose
???? Good to know
All the domains in a multi-domain certificate must belong to the same DNS provider account, since Posh-ACME needs to create DNS records for each one to prove ownership.
Use this when you want to secure all possible sub-domains under a domain. A wildcard certificate for *.example.com will automatically cover shop.example.com, blog.example.com, app.example.com, and any other sub-domain you create.
Important: Wildcard certificates do NOT cover the root domain (example.com) by default. That is why we include it separately in the command below.
New-PACertificate "*.example.com", "example.com" `
-Plugin Cloudflare `
-PluginArgs $pArgs `
-Verbose
After a certificate is successfully issued, Posh-ACME saves several files to your computer. Each file serves a specific purpose.
C:\Users\{username}\AppData\Local\Posh-ACME\{server-hash}\{account-hash}\{domain}\
??? cert.cer # End-entity certificate (PEM)
??? cert.pfx # Certificate + key (PKCS#12, default password: 'poshacme')
??? chain.cer # Intermediate certificate(s)
??? chainX.cer # Alternative chain (if available)
??? fullchain.cer # Certificate + intermediates (use for web servers)
??? fullchain.pfx # Full chain + key (PKCS#12)
??? cert.key # Private key (PEM, unencrypted)~/.local/share/Posh-ACME/{server-hash}/{account-hash}/{your-domain}/| File Name | What It Is / When to Use It |
|---|---|
cert.cer |
The certificate for your domain only (does not include intermediate certificates) |
fullchain.cer |
The certificate PLUS all intermediate certificates — USE THIS for Apache and Nginx |
cert.key |
The private key — USE THIS for Apache and Nginx. Keep this file secure and never share it! |
fullchain.pfx |
Certificate + key combined in one file (PKCS#12 format) — USE THIS for IIS and Plesk. Default password: poshacme |
chain.cer |
The intermediate certificate chain only (without your domain certificate) |
?? Important — Default PFX Password
The PFX file (fullchain.pfx and cert.pfx) is password-protected. The default password set by Posh-ACME is: poshacme — You will need this password when importing the certificate into IIS or Plesk. You can change this password by adding -PfxPass "YourPassword" to your New-PACertificate command.
If you need to know the exact file paths for your certificates (for example, when writing a deployment script), run this command:
$order = Get-PAOrder -MainDomain "demo.example.com"
Write-Host "Certificate (PEM): $($order.CertFile)"
Write-Host "Private Key (PEM): $($order.KeyFile)"
Write-Host "Full Chain (PEM): $($order.ChainFile)"
Write-Host "PFX (PKCS#12): $($order.PfxFile)"
Write-Host "PFX Password: poshacme"
SSL certificates expire after 90 days. Posh-ACME can automatically renew them before they expire — but only if you set up a scheduled task (Windows) or cron job (Linux/macOS) to run the renewal script regularly.
Posh-ACME is smart enough to know when a certificate needs renewing. It will only actually renew a certificate when it is within 30 days of expiry, so it is safe to run the renewal script every day without worrying about unnecessary renewals.
Follow these steps to set up automatic renewal on Windows. The scheduled task will run every day at 2:00 AM and renew any certificates that are close to expiring.
This command creates a PowerShell script file at C:\Scripts\renew-certificates.ps1 that will do the actual renewal work when it runs.
# Set the path where the script will be saved
$renewScript = "C:\Scripts\renew-certificates.ps1"
# Create the script file
@"
Import-Module Posh-ACME
# Load your configuration
. "$HOME\.poshacme-config.ps1"
# Set up logging so you can see what happened
$logFile = "C:\Scripts\Logs\poshacme-renew-$(Get-Date -Format 'yyyy-MM-dd').log"
function Write-Log {
param([string]$message)
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
"$timestamp - $message" | Out-File -FilePath $logFile -Append
Write-Host "$timestamp - $message"
}
Write-Log "Starting certificate renewal check"
# Renew certificates (only renews if within 30 days of expiry)
Submit-Renewal -Verbose 2>&1 | Out-File -FilePath $logFile -Append
Write-Log "Certificate renewal check completed"
"@ | Out-File -FilePath $renewScript -Encoding UTF8
Write-Host "Renewal script created: $renewScript"
Before the scheduled task can run, the folder where it will save log files needs to exist. Run this command to create it:
New-Item -Path "C:\Scripts\Logs" -ItemType Directory -Force
Now create the scheduled task that will run the renewal script automatically every day at 2:00 AM.
# Define the task name and what it does
$taskName = "Posh-ACME Certificate Renewal"
$taskDescription = "Automatically renew SSL certificates using Posh-ACME"
# Tell Windows what program to run and with what arguments
$taskAction = New-ScheduledTaskAction `
-Execute "pwsh.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"C:\Scripts\renew-certificates.ps1`""
# Run every day at 2:00 AM
$taskTrigger = New-ScheduledTaskTrigger -Daily -At 2:00AM
# Run as the SYSTEM account (no user needs to be logged in)
$taskPrincipal = New-ScheduledTaskPrincipal `
-UserId "SYSTEM" `
-LogonType ServiceAccount `
-RunLevel Highest
# Register (create) the scheduled task
Register-ScheduledTask `
-TaskName $taskName `
-Action $taskAction `
-Trigger $taskTrigger `
-Principal $taskPrincipal `
-Description $taskDescription `
-Force
Write-Host "Scheduled task created: $taskName"
Get-ScheduledTask -TaskName "Posh-ACME Certificate Renewal"
Run the task manually right now to confirm it works correctly before waiting for the scheduled time:
Start-ScheduledTask -TaskName "Posh-ACME Certificate Renewal"
After running it, check the log file at C:\Scripts\Logs\ to see if the renewal check completed successfully.
On Linux and macOS, use a cron job to run the renewal script automatically. Follow these steps:
sudo tee /usr/local/bin/poshacme-renew.sh > /dev/null << 'EOF'
#!/bin/bash
# Posh-ACME Renewal Script
LOG_FILE="/var/log/poshacme-renew-$(date '+%Y-%m-%d').log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
log "Starting certificate renewal check"
# Run the Posh-ACME renewal inside PowerShell
pwsh -NoProfile -Command "
Import-Module Posh-ACME
. \"\$HOME/.poshacme-config.ps1\"
Submit-Renewal -Verbose
" >> "$LOG_FILE" 2>&1
log "Certificate renewal check completed"
EOF
# Make the script executable
sudo chmod +x /usr/local/bin/poshacme-renew.sh
# Open the crontab editor
sudo crontab -e
# Add this line at the bottom of the file (runs daily at 2:00 AM):
0 2 * * * /usr/local/bin/poshacme-renew.sh >> /var/log/poshacme-cron.log 2>&1
???? Understanding the schedule
The 0 2 * * * part means: at minute 0, hour 2, every day, every month, every day of the week — in other words, every day at exactly 2:00 AM.
sudo /usr/local/bin/poshacme-renew.sh
Then check the log file to see the output: /var/log/poshacme-renew-{today's-date}.log
Once your certificate is issued, you need to install it on your web server so that visitors to your website will see the secure padlock in their browser. This section covers all major web server types.
IIS (Internet Information Services) is Microsoft's web server that comes built into Windows Server. It uses the PFX file format for certificates.
# Step 1: Find your certificate PFX file
$order = Get-PAOrder -MainDomain "demo.example.com"
$pfxPath = $order.PfxFile
$pfxPassword = "poshacme"
# Step 2: Import the certificate into the Windows certificate store
$certPassword = ConvertTo-SecureString -String $pfxPassword -AsPlainText -Force
$cert = Import-PfxCertificate `
-FilePath $pfxPath `
-CertStoreLocation Cert:\LocalMachine\My `
-Password $certPassword
Write-Host "Certificate imported"
Write-Host "Thumbprint: $($cert.Thumbprint)"
# Step 3: Bind the certificate to your IIS website
Import-Module WebAdministration
$siteName = "Default Web Site"
# Create HTTPS binding if one does not already exist
$binding = Get-WebBinding -Name $siteName -Protocol https
if ($binding -eq $null) {
New-WebBinding -Name $siteName -Protocol https -Port 443
}
# Attach the new certificate to the HTTPS binding
$binding = Get-WebBinding -Name $siteName -Protocol https
$binding.AddSslCertificate($cert.Thumbprint, "My")
Write-Host "Certificate bound to IIS site: $siteName"
Save the following script as C:\Scripts\deploy-iis-cert.ps1. You can then run it any time you need to deploy or update the certificate on your IIS site.
# Usage: .\deploy-iis-cert.ps1 -Domain "demo.example.com" -SiteName "Default Web Site"
param(
[Parameter(Mandatory=$true)]
[string]$Domain,
[Parameter(Mandatory=$true)]
[string]$SiteName
)
Import-Module WebAdministration
$order = Get-PAOrder -MainDomain $Domain
$pfxPath = $order.PfxFile
$pfxPassword = "poshacme"
$certPassword = ConvertTo-SecureString -String $pfxPassword -AsPlainText -Force
$cert = Import-PfxCertificate -FilePath $pfxPath `
-CertStoreLocation Cert:\LocalMachine\My -Password $certPassword
$binding = Get-WebBinding -Name $SiteName -Protocol https
if ($binding) {
$binding.AddSslCertificate($cert.Thumbprint, "My")
Write-Host "Certificate deployed to $SiteName"
} else {
Write-Host "No HTTPS binding found for $SiteName"
}
To run the script:
.\deploy-iis-cert.ps1 -Domain "demo.example.com" -SiteName "Default Web Site"
# Usage: .\deploy-apache-cert.ps1 -Domain "demo.example.com"
param(
[Parameter(Mandatory=$true)]
[string]$Domain,
[string]$ApacheCertPath = "/etc/ssl/certs",
[string]$ApacheKeyPath = "/etc/ssl/private"
)
# Get the certificate and key file paths from Posh-ACME
$order = Get-PAOrder -MainDomain $Domain
$certFile = $order.ChainFile # Always use the full chain file for Apache
$keyFile = $order.KeyFile
if ($IsLinux -or $IsMacOS) {
sudo cp $certFile "$ApacheCertPath/$Domain.crt"
sudo cp $keyFile "$ApacheKeyPath/$Domain.key"
sudo chmod 644 "$ApacheCertPath/$Domain.crt"
sudo chmod 600 "$ApacheKeyPath/$Domain.key"
sudo systemctl reload apache2 # Debian/Ubuntu
# OR: sudo systemctl reload httpd # CentOS/RHEL
Write-Host "Certificate deployed to Apache"
} else {
Copy-Item $certFile "C:\Apache24\conf\ssl\$Domain.crt" -Force
Copy-Item $keyFile "C:\Apache24\conf\ssl\$Domain.key" -Force
Restart-Service -Name "Apache2.4"
Write-Host "Certificate deployed to Apache (Windows)"
}
<VirtualHost *:443>
ServerName demo.example.com
DocumentRoot /var/www/demo.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/demo.example.com.crt
SSLCertificateKeyFile /etc/ssl/private/demo.example.com.key
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
</VirtualHost>
# Usage: .\deploy-nginx-cert.ps1 -Domain "demo.example.com"
param(
[Parameter(Mandatory=$true)]
[string]$Domain,
[string]$NginxCertPath = "/etc/nginx/ssl"
)
$order = Get-PAOrder -MainDomain $Domain
$certFile = $order.ChainFile
$keyFile = $order.KeyFile
if ($IsLinux -or $IsMacOS) {
sudo mkdir -p $NginxCertPath
sudo cp $certFile "$NginxCertPath/$Domain.crt"
sudo cp $keyFile "$NginxCertPath/$Domain.key"
sudo chmod 644 "$NginxCertPath/$Domain.crt"
sudo chmod 600 "$NginxCertPath/$Domain.key"
sudo nginx -t
sudo systemctl reload nginx
Write-Host "Certificate deployed to Nginx"
} else {
New-Item -Path "C:\nginx\ssl" -ItemType Directory -Force
Copy-Item $certFile "C:\nginx\ssl\$Domain.crt" -Force
Copy-Item $keyFile "C:\nginx\ssl\$Domain.key" -Force
nginx.exe -s reload
Write-Host "Certificate deployed to Nginx (Windows)"
}
server {
listen 443 ssl http2;
server_name demo.example.com;
root /var/www/demo.example.com;
ssl_certificate /etc/nginx/ssl/demo.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/demo.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
}# Usage: .\deploy-plesk-cert.ps1 -Domain "demo.example.com"
param(
[Parameter(Mandatory=$true)]
[string]$Domain
)
$order = Get-PAOrder -MainDomain $Domain
$certFile = $order.CertFile
$keyFile = $order.KeyFile
$chainFile = $order.ChainFile
$certPath = $certFile -replace '\\', '/'
$keyPath = $keyFile -replace '\\', '/'
$chainPath = $chainFile -replace '\\', '/'
# Step 1: Create the certificate in Plesk
& "C:\Program Files (x86)\Plesk\bin\certificate.exe" --create "$Domain-cert" `
-cert-file $certPath `
-key-file $keyPath `
-cacert-file $chainPath
# Step 2: Assign the certificate to your Plesk domain
& "C:\Program Files (x86)\Plesk\bin\site.exe" --update $Domain `
-certificate-name "$Domain-cert"
Write-Host "Certificate deployed to Plesk domain: $Domain"
To run the script:
.\deploy-plesk-cert.ps1 -Domain "demo.example.com"
By default, the renewal script only renews the certificate files — it does not automatically push them to your web server. You can add 'post-renewal hooks' to your renewal script so that every time a certificate is renewed, it is also automatically deployed to your web server.
Save this as C:\Scripts\renew-and-deploy.ps1:
Import-Module Posh-ACME
Import-Module WebAdministration
# Load configuration
. "$HOME\.poshacme-config.ps1"
# Step 1: Renew any certificates that are due
Submit-Renewal -Verbose
# Step 2: Deploy all valid (renewed) certificates to IIS
$orders = Get-PAOrder -List | Where-Object { $_.status -eq 'valid' }
foreach ($order in $orders) {
$domain = $order.MainDomain
$pfxPath = $order.PfxFile
$pfxPassword = "poshacme"
$certPassword = ConvertTo-SecureString -String $pfxPassword -AsPlainText -Force
$cert = Import-PfxCertificate -FilePath $pfxPath `
-CertStoreLocation Cert:\LocalMachine\My -Password $certPassword
$site = Get-WebSite | Where-Object { $_.Name -like "*$domain*" }
if ($site) {
$binding = Get-WebBinding -Name $site.Name -Protocol https
if ($binding) {
$binding.AddSslCertificate($cert.Thumbprint, "My")
Write-Host "Certificate deployed to $($site.Name)"
}
}
}
Update your scheduled task (or cron job) to run renew-and-deploy.ps1 instead of renew-certificates.ps1 so the deployment happens automatically every time a renewal occurs.
Even with automatic renewal set up, it is good practice to regularly check your certificates to make sure everything is working correctly.
Get-PAOrder -List | Select-Object MainDomain, status, CertExpires | Format-Table
To check a specific domain and see how many days are left:
$order = Get-PAOrder -MainDomain "demo.example.com"
Write-Host "Domain: $($order.MainDomain)"
Write-Host "Status: $($order.status)"
Write-Host "Expires: $($order.CertExpires)"
Write-Host "Days remaining: $(($order.CertExpires - (Get-Date)).Days)"
# View today's log (last 50 lines)
Get-Content "C:\Scripts\Logs\poshacme-renew-$(Get-Date -Format 'yyyy-MM-dd').log" -Tail 50
# List all available log files
Get-ChildItem "C:\Scripts\Logs\poshacme-renew-*.log" | Sort-Object LastWriteTime -Descending
# Stream today's log in real time
tail -f /var/log/poshacme-renew-$(date '+%Y-%m-%d').log
# List all available log files
ls -lht /var/log/poshacme-renew-*.log
# Force renewal of all certificates
Submit-Renewal -Force -Verbose
# Force renewal of one specific domain
Set-PAOrder -MainDomain "demo.example.com"
Submit-Renewal -Force -Verbose
?? Testing Note
Using -Force will renew the certificate even if it is not due yet. This counts against your Let's Encrypt rate limit (maximum 5 duplicate certificates per week per domain). Only use -Force for testing, not in your regular renewal script.
What you see: The certificate request hangs or fails at the 'Waiting for DNS propagation' step.
Why it happens: After Posh-ACME adds the DNS verification record to your domain, it needs to wait for that record to become visible across the internet. By default it waits 120 seconds, but some DNS providers take longer.
How to fix it: Tell Posh-ACME to wait longer by adding -DnsSleep 180 (180 seconds) to your command:
New-PACertificate "demo.example.com" `
-Plugin Cloudflare `
-PluginArgs $pArgs `
-DnsSleep 180 `
-Verbose
What you see: An error message saying 'Order finalization timed out'.
Why it happens: The ACME server is taking longer than Posh-ACME's default timeout to confirm the certificate is ready.
How to fix it: This requires modifying the Submit-OrderFinalize.ps1 file to increase the timeout. Please refer to the Installation Guide for instructions on how to make this change.
What you see: You are asked for a password when importing the PFX file and your password is being rejected.
Why it happens: The default PFX password set by Posh-ACME is 'poshacme'. If you are getting an incorrect password error, double-check that you are using this exact password.
How to fix it or change the password for future certificates:
New-PACertificate "demo.example.com" `
-Plugin Cloudflare `
-PluginArgs $pArgs `
-PfxPass "YourNewCustomPassword" `
-Verbose
What you see: You are not sure where the certificate files were saved, or you cannot find the folder.
How to fix it: Run the following command — it will show you the exact full path to each certificate file:
$order = Get-PAOrder -MainDomain "demo.example.com"
$order | Select-Object CertFile, KeyFile, PfxFile, ChainFile | Format-List
Before considering your Posh-ACME deployment complete, work through each item on this checklist.
| ? | PowerShell Core 7 or later is installed (required on Linux and macOS) |
| ? | The Posh-ACME PowerShell module is installed and accessible |
| ? | The order finalization timeout has been increased in Submit-OrderFinalize.ps1 |
| ? | Your DNS provider credentials have been configured (e.g. Cloudflare API token) |
| ? | EAB (External Account Binding) credentials have been configured if required by your ACME provider |
| ? | The configuration script has been created at ~/.poshacme-config.ps1 (Linux/macOS) or %USERPROFILE%\.poshacme-config.ps1 (Windows) |
| ? | At least one test certificate has been successfully issued |
| ? | You can find the certificate files in the expected location on disk |
| ? | The certificate has been successfully deployed to your web server(s) |
| ? | Your website is accessible over HTTPS with a valid padlock in the browser |
| ? | Automatic renewal has been set up (Windows Scheduled Task or Linux/macOS cron job) |
| ? | The renewal script has been tested successfully using the -Force flag |
| ? | The deployment script has been tested manually |
| ? | Renewal log files are being created and contain sensible output |
| ? | You can view renewal logs and interpret their contents |
| ? | You have checked the days-remaining value for your certificate(s) |
| ? | A monitoring or alerting process is in place for certificate expiry |
| ? | The Posh-ACME data directory is included in your server backup |
| ? | Any server-specific configuration notes have been written down |
| Resource | Where to Find It |
|---|---|
| Official Posh-ACME Documentation | https://poshac.me/docs/ |
| GitHub Source Code & Issues | https://github.com/rmbolger/Posh-ACME |
| List of Supported DNS Plugins | https://poshac.me/docs/v4/Plugins/ |
| Community Help & Discussions | https://github.com/rmbolger/Posh-ACME/discussions |
| ACME Server Support | Contact your ACME server administrator for EAB credentials and account issues |
When asking for help, always include the following information to get a faster and more accurate response: