Install Certificates for gMSA Accounts# Install Certificates for gMSA Accounts
New 7.xNew 7.x
When using Group Managed Service Accounts (gMSA) with Nodinite v7, you must manually provision and manage certificates in the LocalMachine certificate store. This page provides a PowerShell script to automate certificate creation and private key access for gMSA accounts.Since gMSA accounts typically do not have local administrator privileges, certificates must be installed in the Local Machine certificate store by an administrator. This page provides PowerShell 7 scripts for common certificate installation scenarios.
Understanding Certificate Requirements for gMSA>[!IMPORTANT]
Administrator Rights Required: These scripts must be run with elevated (Administrator) privileges. The gMSA account itself cannot install certificates in the LocalMachine store.
Unlike traditional service accounts where Nodinite auto-generates certificates, gMSA accounts require manual certificate management:
Note
| Requirement | Details |>Why LocalMachine Store? gMSA accounts don't have user profiles, so they cannot access the Personal (CurrentUser) certificate store. All certificates for gMSA accounts must be installed in Cert:\LocalMachine\My with appropriate ACL permissions granted to the gMSA account.
|-------------|---------|
| Certificate Store | Cert:\LocalMachine\My (not user profile) |>[!IMPORTANT]
| Hash Algorithm | SHA-256 or higher (SHA-1 not supported) |>CNG vs CSP Key Storage: Modern Windows uses CNG (Crypto Next Generation) which stores private keys in ProgramData\Microsoft\Crypto\Keys\, while legacy certificates use CSP (Cryptographic Service Provider) storing keys in ProgramData\Microsoft\Crypto\RSA\MachineKeys\. The scripts below automatically detect which storage type your certificate uses and set permissions on the correct location. If you see "private key not found" errors with other scripts, it's usually because they only check one location.
| Key Length | Minimum 2048-bit RSA (4096-bit recommended for production) |
| Exportable | Yes (for backup and disaster recovery) |---
| Private Key Access | gMSA account must have Read permission on private key file |
| Subject Name | Recommended: Match gMSA account name (e.g., CN=NodiniteProdSvc) |## Option 1: Create and Install a Self-Signed Certificate
Important
Use this script when you need a self-signed certificate for testing or non-production environments. This script handles both CNG (modern) and CSP (legacy) private key storage locations:
SHA-1 Certificates Not Supported: Nodinite v7 requires SHA-256 or higher signature algorithms. Attempting to use SHA-1 certificates will cause installation failures. Always specify
-HashAlgorithm "SHA256"when creating certificates.
## Certificate Installation Options# PowerShell 7 script to create and install a self-signed certificate for gMSA
# Must be run with Administrator privileges
You can install certificates for gMSA accounts using:
param(
1. **Self-Signed Certificates** - For testing/development environments (see script below) [string]$Subject = "CN=NodiniteProdSvc",
2. **PFX Import** - For certificates from a Certificate Authority [string]$gMSA = "CONTOSO\NodiniteProdSvc$",
3. **PEM Import** - For OpenSSL or Linux-based PKI certificates [int]$KeyLength = 2048,
[int]$ValidityYears = 3
All methods require granting the gMSA account Read access to the private key file.)
---# Ensure running as Administrator
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
## PowerShell Script: Create Self-Signed Certificate for gMSAif (-not $isAdmin) {
throw "This script must be run as Administrator."
This script automates:}
* Creating a self-signed RSA certificate with SHA-256 in `LocalMachine\My`# Create self-signed certificate in LocalMachine store
* Locating the private key file (handles both CNG and legacy CSP keys)$notAfter = (Get-Date).AddYears($ValidityYears)
* Granting the specified gMSA account Read access to the private keyWrite-Host "Creating self-signed certificate $Subject (KeyLength=$KeyLength) valid until $notAfter..." -ForegroundColor Cyan
### Script Parameters$cert = New-SelfSignedCertificate `
-Subject $Subject `
```powershell -KeyAlgorithm RSA `
param( -KeyLength $KeyLength `
[string]$Subject = "CN=MyApp-Encryption-Cert", -KeyExportPolicy Exportable `
[string]$gMSA = "DOMAIN\gMSA$", -CertStoreLocation "Cert:\LocalMachine\My" `
[int]$KeyLength = 2048, -Provider "Microsoft Software Key Storage Provider" `
[int]$ValidityYears = 5 -HashAlgorithm "SHA256" `
) -NotAfter $notAfter
if (-not $cert) {
| Parameter | Description | Example | throw "Certificate creation failed."
|-----------|-------------|---------|}
| $Subject | Certificate subject name (CN) | "CN=NodiniteProdSvc" |
| $gMSA | gMSA account with domain and trailing $ | "CONTOSO\NodiniteProdSvc$" |Write-Host "Certificate created successfully!" -ForegroundColor Green
| $KeyLength | RSA key size (2048 or 4096) | 4096 (production) |Write-Host " Thumbprint: $($cert.Thumbprint)" -ForegroundColor Yellow
| $ValidityYears | Certificate validity period | 5 (5 years) |Write-Host " Subject: $($cert.Subject)" -ForegroundColor Yellow
Write-Host " Expires: $($cert.NotAfter)" -ForegroundColor Yellow
Full Script
Wait for key file to be fully written
<#
.SYNOPSIS# Locate private key file (handles both CNG and CSP key storage)
Create a self-signed cert in LocalMachine\My and grant a gMSA account read access to the private key.Write-Host "`nLocating private key file..." -ForegroundColor Cyan
$keyFilePath = $null
.DESCRIPTION
- Creates a self-signed RSA cert (exportable) with SHA-256 in the LocalMachine\My store.try {
- Locates the private key file (handles CNG and CSP keys). $rsa = $cert.GetRSAPrivateKey()
- Grants the specified gMSA (DOMAIN\MyGmsa$) read access to the private key file ACL.
if ($rsa -is [System.Security.Cryptography.RSACng]) {
.NOTES # CNG keys -> ProgramData\Microsoft\Crypto\Keys
- Must run elevated (Administrator) to write to LocalMachine store and set ACLs. $uniqueName = $rsa.Key.UniqueName
- gMSA account name must include the trailing $ (e.g. DOMAIN\svc-myapp$). $keyFilePath = Join-Path $env:ProgramData "Microsoft\Crypto\Keys\$uniqueName"
- SHA-256 hash algorithm is required (SHA-1 not supported by Nodinite v7). }
#> elseif ($rsa -is [System.Security.Cryptography.RSACryptoServiceProvider]) {
# CSP keys -> ProgramData\Microsoft\Crypto\RSA\MachineKeys
param( $uniqueName = $rsa.CspKeyContainerInfo.UniqueKeyContainerName
[string]$Subject = "CN=MyApp-Encryption-Cert", $keyFilePath = Join-Path $env:ProgramData "Microsoft\Crypto\RSA\MachineKeys\$uniqueName"
[string]$gMSA = "DOMAIN\gMSA$", }
[int]$KeyLength = 2048,
[int]$ValidityYears = 5 if (-not $keyFilePath -or -not (Test-Path $keyFilePath)) {
) throw "Could not determine private key file path."
}
function Ensure-Elevated {
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) Write-Host "Private key file located: $keyFilePath" -ForegroundColor Green
if (-not $isAdmin) {} catch {
Throw "This script must be run as Administrator." throw "Failed to locate private key file: $_"
}}
}
# Grant gMSA account read access to private key
function Create-Certificate {Write-Host "`nGranting Read access to '$gMSA' on private key file..." -ForegroundColor Cyan
param(
[string]$Subject,try {
[int]$KeyLength, $acl = Get-Acl -Path $keyFilePath
[int]$ValidityYears $rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
) $gMSA,
[System.Security.AccessControl.FileSystemRights]::Read,
# Create a machine-level self-signed certificate using the CNG provider (Microsoft Software Key Storage Provider). [System.Security.AccessControl.InheritanceFlags]::None,
# It's exportable so you can export if needed later. [System.Security.AccessControl.PropagationFlags]::None,
# IMPORTANT: Use SHA-256 or higher - SHA-1 is not supported by Nodinite v7. [System.Security.AccessControl.AccessControlType]::Allow)
$notAfter = (Get-Date).AddYears($ValidityYears)
Write-Host "Creating self-signed certificate $Subject (KeyLength=$KeyLength) valid until $notAfter ..." $acl.AddAccessRule($rule)
$cert = New-SelfSignedCertificate ` Set-Acl -Path $keyFilePath -AclObject $acl
-Subject $Subject ` Write-Host "ACL updated successfully!" -ForegroundColor Green
-KeyAlgorithm RSA `} catch {
-KeyLength $KeyLength ` Write-Warning "Set-Acl failed: $_. Attempting icacls fallback..."
-KeyExportPolicy Exportable ` $escapedAccount = $gMSA -replace '"','\"'
-CertStoreLocation "Cert:\LocalMachine\My" ` $icaclsCmd = "icacls `"$keyFilePath`" /grant `"$escapedAccount`:R`" /C"
-Provider "Microsoft Software Key Storage Provider" ` Write-Host "Running: $icaclsCmd" -ForegroundColor Yellow
-HashAlgorithm "SHA256" ` cmd.exe /c $icaclsCmd
-NotAfter $notAfter}
if (-not $cert) {Write-Host "`nCertificate installation complete!" -ForegroundColor Green
Throw "Certificate creation failed."Write-Host "Store location: LocalMachine\My (use certlm.msc to view)" -ForegroundColor Cyan
}Write-Host "Next steps:" -ForegroundColor Cyan
Write-Host "Created certificate with thumbprint: $($cert.Thumbprint)"Write-Host " 1. Configure Nodinite to use this certificate thumbprint: $($cert.Thumbprint)"
return $certWrite-Host " 2. Verify gMSA account can access the certificate"
}Write-Host " 3. Test service startup with gMSA account"
function Get-PrivateKeyFilePath {
param(**Usage:**
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert
)1. Save the script as `New-GmsaCertificate.ps1`
Open PowerShell 7 as Administrator
Try using GetRSAPrivateKey() which returns either RSACng or RSACryptoServiceProvider.3. Run with parameters:
try {
$rsa = $Cert.GetRSAPrivateKey() ```powershell} catch { .\New-GmsaCertificate.ps1 -Subject "CN=NodiniteProdSvc" -gMSA "CONTOSO\NodiniteProdSvc$" -ValidityYears 3
$rsa = $null ```}
Copy the certificate thumbprint for use in Nodinite configuration
if ($null -ne $rsa) {
# RSACng -> CNG keys live under ProgramData\Microsoft\Crypto\Keys**Key Improvements:** if ($rsa -is [System.Security.Cryptography.RSACng]) { $uniqueName = $rsa.Key.UniqueName* **Handles both CNG and CSP keys** - Modern certificates use CNG (Crypto Next Generation), older ones use CSP (Cryptographic Service Provider) $keyPath = Join-Path $env:ProgramData "Microsoft\Crypto\Keys\$uniqueName"* **Automatic key location detection** - Searches correct paths for both storage types if (Test-Path $keyPath) { return $keyPath }* **Fallback to icacls** - If PowerShell ACL setting fails, falls back to icacls command }* **Administrator check** - Validates elevation before attempting changes
Exportable keys - Allows certificate backup if needed
# RSACryptoServiceProvider -> legacy CSP keys under ProgramData\Microsoft\Crypto\RSA\MachineKeys if ($rsa -is [System.Security.Cryptography.RSACryptoServiceProvider]) {--- $uniqueName = $rsa.CspKeyContainerInfo.UniqueKeyContainerName $keyPath = Join-Path $env:ProgramData "Microsoft\Crypto\RSA\MachineKeys\$uniqueName"## Option 2: Import an Existing Certificate (PFX) if (Test-Path $keyPath) { return $keyPath } }Use this script when you have a certificate from a Certificate Authority (CA) or an existing PFX file:}
# Fallback: try to discover by querying certutil output for the cert thumbprint.# PowerShell 7 script to import existing certificate for gMSA
$thumb = $Cert.Thumbprint# Must be run with Administrator privileges
Write-Host "Private key not found via GetRSAPrivateKey(). Falling back to certutil search for thumbprint $thumb ..."
$storeInfo = certutil -store -v My $thumb 2>&1# Configuration
if ($LASTEXITCODE -ne 0 -and -not $storeInfo) {$certificatePath = "C:\Certificates\NodiniteCert.pfx" # Path to your certificate file
Throw "certutil failed to find certificate info. Cannot locate private key file."$pfxPassword = Read-Host "Enter PFX password" -AsSecureString # Prompt for password securely
}$gmsaAccount = "CONTOSO\NodiniteProdSvc$" # Your gMSA account with trailing $
$certStorePath = "Cert:\LocalMachine\My"
# Look for "Unique container name" (CSP) or "Key Container" / "Key Provider" style lines that include file name.
foreach ($line in $storeInfo) {# Import certificate into LocalMachine store
if ($line -match "Unique container name: (.+)$") {Write-Host "Importing certificate from: $certificatePath" -ForegroundColor Cyan
$unique = $Matches[1].Trim()
$path1 = Join-Path $env:ProgramData "Microsoft\Crypto\RSA\MachineKeys\$unique"try {
if (Test-Path $path1) { return $path1 } $cert = Import-PfxCertificate `
$path2 = Join-Path $env:ProgramData "Microsoft\Crypto\Keys\$unique" -FilePath $certificatePath `
if (Test-Path $path2) { return $path2 } -CertStoreLocation $certStorePath `
} -Password $pfxPassword `
if ($line -match "Key Container = (.+)$") { -Exportable
$unique = $Matches[1].Trim()
$path1 = Join-Path $env:ProgramData "Microsoft\Crypto\RSA\MachineKeys\$unique" Write-Host "Certificate imported successfully!" -ForegroundColor Green
if (Test-Path $path1) { return $path1 } Write-Host " Thumbprint: $($cert.Thumbprint)" -ForegroundColor Yellow
} Write-Host " Subject: $($cert.Subject)" -ForegroundColor Yellow
# certutil sometimes prints a line containing "Private key is in file: <filename>" Write-Host " Issuer: $($cert.Issuer)" -ForegroundColor Yellow
if ($line -match "Private key is in file:\s*(.+)$") { Write-Host " Expires: $($cert.NotAfter)" -ForegroundColor Yellow
$candidate = $Matches[1].Trim()} catch {
if (Test-Path $candidate) { return $candidate } Write-Error "Failed to import certificate: $_"
} exit 1
}}
Throw "Unable to locate private key file for certificate with thumbprint $thumb. (Searched common locations.)"# Grant gMSA account access to certificate private key
}Write-Host "`nGranting gMSA account access to certificate private key..." -ForegroundColor Cyan
function Grant-PrivateKeyReadToAccount {# Get the certificate's private key file path
param(try {
[string]$KeyFilePath, $rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
[string]$AccountName $keyPath = $rsaCert.Key.UniqueName
)
# Locate the private key file
Write-Host "Granting Read access to '$AccountName' on private key file: $KeyFilePath" $privateKeyPath = "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$keyPath"
# Use FileSystemAccessRule to grant Read permission if (Test-Path $privateKeyPath) {
$acl = Get-Acl -Path $KeyFilePath # Grant Read permission to gMSA account
$acl = Get-Acl -Path $privateKeyPath
# FileSystemRights.Read on a key file is usually enough (alternatively use 'FullControl' or 'ReadAndExecute' if required) $permission = $gmsaAccount, "Read", "Allow"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule( $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
$AccountName, $acl.AddAccessRule($accessRule)
[System.Security.AccessControl.FileSystemRights]::Read, Set-Acl -Path $privateKeyPath -AclObject $acl
[System.Security.AccessControl.InheritanceFlags]::None,
[System.Security.AccessControl.PropagationFlags]::None, Write-Host "Successfully granted '$gmsaAccount' Read access to private key!" -ForegroundColor Green
[System.Security.AccessControl.AccessControlType]::Allow) } else {
Write-Warning "Could not locate private key file at: $privateKeyPath"
$acl.AddAccessRule($rule) Write-Warning "You may need to grant permissions manually using certlm.msc"
}
try {} catch {
Set-Acl -Path $KeyFilePath -AclObject $acl Write-Warning "Could not set private key permissions automatically: $_"
Write-Host "ACL updated successfully." Write-Warning "Grant permissions manually:"
} catch { Write-Warning " 1. Run 'certlm.msc' (Local Machine certificates)"
Write-Warning "Set-Acl failed with error: $_. Attempting icacls fallback..." Write-Warning " 2. Navigate to Personal > Certificates"
# Fallback to icacls if Set-Acl fails Write-Warning " 3. Right-click the certificate > All Tasks > Manage Private Keys"
$escapedAccount = $AccountName -replace '"','\"' Write-Warning " 4. Add '$gmsaAccount' with Read permission"
$icaclsCmd = "icacls `"$KeyFilePath`" /grant `"$escapedAccount`:R`" /C"}
Write-Host "Running: $icaclsCmd"
cmd.exe /c $icaclsCmdWrite-Host "`nCertificate installation complete!" -ForegroundColor Green
}Write-Host "Next steps:" -ForegroundColor Cyan
}Write-Host " 1. Configure Nodinite to use this certificate thumbprint: $($cert.Thumbprint)"
Write-Host " 2. Verify gMSA account can access the certificate"
Write-Host " 3. Test service startup with gMSA account"
### MAIN```
Ensure-Elevated
**Usage:**
$cert = Create-Certificate -Subject $Subject -KeyLength $KeyLength -ValidityYears $ValidityYears
1. Place your PFX certificate file on the server
# Give a small wait in case the key files aren't visible instantly2. Edit the `$certificatePath` and `$gmsaAccount` variables
Start-Sleep -Seconds 13. Open PowerShell 7 as Administrator
4. Run the script and enter the PFX password when prompted
try {5. Copy the certificate thumbprint for use in Nodinite configuration
$keyFile = Get-PrivateKeyFilePath -Cert $cert
Write-Host "Private key file located: $keyFile"---
} catch {
Throw "Failed to determine private key file path: $_"## Option 3: Import Certificate from PEM Files (Separate Cert + Key)
}
Use this script when you have separate certificate and private key files (common with OpenSSL or Linux-based PKI):
Grant-PrivateKeyReadToAccount -KeyFilePath $keyFile -AccountName $gMSA
```powershell
Write-Host "Done. Certificate thumbprint: $($cert.Thumbprint)"# PowerShell 7 script to import PEM certificate and key for gMSA
Write-Host "Store location: LocalMachine\My (use certlm.msc to view)"# Must be run with Administrator privileges
Write-Host "Remember to put the certificate thumbprint into your app configuration so the service (running as $gMSA) can find it."
```# Configuration
$certPemPath = "C:\Certificates\certificate.pem" # Path to certificate PEM file
### Usage Examples$keyPemPath = "C:\Certificates\private-key.pem" # Path to private key PEM file
$gmsaAccount = "CONTOSO\NodiniteProdSvc$" # Your gMSA account with trailing $
**Production Environment (4096-bit, 5-year validity):**$certStorePath = "Cert:\LocalMachine\My"
```powershellWrite-Host "Converting PEM files to PFX format..." -ForegroundColor Cyan
.\Install-gMSACertificate.ps1 `
-Subject "CN=NodiniteProdSvc" `# Create temporary PFX with random password
-gMSA "CONTOSO\NodiniteProdSvc$" `$tempPfxPath = "$env:TEMP\temp_cert_$(Get-Random).pfx"
-KeyLength 4096 `$tempPassword = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 20 | ForEach-Object {[char]$_})
-ValidityYears 5$securePassword = ConvertTo-SecureString -String $tempPassword -AsPlainText -Force
try {
Test Environment (2048-bit, 2-year validity): # Convert PEM to PFX using OpenSSL (must be installed)
$opensslPath = "openssl" # Assumes OpenSSL is in PATH
.\Install-gMSACertificate.ps1 ` # Test if OpenSSL is available
-Subject "CN=NodiniteTestSvc" ` try {
-gMSA "CONTOSO\NodiniteTestSvc$" ` $null = & $opensslPath version 2>&1
-KeyLength 2048 ` } catch {
-ValidityYears 2 Write-Error "OpenSSL not found. Install OpenSSL or use Option 2 with a PFX file."
``` Write-Error "Download OpenSSL from: https://slproweb.com/products/Win32OpenSSL.html"
exit 1
### Script Output }
```text # Convert PEM to PFX
Creating self-signed certificate CN=NodiniteProdSvc (KeyLength=4096) valid until 10/29/2030 12:00:00 AM ... & $opensslPath pkcs12 -export `
Created certificate with thumbprint: 9B27C9D1939B821BB4D3F5A8E6C2A1B3D4E5F6A7 -in $certPemPath `
Private key file located: C:\ProgramData\Microsoft\Crypto\Keys\a1b2c3d4e5f6... -inkey $keyPemPath `
Granting Read access to 'CONTOSO\NodiniteProdSvc$' on private key file: C:\ProgramData\Microsoft\Crypto\Keys\a1b2c3d4e5f6... -out $tempPfxPath `
ACL updated successfully. -password "pass:$tempPassword" 2>&1 | Out-Null
Done. Certificate thumbprint: 9B27C9D1939B821BB4D3F5A8E6C2A1B3D4E5F6A7
Store location: LocalMachine\My (use certlm.msc to view) if (-not (Test-Path $tempPfxPath)) {
Remember to put the certificate thumbprint into your app configuration so the service (running as CONTOSO\NodiniteProdSvc$) can find it. throw "Failed to create temporary PFX file"
``` }
--- Write-Host "PEM conversion successful!" -ForegroundColor Green
## Import Existing Certificate (PFX) # Import the temporary PFX
Write-Host "Importing certificate into LocalMachine store..." -ForegroundColor Cyan
If you have a certificate from a Certificate Authority in PFX format:
$cert = Import-PfxCertificate `
```powershell -FilePath $tempPfxPath `
# Import PFX to LocalMachine\My -CertStoreLocation $certStorePath `
$pfxPath = "C:\Certificates\NodiniteProd.pfx" -Password $securePassword `
$pfxPassword = ConvertTo-SecureString -String "YourPfxPassword" -Force -AsPlainText -Exportable
$cert = Import-PfxCertificate `
-FilePath $pfxPath ` Write-Host "Certificate imported successfully!" -ForegroundColor Green
-CertStoreLocation "Cert:\LocalMachine\My" ` Write-Host " Thumbprint: $($cert.Thumbprint)" -ForegroundColor Yellow
-Password $pfxPassword ` Write-Host " Subject: $($cert.Subject)" -ForegroundColor Yellow
-Exportable Write-Host " Issuer: $($cert.Issuer)" -ForegroundColor Yellow
Write-Host " Expires: $($cert.NotAfter)" -ForegroundColor Yellow
Write-Host "Imported certificate with thumbprint: $($cert.Thumbprint)"
# Grant gMSA account access to certificate private key
# Grant gMSA access to private key (use Get-PrivateKeyFilePath and Grant-PrivateKeyReadToAccount functions from script above) Write-Host "`nGranting gMSA account access to certificate private key..." -ForegroundColor Cyan
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
--- $keyPath = $rsaCert.Key.UniqueName
$privateKeyPath = "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$keyPath"
Import Existing Certificate (PEM)
if (Test-Path $privateKeyPath) {
If you have a PEM-format certificate (common with OpenSSL or Linux PKI): $acl = Get-Acl -Path $privateKeyPath
$permission = $gmsaAccount, "Read", "Allow"
# Convert PEM to PFX first (requires OpenSSL) $acl.AddAccessRule($accessRule)
# openssl pkcs12 -export -out certificate.pfx -inkey privatekey.pem -in certificate.pem Set-Acl -Path $privateKeyPath -AclObject $acl
# Then import the PFX as shown above Write-Host "Successfully granted '$gmsaAccount' Read access to private key!" -ForegroundColor Green
``` } else {
Write-Warning "Could not locate private key file at: $privateKeyPath"
--- }
## Verify Certificate Installation Write-Host "`nCertificate installation complete!" -ForegroundColor Green
Write-Host "Next steps:" -ForegroundColor Cyan
After running the script, verify the certificate: Write-Host " 1. Configure Nodinite to use this certificate thumbprint: $($cert.Thumbprint)"
Write-Host " 2. Verify gMSA account can access the certificate"
```powershell Write-Host " 3. Test service startup with gMSA account"
# List certificates in LocalMachine\My
Get-ChildItem -Path "Cert:\LocalMachine\My" | } catch {
Where-Object { $_.Subject -like "CN=Nodinite*" } | Write-Error "Failed to import certificate: $_"
Format-Table Subject, Thumbprint, NotAfter, SignatureAlgorithm exit 1
} finally {
# Verify gMSA account can access the certificate # Clean up temporary PFX file
# (Run as the gMSA account or use Test-Certificate) if (Test-Path $tempPfxPath) {
$cert = Get-ChildItem -Path "Cert:\LocalMachine\My" | Remove-Item $tempPfxPath -Force
Where-Object { $_.Subject -eq "CN=NodiniteProdSvc" } Write-Host "`nTemporary files cleaned up." -ForegroundColor Gray
$cert.HasPrivateKey # Should return True }
```}
Tip
Verify SHA-256: Check the
SignatureAlgorithmcolumn showssha256RSA. If you seesha1RSA, the certificate will not work with Nodinite v7.Usage:
---1. Install OpenSSL (if not already installed): https://slproweb.com/products/Win32OpenSSL.html
- Place your PEM certificate and key files on the server
Certificate Management Best Practices3. Edit the $certPemPath, $keyPemPath, and $gmsaAccount variables
- Open PowerShell 7 as Administrator
Expiration Monitoring5. Run the script
- Copy the certificate thumbprint for use in Nodinite configuration
Monitor certificate expiration dates to avoid service disruptions:
# Check certificates expiring in next 60 days## Verifying Certificate Installation
$expiryThreshold = (Get-Date).AddDays(60)
Get-ChildItem -Path "Cert:\LocalMachine\My" | After running any of the above scripts, verify the certificate is properly installed:
Where-Object { $_.NotAfter -lt $expiryThreshold } |
Format-Table Subject, Thumbprint, NotAfter -AutoSize```powershell
```# List all certificates in LocalMachine\My store
Get-ChildItem -Path Cert:\LocalMachine\My | Format-Table Subject, Thumbprint, NotAfter
### Certificate Renewal Process
# Verify specific certificate by thumbprint
1. **30-60 days before expiration:** Generate or obtain new certificate$thumbprint = "YOUR_CERTIFICATE_THUMBPRINT_HERE"
2. **Install new certificate:** Run script or import PFX$cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $thumbprint}
3. **Update configuration:** Update certificate thumbprint in :Nodinite: settings
4. **Test in non-production:** Verify encryption/decryption worksif ($cert) {
5. **Deploy to production:** Update production configuration Write-Host "Certificate found!" -ForegroundColor Green
6. **Monitor logs:** Check for certificate-related errors Write-Host " Subject: $($cert.Subject)"
7. **Remove old certificate:** After successful validation (7-14 days) Write-Host " Issuer: $($cert.Issuer)"
Write-Host " Expires: $($cert.NotAfter)"
### Security Recommendations Write-Host " Has Private Key: $($cert.HasPrivateKey)"
} else {
* ✅ **Use strong PFX passwords** - Minimum 16 characters for export files Write-Warning "Certificate not found in LocalMachine\My store"
* ✅ **Delete temporary files** - Remove PFX files after import}
* ✅ **Restrict file system access** - Limit who can export certificates```
* ✅ **Backup certificates** - Store exports securely (encrypted, offline)
* ✅ **Use HSM for production** - Consider Hardware Security Modules for high-security environments---
* ✅ **Grant minimal permissions** - gMSA needs only Read access to private key
* ✅ **Document thumbprints** - Maintain certificate inventory for audit/recovery## Certificate Management Best Practices
### Recommended Validity Periods* **Expiration Monitoring:** Set up alerts to notify you 30-60 days before certificate expiration
* **Renewal Process:** Document your certificate renewal process and test it in non-production environments
| Environment | Key Length | Validity | Renewal Frequency |* **Backup Certificates:** Export certificates (with private keys) and store securely for disaster recovery
|-------------|------------|----------|-------------------|* **Naming Convention:** Use consistent certificate subject names that match your gMSA account names (e.g., CN=NodiniteProdSvc)
| **Production** | 4096-bit | 3-5 years | Every 2-3 years |* **Validity Period:** For production, use 1-3 year validity. For testing, shorter periods are acceptable
| **Test/UAT** | 2048-bit | 2-3 years | Every 1-2 years |
| **Development** | 2048-bit | 1-2 years | As needed |>[!WARNING]
>**Security Considerations:**
>[!TIP]>
>**Automate Renewal:** Set calendar reminders 60 days before expiration. Consider using :Nodinite: [Windows Server Monitoring Agent][] to monitor certificate expiration dates automatically.>* Always use secure passwords when creating/importing PFX files
>* Delete temporary certificate files after import
--->* Restrict access to certificate export files (store in secured locations)
>* Use proper ACLs - only grant necessary accounts Read access to private keys
## Troubleshooting>* Consider using Hardware Security Modules (HSM) for production certificates
### "Private key not found" Error---
**Cause:** Certificate was created without exportable flag or using different provider.## Troubleshooting Certificate Issues
**Solution:** Recreate certificate with `-KeyExportPolicy Exportable` and `-Provider "Microsoft Software Key Storage Provider"`.### "Private key not found" or "Cannot locate private key file"
### "Access is denied" When Setting ACL**Cause:** Script is looking in the wrong location for the private key.
**Cause:** Script not running as Administrator.**Solution:**
**Solution:** Right-click PowerShell → "Run as Administrator" and re-run script.* **Modern certificates (CNG)**: Keys stored in `%ProgramData%\Microsoft\Crypto\Keys\`
* **Legacy certificates (CSP)**: Keys stored in `%ProgramData%\Microsoft\Crypto\RSA\MachineKeys\`
### Service Cannot Decrypt Secrets
The scripts on this page automatically detect both locations. If using a different script or manual process, check both paths.
**Cause:** gMSA account lacks Read permission on private key.
**Manual verification:**
**Solution:** Re-run the `Grant-PrivateKeyReadToAccount` function or manually grant permissions via `certlm.msc`:
```powershell
1. Open `certlm.msc` (Local Machine certificates)# List all keys in CNG location
2. Navigate to Personal → CertificatesGet-ChildItem "$env:ProgramData\Microsoft\Crypto\Keys\"
3. Right-click certificate → All Tasks → Manage Private Keys
4. Add gMSA account with Read permission# List all keys in CSP location
Get-ChildItem "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\"
### SHA-1 Certificate Error```
**Cause:** Certificate was created with SHA-1 hash algorithm (not supported).### "Set-Acl: Access is denied"
**Solution:** Recreate certificate with `-HashAlgorithm "SHA256"` parameter.**Cause:** Insufficient permissions or protected key file.
---**Solution:** The scripts automatically fall back to `icacls` command. If both fail, you may need to:
## Next Step1. Verify you're running as Administrator
2. Check if antivirus/security software is blocking ACL changes
* [Configure gMSA Accounts][gMSA] - Complete gMSA setup guide3. Manually set permissions using `certlm.msc`:
* [Secret Management][] - Configure certificate-based encryption in Nodinite * Run `certlm.msc` (Local Machine certificates)
* [Install Nodinite v7][InstallNodiniteV7] - Continue with installation * Navigate to **Personal > Certificates**
* Right-click certificate > **All Tasks > Manage Private Keys**
## Related Topics * Add your gMSA account (e.g., `CONTOSO\NodiniteProdSvc$`) with **Read** permission
* [Group Managed Service Accounts (gMSA)][gMSA]### Certificate created but service still fails with "Cannot find certificate"
* [Secret Management][] - Certificate-based encryption
* [How to set Logon as a Service right][LogonAsAService]**Cause:** Certificate in wrong store or gMSA lacks permission.
* [Windows Server Monitoring Agent][] - Monitor certificate expiration
* [Microsoft: Manage Private Keys](https://learn.microsoft.com/en-us/troubleshoot/windows-server/windows-security/grant-access-certificate-private-key)<sup></sup>**Solution:**
[gMSA]:{ndr}/Install%20and%20Update/Troubleshooting/Windows/gMSA%20accounts.md1. Verify certificate is in LocalMachine\My store:
[InstallNodiniteV7]:{ndr}/Install%20and%20Update/Getting%20started/Install%20Nodinite%20v7.md
[LogonAsAService]:{ndr}/Install%20and%20Update/Troubleshooting/Windows/Logon%20as%20a%20Service%20right.md ```powershell
[Secret Management]:{ndr}/Install%20and%20Update/Troubleshooting/Secret%20Managment.md Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Subject -like "*NodiniteProdSvc*"}
[Windows Server Monitoring Agent]:{ndr}/Logging%20and%20Monitoring/Windows%20Server/Overview.md ```
2. Verify gMSA has Read permission on private key:
```powershell
# Find the private key file (example for CNG)
$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq "YOUR_THUMBPRINT"}
$rsa = $cert.GetRSAPrivateKey()
$keyPath = Join-Path $env:ProgramData "Microsoft\Crypto\Keys\$($rsa.Key.UniqueName)"
# Check ACL
Get-Acl $keyPath | Select-Object -ExpandProperty Access | Where-Object {$_.IdentityReference -like "*NodiniteProdSvc*"}
- Verify certificate thumbprint matches configuration in Nodinite
"OpenSSL not found" (Option 3 - PEM files)
Cause: OpenSSL not installed or not in PATH.
Solution:
Download and install OpenSSL from https://slproweb.com/products/Win32OpenSSL.html
Add OpenSSL bin folder to PATH, or specify full path in script:
$opensslPath = "C:\Program Files\OpenSSL-Win64\bin\openssl.exe"Alternative: Use Option 2 and convert PEM to PFX manually first
Certificate expires - how to renew without downtime?
Process:
- Create/import new certificate with new thumbprint
- Grant gMSA account Read permission on new certificate's private key
- Update Nodinite configuration with new thumbprint
- Restart Nodinite services (they'll pick up new certificate)
- After verification, delete old certificate
Tip
Set up monitoring to alert 30-60 days before certificate expiration. This gives time to plan renewals during maintenance windows.
Next Step
Configure Group Managed Service Accounts (gMSA) - Return to main gMSA configuration guide
Related Topics
- Configure Group Managed Service Accounts (gMSA) - Main gMSA setup guide
- Secret Management - Certificate-based encryption in Nodinite v7
- How to set Logon as a Service right - Service account permissions
- Troubleshooting - General installation and configuration help
- Microsoft: Grant access to certificate private key