Eclipsys Blog

Zero-Click Cert Rotation on Oracle Cloud: CertifyTheWeb + OpenSSL + OCI CLI

Written by Rajesh Madhavarao | Oct 22, 2025 3:03:30 PM
Certificates expire, weekends don’t. This post shows how we eliminated manual renewals by scripting the entire path—CertifyTheWeb → OpenSSL → OCI CLI—including temporary Port 80 open/close, issuer auto-detect (R10–R13), PFX export, and a clean push to OCI Certificates. The result: fewer outages, faster audits, and a repeatable pipeline your ops team can trust.
 
Goal: Fully automate Let's Encrypt renewals on Windows and update OCI Certificates programmatically.
Pattern: CertifyTheWeb renews → OpenSSL extracts keys/chain → OCI CLI updates the certificate resource.
Security: Temporarily open Port 80 only for ACME HTTP-01, then auto-close.
Outcome: Zero-click rotation, audit-friendly logs, and consistent results.
 
Architecture at a Glance
 

Please find the attached script here.
 
https://drive.google.com/file/d/1ojvDTS3zemaxuEAv8by7R-r4ruDgfSma/view?usp=drive_link
 
 

Flow


1. CertifyTheWeb renews via ACME HTTP-01.
2. Script opens Port 80 on the OCI Security List (short window).
3. Export the newest PFX from the Windows Cert Store.
4. OpenSSL: split PFX into key/cert + assemble full chain.
5. Auto-detect Let’s Encrypt R10–R13 intermediate.
6. Close Port 80 (restore rule).
7. OCI CLI updates the OCI Certificate with PEMs.
 

Prerequisites

 
Windows Server hosting CertifyTheWeb (IIS or standalone).
OpenSSL (Win64) is installed and in PATH (or referenced by full path).
OCI CLI configured with a user/group that has permission to update OCI Certificates.

Security List OCID for the public subnet/ingress you will modify.
OCI Certificate OCID for the managed certificate you will update.
A compartment and region set in your CLI config or passed as flags.
 
 

Variables and Paths

 
$OutputDir = "C:\eclipsys\oci_cert_renewal\output"
$CertifyExe = "C:\Program Files\CertifyTheWeb\certify.exe"
$OpenSSL = "C:\Program Files\OpenSSL-Win64\bin\openssl.exe"

$CertSubjectMatch = "*reports.eclipsys.ca*"
$PfxPassword = "<YOUR_PFX_PASSWORD>"

$OciRegion = "ca-toronto-1"
$OciCertificateId = "ocid1.certificate.oc1.ca-toronto-1.<...>"
$SecurityListId = "ocid1.securitylist.oc1.ca-toronto-1.<...>"

$BackupIngressJson = "C:\eclipsys\oci_cert_renewal\uleth_ingress_rules.json"
$OpenIngressJson = "C:\eclipsys\oci_cert_renewal\open_port80_ingress.json"
$CloseIngressJson = "C:\eclipsys\oci_cert_renewal\close_port80_ingress.json"

$PfxFile = "$OutputDir\fastcert.pfx"
$CertPem = "$OutputDir\certificate.pem"
$KeyProtected = "$OutputDir\private_key_nonrsa.pem"
$KeyPem = "$OutputDir\privatekey.pem"
$CaPem = "$OutputDir\eclipsys_cert_ca.crt"
$RootPem = "$OutputDir\eclipsys_cert_root.crt"
$FullChainPem = "$OutputDir\certificatechain.pem"
$LogPath = "$OutputDir\cert_renewal.log
 
Purpose: Centralize configuration so values aren’t scattered throughout the script.


Backup current ingress, then open Port 80 (ACME HTTP-01)

 
oci network security-list get `
--security-list-id $SecurityListId `
--query data.'ingress-security-rules' `
--output json > $BackupIngressJson

oci network security-list update `
--security-list-id $SecurityListId `
--ingress-security-rules file://$OpenIngressJson `
--force
 
What it does: Backs up current rules and temporarily opens TCP/80 to complete the ACME challenge.


Renew with CertifyTheWeb (CTW) and log output


