Introduction to Active Directory Certificate Services

A Public Key Infrastructure (PKI) built on Active Directory Certificate Services (AD CS) on Windows Server 2019 provides the foundation for internal certificate management: TLS/SSL certificates for web servers and services, smart card authentication, encrypted email (S/MIME), code signing, and encrypted file systems (EFS). A proper enterprise PKI uses a two-tier or three-tier hierarchy: a root CA that is kept offline (or standalone), issuing CAs that are online and domain-joined, and optionally a policy CA between them. This guide implements a two-tier hierarchy with an offline root CA and an online Enterprise Issuing CA.

Architecture: Two-Tier PKI

Tier 1 is the offline root CA: a standalone Windows Server 2019 instance that is never connected to a network. It signs only the Issuing CA certificate. After issuance, it is powered off and locked in a physically secure location. Tier 2 is the online enterprise issuing CA: a domain-joined Windows Server 2019 member server that issues all end-entity certificates to computers, users, and services via autoenrollment.

Step 1: Install and Configure the Offline Root CA


# On the standalone (offline) Root CA server (Windows Server 2019 Server Core)
# Install AD CS Certification Authority role
Install-WindowsFeature -Name ADCS-Cert-Authority -IncludeManagementTools

# Configure as Standalone Root CA
Install-AdcsCertificationAuthority `
    -CAType StandaloneRootCa `
    -CACommonName 'Corp Root CA' `
    -CADistinguishedNameSuffix 'O=Corp,C=US' `
    -KeyLength 4096 `
    -HashAlgorithmName SHA256 `
    -ValidityPeriod Years `
    -ValidityPeriodUnits 20 `
    -CryptoProviderName 'RSA#Microsoft Software Key Storage Provider' `
    -DatabaseDirectory 'C:Windowssystem32CertLog' `
    -Force

# Configure CRL Distribution Points (CDP) and Authority Information Access (AIA)
# Point these to a web server URL where CRL files will be published
$certDB = 'C:Windowssystem32CertLog'
certutil -setreg CACRLPublicationURLs `
    "1:C:Windowssystem32CertSrvCertEnroll%3%8%9.crl`n2:http://pki.corp.local/CertEnroll/%3%8%9.crl"

certutil -setreg CACACertPublicationURLs `
    "2:http://pki.corp.local/CertEnroll/%1_%3%4.crt"

# Set CRL validity periods
certutil -setreg CACRLPeriodUnits 26
certutil -setreg CACRLPeriod "Weeks"
certutil -setreg CACRLOverlapPeriodUnits 2
certutil -setreg CACRLOverlapPeriod "Weeks"

# Restart CA to apply settings
Restart-Service CertSvc

# Publish the Root CA CRL
certutil -crl

# Export Root CA certificate and CRL for distribution
certutil -ca.cert 'C:RootCACorpRootCA.cer'
Copy-Item 'C:WindowsSystem32CertSrvCertEnroll*.crl' 'C:RootCA'
Copy-Item 'C:WindowsSystem32CertSrvCertEnroll*.crt' 'C:RootCA'

Step 2: Publish Root CA Certificate to Active Directory

Copy the Root CA certificate and CRL to a domain-joined server, then publish them to AD and to the CRL distribution web server:


# On a DOMAIN-JOINED server, import Root CA cert to AD (run as Domain Admin)
# Copy CorpRootCA.cer from the offline Root CA (USB transfer)

certutil -dspublish -f 'C:PKICorpRootCA.cer' RootCA

# Publish Root CRL to AD (required for clients to trust the root)
certutil -addstore -f Root 'C:PKICorpRootCA.cer'
certutil -addstore -f Root 'C:PKICorp Root CA.crl'

# Verify Root CA is in NTAuthCertificates (for smart card / EKU-based auth)
certutil -viewstore -enterprise Root

# Copy Root CA cert and CRL to the CRL distribution web server
# (Create a virtual directory at http://pki.corp.local/CertEnroll/)
Copy-Item 'C:PKICorpRootCA.cer' '\WEB-PKICertEnroll'
Copy-Item 'C:PKICorp Root CA.crl' '\WEB-PKICertEnroll'

Step 3: Install the Enterprise Issuing CA (Online)


# On the DOMAIN-JOINED Issuing CA server (Windows Server 2019)
Install-WindowsFeature -Name ADCS-Cert-Authority, ADCS-Web-Enrollment `
    -IncludeManagementTools

# Install as Enterprise Subordinate CA
Install-AdcsCertificationAuthority `
    -CAType EnterpriseSubordinateCa `
    -CACommonName 'Corp Issuing CA 01' `
    -CADistinguishedNameSuffix 'O=Corp,C=US' `
    -KeyLength 2048 `
    -HashAlgorithmName SHA256 `
    -CryptoProviderName 'RSA#Microsoft Software Key Storage Provider' `
    -OutputCertRequestFile 'C:PKIIssuingCA_Request.req' `
    -DatabaseDirectory 'D:CertLog' `
    -LogDirectory 'D:CertLog' `
    -Force

# This generates a certificate request file. Copy it to the offline Root CA.
# On Root CA: submit the request and issue the certificate
certreq -submit 'C:PKIIssuingCA_Request.req' 'C:PKIIssuingCA.cer'
# Or via certutil if auto-approved:
certutil -ca.cert 'C:PKIIssuingCA.cer'

# Back on the Issuing CA: install the issued certificate
certutil -installcert 'C:PKIIssuingCA.cer'
Start-Service CertSvc

Step 4: Configure CDP and AIA on the Issuing CA


