SQL Server 2025 and TDS 8/TLS 1.3
Part 1 - Create SSL with PowerShell and Let’s Encrypt
DISCLAIMER:
The scripts and procedures outlined in this guide are intended strictly for non-production environments (Development, Test, POC). Code listed is AI generated, but completely tested on Windows 11 and reader assume all risks associated with its use.
In this multi part blog series, let’s explore how client systems can communicate with SQL Server 2025 using encryption. Instead of using "self-signed certificates" OR Active Directory Certificate Services (AD CS), we can generate a free, trusted 90-day certificate using Let's Encrypt and a tool called win-acme.
Prerequisites
Download win-acme (the
.zipfile from https://www.win-acme.com/) and extract it to sayD:\win-acme.Ensure your Windows computer has internet access (to talk to Let's Encrypt APIs).
Access to DNS settings on domain registrar portal (you have it if you own personal domain) OR in enterprise setup, seek help from IT security group to create TXT record.
PowerShell 7.5.4 or later.
The Workflow: How "Manual DNS" Validation Works
In this process, we use DNS Validation. Instead of the internet checking your server, Let's Encrypt checks your Domain Name System (DNS) records.
Here is the concept in plain English:
You ask for a certificate (sub-domain) such as “sql2025ssl.mydomain.dev“
Let's Encrypt says: "Prove you own that domain. Go create a specific TXT record in your DNS."
You login to your registrar (GoDaddy, Porkbun, etc.) and create the TXT type of record.
Let's Encrypt checks the DNS from the outside.
If it matches, they issue the certificate immediately.
The PowerShell Script
This PowerShell script wraps the wacs.exe (win-acme) tool to streamline the request. It requests a certificate for your specific subdomain using the Manual DNS method.
<#
.SYNOPSIS
Generate SSL certificate using win-acme (wacs.exe) with DNS-01 validation.
.DESCRIPTION
This script generates a 90-day valid SSL certificate using win-acme for non-IIS servers.
Uses DNS-01 validation method where TXT records must be manually added to domain DNS settings.
.NOTES
Author: Auto-generated
Requires: win-acme (WACS) installed on the system
Run as Administrator
#>
# ============================================================================
# CONFIGURATION VARIABLES - Will be set interactively via user prompts
# ============================================================================
$WacsPath = ""
$DomainName = ""
$AdditionalDomains = @()
$FriendlyName = ""
$ExportPath = ""
$PfxPassword = ""
# $DnsValidationMode = "manual"
# ============================================================================
# SCRIPT BANNER
# ============================================================================
Write-Host "============================================" -ForegroundColor Cyan
Write-Host " SSL Certificate Generator using win-acme" -ForegroundColor Cyan
Write-Host " DNS-01 Validation Method" -ForegroundColor Cyan
Write-Host "============================================" -ForegroundColor Cyan
Write-Host ""
# ============================================================================
# WIN-ACME INSTALLATION CHECK
# ============================================================================
function Get-WinAcmePath {
<#
.SYNOPSIS
Prompts user for win-acme installation status and path.
.DESCRIPTION
Asks user if win-acme is installed, and if yes, prompts for the installation folder.
Validates that wacs.exe exists in the specified folder.
.OUTPUTS
Returns the validated path to win-acme installation folder, or exits if not found.
#>
$ValidPath = $false
while (-not $ValidPath) {
Write-Host "Do you have win-acme software already installed on this machine? (Yes/No): " -ForegroundColor Yellow -NoNewline
$HasWinAcme = Read-Host
if ($HasWinAcme -match "^[Yy](es)?$") {
# User has win-acme installed, ask for the path
Write-Host ""
Write-Host "Please provide the folder path where 'wacs.exe' is located." -ForegroundColor Cyan
Write-Host "Example: C:\Tools\win-acme" -ForegroundColor Gray
Write-Host ""
Write-Host "Enter win-acme folder path: " -ForegroundColor Yellow -NoNewline
$UserPath = Read-Host
# Remove quotes if user included them
$UserPath = $UserPath.Trim('"').Trim("'").Trim()
# Check if the path exists
if ([string]::IsNullOrWhiteSpace($UserPath)) {
Write-Host ""
Write-Host "[ERROR] Path cannot be empty. Please try again." -ForegroundColor Red
Write-Host ""
continue
}
if (-not (Test-Path $UserPath -PathType Container)) {
Write-Host ""
Write-Host "[ERROR] The specified folder does not exist: $UserPath" -ForegroundColor Red
Write-Host "Please verify the path and try again." -ForegroundColor Yellow
Write-Host ""
continue
}
# Check if wacs.exe exists in the specified folder
$WacsExePath = Join-Path $UserPath "wacs.exe"
if (-not (Test-Path $WacsExePath -PathType Leaf)) {
Write-Host ""
Write-Host "[ERROR] 'wacs.exe' not found in: $UserPath" -ForegroundColor Red
Write-Host "Please make sure you provide the correct folder containing wacs.exe" -ForegroundColor Yellow
Write-Host ""
# List files in the directory to help user
Write-Host "Files found in the specified folder:" -ForegroundColor Gray
$Files = Get-ChildItem -Path $UserPath -File -ErrorAction SilentlyContinue | Select-Object -First 10
if ($Files) {
foreach ($file in $Files) {
Write-Host " - $($file.Name)" -ForegroundColor Gray
}
if ((Get-ChildItem -Path $UserPath -File).Count -gt 10) {
Write-Host " - ... (more files)" -ForegroundColor Gray
}
} else {
Write-Host " (no files found or access denied)" -ForegroundColor Gray
}
Write-Host ""
continue
}
# Path is valid
Write-Host ""
Write-Host "[SUCCESS] Found wacs.exe at: $WacsExePath" -ForegroundColor Green
Write-Host ""
return $UserPath
} elseif ($HasWinAcme -match "^[Nn](o)?$") {
# User does not have win-acme installed
Write-Host ""
Write-Host "============================================" -ForegroundColor Red
Write-Host " WIN-ACME NOT INSTALLED" -ForegroundColor Red
Write-Host "============================================" -ForegroundColor Red
Write-Host ""
Write-Host "win-acme (WACS) is required to generate SSL certificates." -ForegroundColor Yellow
Write-Host ""
Write-Host "To install win-acme:" -ForegroundColor Cyan
Write-Host " 1. Visit: https://www.win-acme.com/" -ForegroundColor White
Write-Host " 2. Download the latest release (choose 'pluggable' for full features)" -ForegroundColor White
Write-Host " 3. Extract to a folder (e.g., C:\Tools\win-acme)" -ForegroundColor White
Write-Host " 4. Run this script again" -ForegroundColor White
Write-Host ""
Write-Host "Alternative download:" -ForegroundColor Cyan
Write-Host " GitHub: https://github.com/win-acme/win-acme/releases" -ForegroundColor White
Write-Host ""
exit 0
} else {
# Invalid input
Write-Host ""
Write-Host "[ERROR] Invalid input. Please enter 'Yes' or 'No'." -ForegroundColor Red
Write-Host ""
}
}
}
# Get win-acme path from user
$WacsPath = Get-WinAcmePath
# Script execution settings (now set after getting WacsPath from user)
$WacsExecutable = Join-Path $WacsPath "wacs.exe"
$LogPath = Join-Path $WacsPath "Logs"
# ============================================================================
# CERTIFICATE CONFIGURATION PROMPTS
# ============================================================================
Write-Host "============================================" -ForegroundColor Cyan
Write-Host " CERTIFICATE CONFIGURATION" -ForegroundColor Cyan
Write-Host "============================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Please provide the following information for your SSL certificate." -ForegroundColor White
Write-Host ""
# --- 1. Domain Name ---
function Get-DomainName {
while ($true) {
Write-Host "1. Domain Name" -ForegroundColor Cyan
Write-Host " The primary domain for the SSL certificate (e.g., example.com, app.mysite.org)" -ForegroundColor Gray
Write-Host ""
Write-Host " Enter domain name: " -ForegroundColor Yellow -NoNewline
$UserInput = Read-Host
$UserInput = $UserInput.Trim()
if ([string]::IsNullOrWhiteSpace($UserInput)) {
Write-Host ""
Write-Host " [ERROR] Domain name cannot be empty." -ForegroundColor Red
Write-Host ""
continue
}
# Basic domain validation (allows subdomains, no protocol)
if ($UserInput -match "^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$") {
Write-Host " [OK] Domain: $UserInput" -ForegroundColor Green
Write-Host ""
return $UserInput
} else {
Write-Host ""
Write-Host " [ERROR] Invalid domain format. Please enter a valid domain (e.g., example.com)" -ForegroundColor Red
Write-Host " Do not include http:// or https://" -ForegroundColor Yellow
Write-Host ""
}
}
}
# --- 2. Friendly Name ---
function Get-FriendlyName {
param([string]$DefaultName)
Write-Host "2. Certificate Friendly Name" -ForegroundColor Cyan
Write-Host " A descriptive name shown in certificate managers (e.g., 'Production SSL', 'API Server Cert')" -ForegroundColor Gray
Write-Host " Default: $DefaultName" -ForegroundColor Gray
Write-Host ""
Write-Host " Enter friendly name (or press Enter for default): " -ForegroundColor Yellow -NoNewline
$UserInput = Read-Host
$UserInput = $UserInput.Trim()
if ([string]::IsNullOrWhiteSpace($UserInput)) {
Write-Host " [OK] Using default: $DefaultName" -ForegroundColor Green
Write-Host ""
return $DefaultName
}
Write-Host " [OK] Friendly Name: $UserInput" -ForegroundColor Green
Write-Host ""
return $UserInput
}
# --- 3. Export Path ---
function Get-ExportPath {
$DefaultPath = "C:\Certificates"
while ($true) {
Write-Host "3. Certificate Export Path" -ForegroundColor Cyan
Write-Host " Directory where certificate files (PEM, PFX) will be saved" -ForegroundColor Gray
Write-Host " Default: $DefaultPath" -ForegroundColor Gray
Write-Host ""
Write-Host " Enter export path (or press Enter for default): " -ForegroundColor Yellow -NoNewline
$UserInput = Read-Host
$UserInput = $UserInput.Trim('"').Trim("'").Trim()
if ([string]::IsNullOrWhiteSpace($UserInput)) {
$UserInput = $DefaultPath
}
# Check if path is valid (create if doesn't exist)
try {
if (-not (Test-Path $UserInput -PathType Container)) {
Write-Host " [INFO] Directory does not exist. It will be created." -ForegroundColor Yellow
}
Write-Host " [OK] Export Path: $UserInput" -ForegroundColor Green
Write-Host ""
return $UserInput
} catch {
Write-Host ""
Write-Host " [ERROR] Invalid path format. Please enter a valid directory path." -ForegroundColor Red
Write-Host ""
}
}
}
# --- 4. PFX Password ---
function Get-PfxPassword {
Write-Host "4. PFX File Password" -ForegroundColor Cyan
Write-Host " Password to protect the exported PFX certificate file" -ForegroundColor Gray
Write-Host " Default: None (no password)" -ForegroundColor Gray
Write-Host ""
Write-Host " Enter PFX password (or press Enter for none): " -ForegroundColor Yellow -NoNewline
$UserInput = Read-Host
if ([string]::IsNullOrWhiteSpace($UserInput)) {
Write-Host " [OK] No password (PFX will be unprotected)" -ForegroundColor Green
Write-Host ""
return ""
}
Write-Host " [OK] Password set" -ForegroundColor Green
Write-Host ""
return $UserInput
}
# Gather all configuration from user
$DomainName = Get-DomainName
$FriendlyName = Get-FriendlyName -DefaultName "$DomainName SSL Certificate"
$ExportPath = Get-ExportPath
$PfxPassword = Get-PfxPassword
# ============================================================================
# VALIDATION AND SETUP
# ============================================================================
# Create export directory if it doesn't exist
if (-not (Test-Path $ExportPath)) {
Write-Host "[INFO] Creating certificate export directory: $ExportPath" -ForegroundColor Yellow
New-Item -ItemType Directory -Path $ExportPath -Force | Out-Null
}
# Build the full domain list
$AllDomains = @($DomainName) + $AdditionalDomains
$DomainList = $AllDomains -join ","
Write-Host "[INFO] Configuration Summary:" -ForegroundColor Green
Write-Host " - WACS Path: $WacsExecutable" -ForegroundColor White
Write-Host " - Domain(s): $DomainList" -ForegroundColor White
Write-Host " - Friendly Name: $FriendlyName" -ForegroundColor White
Write-Host " - PFX Export Path: $ExportPath" -ForegroundColor White
Write-Host " - Key Type: RSA (default 3072-bit)" -ForegroundColor White
Write-Host " - Validation: DNS-01 (Manual TXT Record)" -ForegroundColor White
Write-Host " - Storage: PFX archive" -ForegroundColor White
Write-Host ""
# ============================================================================
# BUILD WACS ARGUMENTS FOR FULL OPTIONS MODE (M)
# ============================================================================
<#
Win-acme v2.2.9 Full Options Mode (M) - Command Line Arguments
Reference: https://www.win-acme.com/reference/cli
SOURCE PLUGINS (--source):
manual : Option 2 - "Manual input" - Specify domain names manually via --host
iis : Option 1 - "Read bindings from IIS"
csr : Option 3 - "CSR created by another program"
ORDER PLUGINS (--order):
single : Option 4 - "Single certificate" for all domains
host : Option 2 - "Separate certificate for each host"
domain : Option 1 - "Separate certificate for each domain"
site : Option 3 - "Separate certificate for each IIS site"
CSR PLUGINS (--csr):
rsa : Option 2 - "RSA key"
ec : Option 1 - "Elliptic Curve key"
Note: RSA key size (default 3072 bits, min 2048) is configured in settings.json, not via CLI
EC Curve can be configured in settings.json (secp256r1, secp384r1)
VALIDATION PLUGINS (--validation):
For DNS-01 validation with manual TXT record creation:
manual : Option 6 - "[dns] Create verification records manually (auto-renew not possible)"
Other DNS validation plugins:
acme-dns, azure, cloudflare, route53, godaddy, script, hetzner, digitalocean, etc.
STORE PLUGINS (--store):
centralssl : Option 1 - "IIS Central Certificate Store (.pfx per host)"
pemfiles : Option 2 - "PEM encoded files (Apache, nginx, etc.)"
pfxfile : Option 3 - "PFX archive"
certificatestore : Option 4 - "Windows Certificate Store (Local Computer)"
none : Option 5 - "No (additional) store steps"
Multiple stores: Use comma-separated list (e.g., --store certificatestore,pemfiles,pfxfile)
Single store in unattended mode skips "Would you like to store it in another way too?" prompt
PFX file options:
--pfxfilepath : Path to write the .pfx file to
--pfxpassword : Password to protect the .pfx file (omit for no password)
PFX Password options (when prompted):
Option 1 - None (no password)
Option 2 - Type/paste in console
Option 3 - Search in vault
INSTALLATION PLUGINS (--installation):
iis : Option 1 - "Create or update bindings in IIS"
script : Option 2 - "Start external script or program"
none : Option 3 - "No (additional) installation steps" (for non-IIS servers)
#>
# Build argument list for wacs.exe with Full Options (M) mode
# Note: RSA key size (default 3072 bits) is configured in settings.json, not via CLI
$WacsArguments = @(
"--source", "manual"
"--host", $DomainList
"--order", "single"
"--csr", "rsa"
"--validation", "manual"
"--accepttos"
"--friendlyname", "`"$FriendlyName`""
)
# Store configuration: PFX archive only (Option 3)
# Options: 1=centralssl, 2=pemfiles, 3=pfxfile, 4=certificatestore, 5=none
$WacsArguments += @(
"--store", "pfxfile"
"--pfxfilepath", "`"$ExportPath`""
)
# PFX Password: Add only if user provided a password
# If empty, wacs.exe uses Option 1 = None (no password protection)
if (-not [string]::IsNullOrWhiteSpace($PfxPassword)) {
$WacsArguments += @("--pfxpassword", "`"$PfxPassword`"")
}
# No installation step for non-IIS servers
$WacsArguments += @(
"--installation", "none"
)
# Optional: Skip scheduled task creation for manual renewals
# (Since dns-manual doesn't support auto-renewal anyway)
$WacsArguments += @(
"--notaskscheduler"
)
# ============================================================================
# DISPLAY DNS VALIDATION INSTRUCTIONS
# ============================================================================
Write-Host "============================================" -ForegroundColor Yellow
Write-Host " DNS-01 VALIDATION INSTRUCTIONS" -ForegroundColor Yellow
Write-Host "============================================" -ForegroundColor Yellow
Write-Host ""
Write-Host "During the certificate generation process:" -ForegroundColor White
Write-Host ""
Write-Host "1. WACS will display a TXT record that you need to create" -ForegroundColor Cyan
Write-Host "2. Go to your domain registrar's DNS settings" -ForegroundColor Cyan
Write-Host "3. Create a TXT record with:" -ForegroundColor Cyan
Write-Host " - Name/Host: _acme-challenge.$DomainName" -ForegroundColor Green
Write-Host " - Type: TXT" -ForegroundColor Green
Write-Host " - Value: (provided by WACS during execution)" -ForegroundColor Green
Write-Host " - TTL: 300 (or lowest available)" -ForegroundColor Green
Write-Host ""
Write-Host "4. Wait for DNS propagation (usually 1-5 minutes)" -ForegroundColor Cyan
Write-Host "5. Press Enter in WACS when the record is created" -ForegroundColor Cyan
Write-Host ""
if ($AdditionalDomains.Count -gt 0) {
Write-Host "[NOTE] You have additional domains. You may need to create" -ForegroundColor Yellow
Write-Host " TXT records for each domain:" -ForegroundColor Yellow
foreach ($domain in $AdditionalDomains) {
Write-Host " - _acme-challenge.$domain" -ForegroundColor Yellow
}
Write-Host ""
}
Write-Host "============================================" -ForegroundColor Yellow
Write-Host ""
# ============================================================================
# EXECUTE WACS
# ============================================================================
Write-Host "[INFO] Starting win-acme certificate generation..." -ForegroundColor Green
Write-Host "[INFO] Command: $WacsExecutable $($WacsArguments -join ' ')" -ForegroundColor Gray
Write-Host ""
# Confirm before proceeding
$Confirmation = Read-Host "Ready to generate certificate? (Y/N)"
if ($Confirmation -notmatch "^[Yy]$") {
Write-Host "[INFO] Operation cancelled by user." -ForegroundColor Yellow
exit 0
}
Write-Host ""
Write-Host "[INFO] Launching WACS..." -ForegroundColor Green
Write-Host "============================================" -ForegroundColor Cyan
Write-Host ""
try {
# Execute wacs.exe with the configured arguments
$Process = Start-Process -FilePath $WacsExecutable `
-ArgumentList $WacsArguments `
-NoNewWindow `
-Wait `
-PassThru
if ($Process.ExitCode -eq 0) {
Write-Host ""
Write-Host "============================================" -ForegroundColor Green
Write-Host "[SUCCESS] Certificate generation completed!" -ForegroundColor Green
Write-Host "============================================" -ForegroundColor Green
Write-Host ""
Write-Host "Certificate files saved to: $ExportPath" -ForegroundColor White
Write-Host ""
Write-Host "Generated files:" -ForegroundColor Cyan
# List generated certificate files
if (Test-Path $ExportPath) {
$CertFiles = Get-ChildItem -Path $ExportPath -File |
Where-Object { $_.Extension -match '\.(pem|pfx|crt|key)$' } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 10
foreach ($file in $CertFiles) {
Write-Host " - $($file.Name)" -ForegroundColor White
}
}
Write-Host ""
Write-Host "[IMPORTANT] Certificate is valid for 90 days." -ForegroundColor Yellow
Write-Host " Manual DNS validation does NOT support automatic renewal." -ForegroundColor Yellow
Write-Host " You must run this script again before expiry to renew." -ForegroundColor Yellow
}
else {
Write-Host ""
Write-Host "[ERROR] Certificate generation failed with exit code: $($Process.ExitCode)" -ForegroundColor Red
Write-Host "Check the logs at: $LogPath" -ForegroundColor Yellow
}
}
catch {
Write-Host ""
Write-Host "[ERROR] Failed to execute wacs.exe: $_" -ForegroundColor Red
Write-Host "Exception details: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}
# ============================================================================
# POST-GENERATION VERIFICATION
# ============================================================================
Write-Host ""
Write-Host "============================================" -ForegroundColor Cyan
Write-Host " POST-GENERATION VERIFICATION" -ForegroundColor Cyan
Write-Host "============================================" -ForegroundColor Cyan
Write-Host ""
# Check if PFX file was created and verify its contents
try {
$PfxFiles = Get-ChildItem -Path $ExportPath -Filter "*.pfx" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if ($PfxFiles) {
Write-Host "[INFO] PFX file found: $($PfxFiles.FullName)" -ForegroundColor Green
Write-Host " - File Size: $([math]::Round($PfxFiles.Length / 1KB, 2)) KB" -ForegroundColor White
Write-Host " - Created: $($PfxFiles.LastWriteTime)" -ForegroundColor White
# Try to read certificate details from PFX
try {
$PfxCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
$PfxFiles.FullName,
$PfxPassword,
[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
)
Write-Host " - Subject: $($PfxCert.Subject)" -ForegroundColor White
Write-Host " - Issuer: $($PfxCert.Issuer)" -ForegroundColor White
Write-Host " - Thumbprint: $($PfxCert.Thumbprint)" -ForegroundColor White
Write-Host " - Valid From: $($PfxCert.NotBefore)" -ForegroundColor White
Write-Host " - Valid Until: $($PfxCert.NotAfter)" -ForegroundColor White
Write-Host " - Days Until Expiry: $(($PfxCert.NotAfter - (Get-Date)).Days)" -ForegroundColor White
$PfxCert.Dispose()
}
catch {
Write-Host " [INFO] Could not read certificate details from PFX." -ForegroundColor Yellow
}
}
else {
Write-Host "[WARNING] No PFX file found in: $ExportPath" -ForegroundColor Yellow
}
}
catch {
Write-Host "[WARNING] Could not verify PFX file: $_" -ForegroundColor Yellow
}
Write-Host ""
Write-Host "============================================" -ForegroundColor Green
Write-Host " Script completed" -ForegroundColor Green
Write-Host "============================================" -ForegroundColor GreenRunning the script is easy, but the interaction requires a bit of care. Be ready with at least three inputs i) win-acme folder path ii) Sub domain name (example:- sql2025ssl.mydomain.dev) and iii) Certificate Export Path folder
As this script runs, wacs.exe will show output similar to below:

The Waiting Game (Propagation)
The "Token" (that long random string in Content), needs to be added to domain’s DNS portal settings as per output and as a TXT record.
STOP! Do not press Enter yet.
Wait 60 seconds. Then, open a separate PowerShell prompt and verify it yourself using Google's DNS:
nslookup -q=TXT _acme-challenge.sql2025ssl.mydomain.dev 8.8.8.8
Only press <Enter> in the script window once you see the text record appear in the output of nslookup command. After successful execution, at the end, SSL’s .pfx file should be available in the export folder.
The "Delete Record" Prompt
After the certificate is successfully issued, you will see a prompt like this:
Please press after you've deleted the record
This confuses everyone.
Question: "Do I really need to delete it right now?"
Answer: No! The certificate is already on your hard drive. This is just a cleanup reminder.
Simply press <Enter> to finish the script. The TXT record that was added can be deleted from DNS provider later when you have time—it won't hurt anything to leave it there.
TL;DR
D:\win-acme\wacs.exe --source manual --host sql2025ssl.mydomain.dev --order single --csr rsa --validation manual --accepttos --friendlyname "sql2025ssl.mydomain.dev SSL Certificate" --store pfxfile --pfxfilepath "D:\Certificates" --pfxpassword "" --installation none --notaskscheduler
Final words
I value the input of fellow developers. Thank you for reading, and please feel free to drop a comment with your feedback or any technical corrections. Your expertise makes our community stronger.
Comments
Post a Comment