Install Certificates for gMSA Accounts
New 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 PowerShell scripts (compatible with PowerShell 5.1 and PowerShell 7) for common certificate installation scenarios.
Note
If you're setting up TLS/HTTPS for your Nodinite environment, see Install Nodinite v7 - Step 5: Configure TLS for Portal configuration steps before installing certificates.
Understanding Certificate Requirements for gMSA
Important
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:
| Requirement | Details |
|---|---|
| Certificate Store | Cert:\LocalMachine\My (not user profile) |
| Hash Algorithm | SHA-256 or higher (SHA-1 not supported) |
| 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) |
Note
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\Mywith appropriate ACL permissions granted to the gMSA account.
Important
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 inProgramData\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.
Understanding Certificate Storage Architecture
The diagram below shows how certificates are installed in LocalMachine store and how gMSA accounts access private keys:
(Elevated PowerShell)"] CERT[" Certificate
LocalMachine\\My Store"] CNG[" CNG Private Key
ProgramData\\Crypto\\Keys\\"] CSP[" CSP Private Key
ProgramData\\Crypto\\RSA\\MachineKeys\\"] ACL[" ACL Permissions
(Read Access)"] GMSA[" gMSA Account
(Service Identity)"] SERVICE[" Nodinite Services
(IIS/Windows Service)"] ADMIN --> CERT CERT --> CNG CERT --> CSP CNG --> ACL CSP --> ACL ACL --> GMSA GMSA --> SERVICE style ADMIN fill:#87CEEB style CERT fill:#FFD700 style CNG fill:#90EE90 style CSP fill:#90EE90 style ACL fill:#FFD700 style GMSA fill:#90EE90 style SERVICE fill:#87CEEB
This diagram shows the certificate installation flow: Administrator installs certificate in LocalMachine store (yellow), which creates a private key in either CNG or CSP location (green). ACL permissions (yellow) grant the gMSA account (green) Read access, allowing Nodinite services (blue) to use the certificate for encryption.
Certificate Installation Options
You can install certificates for gMSA accounts using:
- Self-Signed Certificates - For testing/development environments (see script below)
- PFX Import - For certificates from a Certificate Authority
- PEM Import - For OpenSSL or Linux-based PKI certificates
All methods require granting the gMSA account Read access to the private key file.
Option 1: Create and Install a Self-Signed Certificate
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:
# PowerShell script to create and install a self-signed certificate for gMSA
# Compatible with PowerShell 5.1+ and PowerShell 7+
# Must be run with Administrator privileges
param(
[string]$Subject = "CN=NodiniteProdSvc",
[string]$gMSA = "CONTOSO\NodiniteProdSvc$",
[int]$KeyLength = 2048,
[int]$ValidityYears = 3
)
# Ensure running as Administrator
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
throw "This script must be run as Administrator."
}
# Create self-signed certificate in LocalMachine store
$notAfter = (Get-Date).AddYears($ValidityYears)
Write-Host "Creating self-signed certificate $Subject (KeyLength=$KeyLength) valid until $notAfter..." -ForegroundColor Cyan
$cert = New-SelfSignedCertificate `
-Subject $Subject `
-KeyAlgorithm RSA `
-KeyLength $KeyLength `
-KeyExportPolicy Exportable `
-CertStoreLocation "Cert:\LocalMachine\My" `
-Provider "Microsoft Software Key Storage Provider" `
-HashAlgorithm "SHA256" `
-NotAfter $notAfter
if (-not $cert) {
throw "Certificate creation failed."
}
Write-Host "Certificate created successfully!" -ForegroundColor Green
Write-Host " Thumbprint: $($cert.Thumbprint)" -ForegroundColor Yellow
Write-Host " Subject: $($cert.Subject)" -ForegroundColor Yellow
Write-Host " Expires: $($cert.NotAfter)" -ForegroundColor Yellow
# Wait for key file to be fully written
Start-Sleep -Seconds 1
# Locate private key file (handles both CNG and CSP key storage)
Write-Host "`nLocating private key file..." -ForegroundColor Cyan
$keyFilePath = $null
try {
# Use PrivateKey property for compatibility with PowerShell 5.1 and 7
$rsa = $cert.PrivateKey
if ($rsa -is [System.Security.Cryptography.RSACng]) {
# CNG keys -> ProgramData\Microsoft\Crypto\Keys
$uniqueName = $rsa.Key.UniqueName
$keyFilePath = Join-Path $env:ProgramData "Microsoft\Crypto\Keys\$uniqueName"
}
elseif ($rsa -is [System.Security.Cryptography.RSACryptoServiceProvider]) {
# CSP keys -> ProgramData\Microsoft\Crypto\RSA\MachineKeys
$uniqueName = $rsa.CspKeyContainerInfo.UniqueKeyContainerName
$keyFilePath = Join-Path $env:ProgramData "Microsoft\Crypto\RSA\MachineKeys\$uniqueName"
}
if (-not $keyFilePath -or -not (Test-Path $keyFilePath)) {
throw "Could not determine private key file path."
}
Write-Host "Private key file located: $keyFilePath" -ForegroundColor Green
} catch {
throw "Failed to locate private key file: $($_)"
}
# Grant gMSA account read access to private key
Write-Host "`nGranting Read access to '$gMSA' on private key file..." -ForegroundColor Cyan
try {
$acl = Get-Acl -Path $keyFilePath
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$gMSA,
[System.Security.AccessControl.FileSystemRights]::Read,
[System.Security.AccessControl.InheritanceFlags]::None,
[System.Security.AccessControl.PropagationFlags]::None,
[System.Security.AccessControl.AccessControlType]::Allow)
$acl.AddAccessRule($rule)
Set-Acl -Path $keyFilePath -AclObject $acl
Write-Host "ACL updated successfully!" -ForegroundColor Green
} catch {
Write-Warning "Set-Acl failed: $_. Attempting icacls fallback..."
$escapedAccount = $gMSA -replace '"','\"'
$icaclsCmd = "icacls `"$keyFilePath`" /grant `"$escapedAccount`:R`" /C"
Write-Host "Running: $icaclsCmd" -ForegroundColor Yellow
cmd.exe /c $icaclsCmd
}
Write-Host "`nCertificate installation complete!" -ForegroundColor Green
Write-Host "Store location: LocalMachine\My (use certlm.msc to view)" -ForegroundColor Cyan
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"
Usage - Self-Signed Certificate
Save the script as
New-GmsaCertificate.ps1Open PowerShell as Administrator (PowerShell 5.1+ or PowerShell 7+)
Run with parameters:
.\New-GmsaCertificate.ps1 -Subject "CN=NodiniteProdSvc" -gMSA "CONTOSO\NodiniteProdSvc$" -ValidityYears 3Copy the certificate thumbprint for use in Nodinite configuration
Key Improvements
- Handles both CNG and CSP keys - Modern certificates use CNG (Crypto Next Generation), older ones use CSP (Cryptographic Service Provider)
- Automatic key location detection - Searches correct paths for both storage types
- 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
Option 2: Import an Existing Certificate (PFX)
Use this script when you have a certificate from a Certificate Authority (CA) or an existing PFX file:
# PowerShell script to import existing certificate for gMSA
# Compatible with PowerShell 5.1+ and PowerShell 7+
# Must be run with Administrator privileges
# Configuration
$certificatePath = "C:\Certificates\NodiniteCert.pfx" # Path to your certificate file
$pfxPassword = Read-Host "Enter PFX password" -AsSecureString # Prompt for password securely
$gmsaAccount = "CONTOSO\NodiniteProdSvc$" # Your gMSA account with trailing $
$certStorePath = "Cert:\LocalMachine\My"
# Import certificate into LocalMachine store
Write-Host "Importing certificate from: $certificatePath" -ForegroundColor Cyan
try {
$cert = Import-PfxCertificate `
-FilePath $certificatePath `
-CertStoreLocation $certStorePath `
-Password $pfxPassword `
-Exportable
Write-Host "Certificate imported successfully!" -ForegroundColor Green
Write-Host " Thumbprint: $($cert.Thumbprint)" -ForegroundColor Yellow
Write-Host " Subject: $($cert.Subject)" -ForegroundColor Yellow
Write-Host " Issuer: $($cert.Issuer)" -ForegroundColor Yellow
Write-Host " Expires: $($cert.NotAfter)" -ForegroundColor Yellow
} catch {
Write-Error "Failed to import certificate: $_"
exit 1
}
# Grant gMSA account access to certificate private key
Write-Host "`nGranting gMSA account access to certificate private key..." -ForegroundColor Cyan
# Get the certificate's private key file path
try {
# Use PrivateKey property for compatibility with PowerShell 5.1 and 7
$rsa = $cert.PrivateKey
$keyFilePath = $null
if ($rsa -is [System.Security.Cryptography.RSACng]) {
# CNG keys -> ProgramData\Microsoft\Crypto\Keys
$uniqueName = $rsa.Key.UniqueName
$keyFilePath = Join-Path $env:ProgramData "Microsoft\Crypto\Keys\$uniqueName"
}
elseif ($rsa -is [System.Security.Cryptography.RSACryptoServiceProvider]) {
# CSP keys -> ProgramData\Microsoft\Crypto\RSA\MachineKeys
$uniqueName = $rsa.CspKeyContainerInfo.UniqueKeyContainerName
$keyFilePath = Join-Path $env:ProgramData "Microsoft\Crypto\RSA\MachineKeys\$uniqueName"
}
if ($keyFilePath -and (Test-Path $keyFilePath)) {
# Grant Read permission to gMSA account
$acl = Get-Acl -Path $keyFilePath
$permission = $gmsaAccount, "Read", "Allow"
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
$acl.AddAccessRule($accessRule)
Set-Acl -Path $keyFilePath -AclObject $acl
Write-Host "Successfully granted '$gmsaAccount' Read access to private key!" -ForegroundColor Green
} else {
Write-Warning "Could not locate private key file at: $keyFilePath"
Write-Warning "You may need to grant permissions manually using certlm.msc"
}
} catch {
Write-Warning "Could not set private key permissions automatically: $_"
Write-Warning "Grant permissions manually:"
Write-Warning " 1. Run 'certlm.msc' (Local Machine certificates)"
Write-Warning " 2. Navigate to Personal > Certificates"
Write-Warning " 3. Right-click the certificate > All Tasks > Manage Private Keys"
Write-Warning " 4. Add '$gmsaAccount' with Read permission"
}
Write-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"
Usage - PFX Import
- Place your PFX certificate file on the server
- Edit the
$certificatePathand$gmsaAccountvariables - Open PowerShell as Administrator (PowerShell 5.1+ or PowerShell 7+)
- Run the script and enter the PFX password when prompted
- Copy the certificate thumbprint for use in Nodinite configuration
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):
# PowerShell script to import PEM certificate and key for gMSA
# Compatible with PowerShell 5.1+ and PowerShell 7+
# Must be run with Administrator privileges
# Configuration
$certPemPath = "C:\Certificates\certificate.pem" # Path to certificate PEM file
$keyPemPath = "C:\Certificates\private-key.pem" # Path to private key PEM file
$gmsaAccount = "CONTOSO\NodiniteProdSvc$" # Your gMSA account with trailing $
$certStorePath = "Cert:\LocalMachine\My"
Write-Host "Converting PEM files to PFX format..." -ForegroundColor Cyan
# Create temporary PFX with random password
$tempPfxPath = "$env:TEMP\temp_cert_$(Get-Random).pfx"
$tempPassword = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 20 | ForEach-Object {[char]$_})
$securePassword = ConvertTo-SecureString -String $tempPassword -AsPlainText -Force
try {
# Convert PEM to PFX using OpenSSL (must be installed)
$opensslPath = "openssl" # Assumes OpenSSL is in PATH
# Test if OpenSSL is available
try {
$null = & $opensslPath version 2>&1
} catch {
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
}
# Convert PEM to PFX
& $opensslPath pkcs12 -export `
-in $certPemPath `
-inkey $keyPemPath `
-out $tempPfxPath `
-password "pass:$tempPassword" 2>&1 | Out-Null
if (-not (Test-Path $tempPfxPath)) {
throw "Failed to create temporary PFX file"
}
Write-Host "PEM conversion successful!" -ForegroundColor Green
# Import the temporary PFX
Write-Host "Importing certificate into LocalMachine store..." -ForegroundColor Cyan
$cert = Import-PfxCertificate `
-FilePath $tempPfxPath `
-CertStoreLocation $certStorePath `
-Password $securePassword `
-Exportable
Write-Host "Certificate imported successfully!" -ForegroundColor Green
Write-Host " Thumbprint: $($cert.Thumbprint)" -ForegroundColor Yellow
Write-Host " Subject: $($cert.Subject)" -ForegroundColor Yellow
Write-Host " Issuer: $($cert.Issuer)" -ForegroundColor Yellow
Write-Host " Expires: $($cert.NotAfter)" -ForegroundColor Yellow
# Grant gMSA account access to certificate private key
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"
if (Test-Path $privateKeyPath) {
$acl = Get-Acl -Path $privateKeyPath
$permission = $gmsaAccount, "Read", "Allow"
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
$acl.AddAccessRule($accessRule)
Set-Acl -Path $privateKeyPath -AclObject $acl
Write-Host "Successfully granted '$gmsaAccount' Read access to private key!" -ForegroundColor Green
} else {
Write-Warning "Could not locate private key file at: $privateKeyPath"
}
Write-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"
} catch {
Write-Error "Failed to import certificate: $_"
exit 1
} finally {
# Clean up temporary PFX file
if (Test-Path $tempPfxPath) {
Remove-Item $tempPfxPath -Force
Write-Host "`nTemporary files cleaned up." -ForegroundColor Gray
}
}
Usage - PEM Import
- Install OpenSSL (if not already installed): https://slproweb.com/products/Win32OpenSSL.html
- Place your PEM certificate and key files on the server
- Edit the
$certPemPath,$keyPemPath, and$gmsaAccountvariables - Open PowerShell as Administrator (PowerShell 5.1+ or PowerShell 7+)
- Run the script
- Copy the certificate thumbprint for use in Nodinite configuration
Verifying Certificate Installation
After running any of the above scripts, verify the certificate is properly installed:
# List all certificates in LocalMachine\My store
Get-ChildItem -Path Cert:\LocalMachine\My | Format-Table Subject, Thumbprint, NotAfter
# Verify specific certificate by thumbprint
$thumbprint = "YOUR_CERTIFICATE_THUMBPRINT_HERE"
$cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $thumbprint}
if ($cert) {
Write-Host "Certificate found!" -ForegroundColor Green
Write-Host " Subject: $($cert.Subject)"
Write-Host " Issuer: $($cert.Issuer)"
Write-Host " Expires: $($cert.NotAfter)"
Write-Host " Has Private Key: $($cert.HasPrivateKey)"
} else {
Write-Warning "Certificate not found in LocalMachine\My store"
}
Certificate Management Best Practices
- 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
- 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)
- Validity Period: For production, use 1-3 year validity. For testing, shorter periods are acceptable
Tip
Set calendar reminders 60 days before expiration. Consider using Nodinite Windows Server Monitoring Agent to monitor certificate expiration dates automatically.
Warning
- 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
- Consider using Hardware Security Modules (HSM) for production certificates
Troubleshooting Certificate Issues
"Private key not found" or "Cannot locate private key file"
Script is looking in the wrong location for the private key.
- Modern certificates (CNG): Keys stored in
%ProgramData%\Microsoft\Crypto\Keys\ - Legacy certificates (CSP): Keys stored in
%ProgramData%\Microsoft\Crypto\RSA\MachineKeys\
The scripts on this page automatically detect both locations. If using a different script or manual process, check both paths.
Manual Verification
# List all keys in CNG location
Get-ChildItem "$env:ProgramData\Microsoft\Crypto\Keys\"
# List all keys in CSP location
Get-ChildItem "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\"
"Set-Acl: Access is denied"
Insufficient permissions or protected key file.
The scripts automatically fall back to icacls command. If both fail, you may need to:
- Verify you're running as Administrator
- Check if antivirus/security software is blocking ACL changes
- Manually set permissions using
certlm.msc:- Run
certlm.msc(Local Machine certificates) - Navigate to Personal > Certificates
- Right-click certificate > All Tasks > Manage Private Keys
- Add your gMSA account (e.g.,
CONTOSO\NodiniteProdSvc$) with Read permission
- Run
Certificate created but service still fails with "Cannot find certificate"
Certificate in wrong store or gMSA lacks permission.
Verify certificate is in LocalMachine\My store:
Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Subject -like "*NodiniteProdSvc*"}Verify gMSA has Read permission on private key:
# 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)
OpenSSL not installed or not in PATH.
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?
- 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. Allow at least 24 hours overlap to avoid downtime. We recommend a week overlap in production.
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