Create a Self‑Signed Certificate
Generate a development CA root, issue a localhost leaf certificate from it, and bind the leaf to an HTTPS listener.
Prerequisites: see Introduction.
Full source
<#
Create a development root CA, issue a localhost leaf certificate, optionally trust the root,
and start an HTTPS listener.
FileName: 6.1-Cert-SelfSigned.ps1
#>
param(
[int]$Port = $env:PORT ?? 5000,
[switch]$TrustRoot
)
# Initialize Kestrun root directory
Initialize-KrRoot -Path $PSScriptRoot
# Configure default logging
New-KrLogger |
Set-KrLoggerLevel -Value Debug |
Add-KrSinkConsole |
Register-KrLogger -Name 'myLogger' -SetAsDefault
$bundle = New-KrSelfSignedCertificate -Development `
-DnsNames 'localhost', '127.0.0.1', '::1' `
-Exportable `
-LeafValidDays 30 `
-RootValidDays 3650 `
-TrustRoot:$TrustRoot.IsPresent
$root = $bundle.RootCertificate
$cert = $bundle.LeafCertificate
Write-Host "Development root: $($root.Subject)" -ForegroundColor Cyan
Write-Host "Leaf certificate: $($cert.Subject)" -ForegroundColor Cyan
if ($bundle.RootTrusted) {
Write-Host 'Development root is present in CurrentUser\\Root.' -ForegroundColor Green
} elseif ($TrustRoot.IsPresent) {
Write-Host 'TrustRoot was requested but no Windows trust action was performed.' -ForegroundColor Yellow
} else {
Write-Host 'Root trust skipped. Use -TrustRoot on Windows if you want Chromium-family browsers to trust the root.' -ForegroundColor Yellow
}
# Show EKUs
Get-KrCertificatePurpose -Certificate $cert | Out-Host
# Configure HTTPS listener with the certificate
New-KrServer -Name 'HTTPS Demo'
Add-KrEndpoint -Port $Port -X509Certificate $cert -Protocols Http1
# Minimal route to verify HTTPS works
Enable-KrConfiguration
Add-KrMapRoute -Verbs Get -Pattern '/hello' -ScriptBlock { Write-KrTextResponse 'hello https' }
Start-KrServer
# Clean up and close all the loggers when the server stops
Close-KrLogger
Step-by-step
- Initialize root:
Initialize-KrRoot -Path $PSScriptRootso relative paths resolve predictably. - Logging: create and register a default console logger at Debug (helps trace setup and requests).
- Create the development bundle with
New-KrSelfSignedCertificate -Development:- Root: generates a CA root when you do not pass
-RootCertificate. - Leaf: issues a localhost certificate with
DNS:localhostand IP SANs for127.0.0.1/::1. - Trust (optional): add
-TrustRooton Windows to place the root inCurrentUser\Root.
- Root: generates a CA root when you do not pass
- Extract the returned certificates:
$bundle.RootCertificateis the development root CA.$bundle.LeafCertificateis the localhost server certificate.
- Inspect EKU (optional):
Get-KrCertificatePurpose -Certificate $bundle.LeafCertificateto verify usages like ServerAuth. - Validate the leaf (optional): use
Test-KrCertificate -Certificate $bundle.LeafCertificate -CertificateChain $bundle.RootCertificatewhen the development root is not trusted by the OS. - Confirm localhost coverage:
DNS:localhostIP:127.0.0.1IP:::1
- Create server:
New-KrServer. - Bind HTTPS listener:
Add-KrEndpoint -Port 5000 -IPAddress Loopback -X509Certificate $bundle.LeafCertificate.- Optionally keep HTTP on 5000 for plaintext testing.
- You can restrict protocols, e.g.,
-Protocols Http1.
- Apply config:
Enable-KrConfiguration. - Map a minimal route (e.g., GET
/hello) returning JSON so you can verify TLS termination. - Start the server:
Start-KrServer. - Client notes:
- For self‑signed certs, use
curl -korInvoke-WebRequest -SkipCertificateCheckduring development. - For Windows Chromium browsers, prefer trusting the generated development root when possible.
- For self‑signed certs, use
Try it
Save the sample locally so it’s easy to run. Copy the contents of pwsh/tutorial/examples/6.1-Cert-SelfSigned.ps1 into a new file in an empty working folder (for example, cert-https.ps1), then run:
# From your working folder
pwsh .\cert-https.ps1
curl -k https://127.0.0.1:5000/hello
Invoke-WebRequest -SkipCertificateCheck -Uri 'https://127.0.0.1:5000/hello' | Select-Object -ExpandProperty Content
# On Windows, trust the generated root first for browser testing
pwsh .\cert-https.ps1 -TrustRoot
Note: -k/SkipCertificateCheck is used because this is a self‑signed development cert.
Key points
Recommended localhost development bundle:
$bundle = New-KrSelfSignedCertificate -Development -TrustRoot
$cert = $bundle.LeafCertificate
Validate a development leaf when the root is not in the platform trust store:
$valid = Test-KrCertificate `
-Certificate $bundle.LeafCertificate `
-CertificateChain $bundle.RootCertificate `
-FailureReasonVariable 'reason'
If $valid is $false and $reason contains PartialChain, the leaf certificate is usually fine, but the issuing root is not trusted or was not supplied for validation.
Manual CA + leaf flow:
$root = New-KrSelfSignedCertificate `
-DnsNames 'Kestrun Development Root CA' `
-CertificateAuthority `
-Exportable `
-ValidDays 3650
$cert = New-KrSelfSignedCertificate `
-DnsNames 'localhost','127.0.0.1','::1' `
-IssuerCertificate $root `
-Exportable `
-ValidDays 30
When to use which parameter:
- Use
-CertificateAuthoritywhen you are creating the root/intermediate CA certificate itself. - Use
-IssuerCertificatewhen you are creating a leaf certificate that should be signed by an existing CA.
Reuse across sessions:
$password = ConvertTo-SecureString 'p@ssw0rd!' -AsPlainText -Force
$bundle = New-KrSelfSignedCertificate -Development -Exportable
Export-KrCertificate -Certificate $bundle.RootCertificate -FilePath './certs/dev-root' -Format Pfx -Password $password -IncludePrivateKey
Export-KrCertificate -Certificate $bundle.LeafCertificate -FilePath './certs/localhost' -Format Pfx -Password $password -IncludePrivateKey
$root = Import-KrCertificate -FilePath './certs/dev-root.pfx' -Password $password
$nextBundle = New-KrSelfSignedCertificate -Development -RootCertificate $root -Exportable
For a fuller import/export walkthrough, see Import / Export / Validate.
For ECDSA:
$cert = New-KrSelfSignedCertificate `
-DnsNames 'localhost','::1' `
-KeyType Ecdsa `
-KeyLength 256 `
-KeyUsage DigitalSignature `
-Exportable `
-ValidDays 30
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Browser shows certificate warning | Self‑signed not trusted | Trust the certificate locally or use curl -k for quick tests |
| Listener fails to start on 5000 | Port in use | Pick another port or stop the conflicting process |
| TLS handshake errors | Wrong IP/DNS in certificate | Include localhost, 127.0.0.1, and ::1 in -DnsNames |
| JSON route still on HTTP only | Wrong HTTPS port in test URL | Verify listener and test URL use https://127.0.0.1:5000 |
| Edge / Brave reject localhost cert | Chromium trust/store behavior | Prefer a trusted dev CA on Windows instead of a raw self-signed leaf |
| Issuer-signed leaf creation fails | Issuer is not a CA or lacks exportable private key | Create/import the issuer as an exportable CA root certificate |
Test-KrCertificate shows PartialChain | Development root is not trusted or not supplied for validation | Pass -CertificateChain $bundle.RootCertificate or trust the root on Windows |
References
- New-KrSelfSignedCertificate
- Test-KrCertificate
- Get-KrCertificatePurpose
- Add-KrEndpoint
- New-KrServer
- Enable-KrConfiguration
- Add-KrMapRoute
- Start-KrServer
Previous / Next
Previous: Logging Next: Generate a CSR