Start-Process -FilePath $CertifyExe `
-ArgumentList "renew --force-renew-all --verbose" `
-RedirectStandardOutput $LogPath `
-Wait -NoNewWindow
What it does: Forces a renewal and captures verbose logs for audit/troubleshooting.
 
 
 

Export the newest matching certificate (PFX) from the Windows store


New-Item -Path $OutputDir -ItemType Directory -Force | Out-Null
$cert = Get-ChildItem Cert:\LocalMachine\My |
Where-Object { $_.Subject -like $CertSubjectMatch } |
Sort-Object NotAfter -Descending | Select-Object -First 1

if (-not $cert) { throw "No certificate found for $CertSubjectMatch" }

$bytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $PfxPassword)
[System.IO.File]::WriteAllBytes($PfxFile, $bytes)

What it does: Selects the most recent matching cert and exports it as PFX (includes private key).
 
 

OpenSSL: split PFX → private key + leaf cert, and capture CA


& $OpenSSL pkcs12 -in $PfxFile -nocerts -out $KeyProtected -passin pass:$PfxPassword -passout pass:$PfxPassword
& $OpenSSL rsa -in $KeyProtected -out $KeyPem -passin pass:$PfxPassword
& $OpenSSL pkcs12 -in $PfxFile -clcerts -nokeys -out $CertPem -passin pass:$PfxPassword
& $OpenSSL pkcs12 -in $PfxFile -cacerts -nokeys -out $CaPem -passin pass:$PfxPassword

What it does: Extracts private key, leaf certificate, and intermediate(s).
 

Auto-detect Let’s Encrypt issuer (R10–R13), download intermediate + root, build full chain


$issuer = (& $OpenSSL x509 -in $CertPem -noout -issuer)

$intermediateURL = switch -Regex ($issuer) {
"R13" { "https://letsencrypt.org/certs/2024/r13.pem" ; break }
"R12" { "https://letsencrypt.org/certs/2024/r12.pem" ; break }
"R11" { "https://letsencrypt.org/certs/2024/r11.pem" ; break }
default { "https://letsencrypt.org/certs/2024/r10.pem" }
}
$rootURL = "https://letsencrypt.org/certs/isrgrootx1.pem"

Invoke-WebRequest -Uri $intermediateURL -OutFile $CaPem
Invoke-WebRequest -Uri $rootURL -OutFile $RootPem
Get-Content $CaPem, $RootPem | Set-Content $FullChainPem

What it does: Ensures you always include the correct intermediate and root for a valid trust chain.
 
Update the OCI Certificate with key, leaf, and full chain

$CERT_CHAIN_PEM = Get-Content $FullChainPem -Raw
$CERTIFICATE_PEM = Get-Content $CertPem -Raw
$PRIVATE_KEY_PEM = Get-Content $KeyPem -Raw

oci certs-mgmt certificate update-certificate-by-importing-config-details `
--certificate-id $OciCertificateId `
--certificate-pem "$CERTIFICATE_PEM" `
--cert-chain-pem "$CERT_CHAIN_PEM" `
--private-key-pem "$PRIVATE_KEY_PEM" `
--region $OciRegion `
--force
What it does: Publishes a new certificate version in OCI Certificates for downstream services to consume.
 

Restore the Security List (close Port 80)

oci network security-list update `
--security-list-id $SecurityListId `
--ingress-security-rules file://$CloseIngressJson `
--force
What it does: Closes the HTTP window by restoring your normal ingress rules.
 

Optional: Validate artifacts


& $OpenSSL x509 -in "$CertPem" -text -noout
& $OpenSSL rsa -in "$KeyPem" -check
& $OpenSSL x509 -in "$CaPem" -text -noout

Quick checks for leaf certificate, private key, and intermediate details.

Observability & Audit

• Logs: CertifyTheWeb renewal log and PowerShell output saved per run.
• OCI: Console shows certificate versions and Security List changes.
• Monitoring: Add alerts on expiration and HTTP availability during the ACME window.

Security Notes

• Least privilege: OCI principal limited to Certificates Mgmt and the target Security List.
• Short windows: Open Port 80 just-in-time, close immediately after.
• Key handling: Restrict file ACLs; consider cleanup post-upload; avoid hardcoding secrets.

Troubleshooting

• Invalid JSON on update: verify Windows file:// path and JSON validity.
• Issuer mismatch: confirm R10–R13 URLs and reachability; check the leaf’s issuer.
• OCI update errors: verify certificate OCID, region, IAM policies.
• PFX missing: confirm $CertSubjectMatch and CTW renewal success.
 

Scheduling the script

The renewal workflow runs automatically via Windows Task Scheduler.
Create a task (Run whether user is logged on or not; Run with highest privileges) with a daily trigger, and an action that starts powershell.exe with:
- NoProfile -ExecutionPolicy Bypass -File "C:\Eclipsys\oci_cert_renewal\Eclipsys_OCI_CertRenewal.ps1".
Set Start in to C:\Eclipsys\oci_cert_renewal so relative paths work.
After the first run, confirm success in the script log and in the OCI Console (new certificate version present).
 
 
 
Under Certificate, we can see the certificate renewal and expiry date
 
 
Conclusion
By scripting the full path—CertifyTheWeb → OpenSSL → OCI CLI—and scheduling it with Task Scheduler, certificate renewals stop being a weekend fire drill and become a quiet, repeatable control. The workflow briefly opens Port 80 only for ACME, assembles the correct chain automatically, updates OCI Certificates, and then locks everything back down—leaving you with fewer outages, clear audit trails, and a process you can trust at scale.