# Remove default CDP/AIA entries and configure custom URLs
certutil -setreg CACRLPublicationURLs `
    "65:C:Windowssystem32CertSrvCertEnroll%3%8%9.crl`n6:ldap:///CN=%7%8,CN=%2,CN=CDP,CN=Public Key Services,CN=Services,%6%10`n2:http://pki.corp.local/CertEnroll/%3%8%9.crl"

certutil -setreg CACACertPublicationURLs `
    "1:C:Windowssystem32CertSrvCertEnroll%1_%3%4.crt`n3:ldap:///CN=%7,CN=AIA,CN=Public Key Services,CN=Services,%6%11`n2:http://pki.corp.local/CertEnroll/%1_%3%4.crt"

# Set CRL validity (shorter for online CA)
certutil -setreg CACRLPeriodUnits 1
certutil -setreg CACRLPeriod "Weeks"
certutil -setreg CACRLDeltaPeriodUnits 1
certutil -setreg CACRLDeltaPeriod "Days"
certutil -setreg CAValidityPeriodUnits 2
certutil -setreg CAValidityPeriod "Years"

# Enable all audit events for PKI compliance
certutil -setreg CAAuditFilter 127

Restart-Service CertSvc

# Publish initial CRL
certutil -crl

Step 5: Configure Certificate Templates


# Certificate templates are configured via the Certification Authority MMC
# or Certificate Templates Console (certtmpl.msc)

# List available templates
certutil -template

# Add common templates to the Issuing CA
# Via PowerShell (using the PSPKI module)
Install-Module -Name PSPKI -Force
Import-Module PSPKI

# Get the issuing CA
$ca = Get-CertificationAuthority -Name 'Corp Issuing CA 01'

# Add Workstation Authentication template
Add-CATemplate -CertificationAuthority $ca -Name 'Workstation'

# Add User template for smart card / EFS
Add-CATemplate -CertificationAuthority $ca -Name 'User'

# Add Web Server template for IIS/TLS certs
Add-CATemplate -CertificationAuthority $ca -Name 'WebServer'

# Create a custom template duplicated from Web Server
$webTemplate = Get-CertificateTemplate -Name 'WebServer'
$customTemplate = $webTemplate | Copy-CertificateTemplate -Name 'Corp Web Server v2'

# Configure the custom template: validity 2 years, auto-enroll enabled
$customTemplate | Set-CertificateTemplateAttribute -ValidityPeriod (New-TimeSpan -Days 730)
$customTemplate | Add-CertificateTemplateAcl -Identity 'CORPWEB-Servers' -AccessType Allow `
    -AccessMask Read, Enroll, AutoEnroll

Add-CATemplate -CertificationAuthority $ca -Name 'Corp Web Server v2'

Step 6: Configure Autoenrollment via Group Policy


# Enable autoenrollment for computers and users via GPO
$gpo = New-GPO -Name 'PKI Autoenrollment'
New-GPLink -Name 'PKI Autoenrollment' -Target 'DC=corp,DC=local'

# Configure via GPO UI:
# Computer Configuration > Windows Settings > Security Settings > Public Key Policies
# Certificate Services Client - Auto-Enrollment Settings:
#   Configuration Model: Enabled
#   Renew expired certificates, update pending certificates: checked
#   Update certificates that use certificate templates: checked

# User Configuration > Windows Settings > Security Settings > Public Key Policies
# Same settings as above

# Force GPO update and trigger enrollment
gpupdate /force
certutil -pulse   # triggers autoenrollment

# Verify certificates are issued
Get-ChildItem Cert:LocalMachineMy | Select-Object Subject, Thumbprint, NotAfter

CRL Distribution Web Server Setup


# On the CRL web server (pki.corp.local)
Install-WindowsFeature -Name Web-Server -IncludeManagementTools

# Create the CertEnroll virtual directory
$certEnrollPath = 'C:inetpubwwwrootCertEnroll'
New-Item -Path $certEnrollPath -ItemType Directory -Force

# Create IIS virtual directory
New-WebVirtualDirectory -Site 'Default Web Site' -Name 'CertEnroll' `
    -PhysicalPath $certEnrollPath

# Enable directory browsing (allows certutil to find files)
Set-WebConfigurationProperty `
    -Filter system.webServer/directoryBrowse `
    -Name Enabled -Value $true `
    -Location 'Default Web Site/CertEnroll'

# Allow double-escaping in URLs (required for Delta CRLs with + in name)
Set-WebConfigurationProperty `
    -Filter system.webServer/security/requestFiltering `
    -Name allowDoubleEscaping -Value $true `
    -Location 'Default Web Site'

# Schedule CRL copy from Issuing CA to web server
$action = New-ScheduledTaskAction -Execute 'robocopy.exe' `
    -Argument '\CA01CertEnroll C:inetpubwwwrootCertEnroll /COPY:DAT /R:3 /W:5'
Register-ScheduledTask -TaskName 'Sync PKI CRL' -Action $action `
    -Trigger (New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Hours 4) -Once -At (Get-Date)) `
    -RunLevel Highest

Conclusion

A two-tier PKI on Windows Server 2019 with AD CS provides a trustworthy internal certificate authority that supports TLS for all internal services, device authentication, and user certificate enrollment via autoenrollment GPO. The offline root CA keeps the trust anchor secure, while the online enterprise issuing CA integrates with Active Directory to deliver certificates transparently to domain members. Proper CRL publication via web and LDAP ensures all clients can verify certificate validity without requiring network access to the CA itself.