Homelab · Part 7

Homelab Build — Part 7 — Certificate Authority

With time trustworthy across the forest, this part gives the lab its first internal certificate authority: the root-versus-subordinate decision, standing up AD CS on dc01, and issuing the lab’s first certificate.

Quick idea: This part installs an Enterprise Root CA directly on dc01 with Install-AdcsCertificationAuthority, publishes a certificate template with certutil -SetCATemplates, and issues the lab’s first certificate to dc02 with Get-Certificate — the minimum viable PKI a homelab needs before certificates can back anything else in this series.
Root CA

The trust anchor. In production it’s usually kept offline; in this lab it runs on dc01 as a deliberate simplification.

Subordinate CA

An issuing CA certified by the root, doing the actual day-to-day certificate issuance in a two-tier hierarchy.

Certificate Template

The policy object that defines what a certificate request can ask for, and who’s allowed to ask for it.

What This Part Covers

Part 6 made sure every machine in the lab agrees on the time, which matters here because certificate validity is a time window — a client with a clock five hours off will reject a perfectly good certificate as not-yet-valid or expired. This part builds on that by giving the lab its own certificate authority (CA), so later parts can issue certificates for LDAPS, RDP, internal web services, and anything else that benefits from TLS instead of relying on self-signed certificates or a public CA that has no reason to trust an internal hostname.

Series scope: This post continues from Part 4 — Domain Controllers and Part 6 — Time Hierarchy. It uses dc01 (10.10.10.10) as the CA host and dc02 (10.10.10.11) as the first client to request a certificate, inside the lab.example.com forest. For the concepts behind CA hierarchies, certificate templates, and autoenrollment in general, see Active Directory Certificate Services (AD CS) — this post applies that background to this specific lab rather than repeating it.

The Root-Versus-Subordinate Decision

Think of a two-tier PKI hierarchy the way a company handles its master keys: the root CA is the master key, kept in a safe and touched as rarely as possible, while one or more subordinate CAs are the keys office staff actually use every day. A production PKI usually follows exactly that shape — a standalone root CA that’s built, used once to certify a subordinate, and then shut down or air-gapped, with an Enterprise Subordinate CA handling every real certificate request while domain-joined and always reachable.

That two-tier design exists to limit the blast radius of a compromise: if the root’s private key is only ever online for the few minutes it takes to sign a subordinate’s certificate, there’s a vanishingly small window for an attacker to steal it. Losing an issuing subordinate CA is bad but recoverable — revoke its certificate and issue a new one from the still-secure root. Losing the root CA’s key means rebuilding the entire trust hierarchy from scratch.

Production note: This lab runs a single Enterprise Root CA directly on dc01 rather than building a separate offline root plus subordinate — the same kind of scale trade-off Part 4 made keeping both domain controllers on one site. A production environment with real compliance requirements should still split the root out, keep it offline, and put the day-to-day issuing role on a dedicated subordinate CA that isn’t also a domain controller. The AD CS conceptual post‘s “Designing the CA Hierarchy” section covers that two-tier build in full.

Running the CA role on a domain controller specifically is also worth calling out on its own: it works, because both roles ultimately depend on the same domain, but it means the CA’s private key material now lives on a Tier-0 asset that already has to be protected to the highest standard anyway. For this lab, that’s an acceptable simplification. It’s not one to carry into a production deployment without deciding that trade-off deliberately.

Installing the Root CA on dc01

Installing AD CS is a two-step process: add the Windows feature, then run the cmdlet that actually creates the CA and its self-signed root certificate. Because dc01 is already domain-joined and the account running this is a Domain Admin (a superset of what Enterprise Admin requires), no additional domain prep is needed first.

# On dc01: install the Certification Authority role and its management tools
Install-WindowsFeature -Name ADCS-Cert-Authority -IncludeManagementTools

# Configure it as an Enterprise Root CA — RSA 2048-bit key, SHA256, 10-year root validity
$params = @{
    CAType                    = "EnterpriseRootCa"
    CACommonName              = "LAB-Root-CA"
    CADistinguishedNameSuffix = "DC=lab,DC=example,DC=com"
    CryptoProviderName        = "RSA#Microsoft Software Key Storage Provider"
    KeyLength                 = 2048
    HashAlgorithmName         = "SHA256"
    ValidityPeriod            = "Years"
    ValidityPeriodUnits       = 10
}
Install-AdcsCertificationAuthority @params -Force
Key rule: -CAType EnterpriseRootCa is what makes this an Active Directory-integrated CA — the root certificate publishes itself to the forest’s Trusted Root Certification Authorities container automatically, and every domain-joined machine trusts it the next time Group Policy refreshes. A standalone CA would need that trust distributed manually with certutil -dspublish.

Because this is an Enterprise CA, there’s no manual root-certificate distribution step the way there would be with a standalone root — that automatic AD publication is the main practical advantage of accepting the DC co-location trade-off in a lab this size.

Publishing a Certificate Template

A fresh CA doesn’t publish every available template by default — it has to be told which ones it’s allowed to issue. To let dc02 request a web-server-style certificate later in this part, publish the built-in WebServer template explicitly.

# On dc01: list templates the CA currently offers
certutil -CATemplates

# Publish the WebServer template so clients can request it
certutil -SetCATemplates +WebServer

# Confirm it now appears in the CA's published list
certutil -CATemplates

Autoenrollment — letting domain members request and renew certificates on their own, without an administrator approving each request — is out of scope for this part beyond a pointer: it’s configured through Group Policy at Computer Configuration → Policies → Windows Settings → Security Settings → Public Key Policies → Certificate Services Client – Auto-Enrollment, and the target template’s permissions need both Enroll and Autoenroll granted, not just Enroll. The AD CS conceptual post covers that end to end; this part sticks to a manual, on-demand request so the CA can be verified working before layering GPO-driven automation on top of it.

Issuing the Lab’s First Certificate

dc02 is the only other domain-joined machine standing in the lab so far, which makes it the natural first client. Because dc02 is domain-joined, it can request a certificate directly from the CA over LDAP using Windows integrated authentication — no web enrollment page needed.

# On dc02: request a certificate using the WebServer template,
# authenticating to the CA directly rather than through a web front end
Set-Location -Path Cert:\LocalMachine\My
Get-Certificate -Template WebServer -Url ldap: -CertStoreLocation Cert:\LocalMachine\My

# Confirm the certificate landed in dc02's local machine store
Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Subject -like "*dc02*" }
Healthy output: Get-Certificate returns an EnrollmentResult with Status: Issued, and the follow-up Get-ChildItem shows a certificate for dc02 issued by LAB-Root-CA. A status of Pending instead means the template or CA is set to require manual approval — expected on a standalone CA, unusual on an Enterprise CA with a template that already grants Enroll.

Verifying the CA

# Confirm the Certificate Services service is running on dc01
Get-Service -Name CertSvc

# Test that a client can reach the CA's request interface
certutil -ping

# Display the CA's own configuration, including its current certificate and CRL status
certutil -CAInfo

# List templates currently published on the CA
certutil -CATemplates

Certificate Authority Setup Issues

Symptom Likely Cause Fix
Install-AdcsCertificationAuthority fails with an Enterprise Admin permissions error The account running the command isn’t a member of Enterprise Admins, even if it’s a Domain Admin in a single-domain forest with a different group nesting. Confirm the account is in Enterprise Admins specifically, not just Domain Admins, before retrying.
Get-Certificate returns Status: Pending instead of Issued The template’s security permissions grant Enroll to a group the requesting computer account isn’t a member of, or the CA is set to require manager approval for that template. Check the template’s Security tab for the requesting account’s group, and check the CA’s certificate template properties for an “Issuance Requires” approval setting.
Template doesn’t appear as an option when requesting a certificate The template exists in Active Directory but was never published to this specific CA. Publish it with certutil -SetCATemplates +TemplateName, then confirm with certutil -CATemplates.
certutil -ping fails from a client The CertSvc service isn’t running on dc01, or RPC (port 135 plus its dynamic high-port range, from Part 4’s port table) is blocked between the client and dc01. Check Get-Service -Name CertSvc on dc01 first; if it’s running, the problem is almost always the RPC path, not the CA configuration.
Issued certificate is rejected by another machine as not yet valid or expired Clock skew between the requesting machine and dc01 — certificate validity is a time window, and the two machines don’t agree on the current time. Confirm w32tm /query /status reports a small, stable offset on both machines, per Part 6 — Time Hierarchy.

Final Thoughts

The lab now has an internal PKI, even in the simplified single-tier shape this part chose deliberately. dc01 can issue certificates, dc02 has proven it can request and receive one, and every later part of this series that wants LDAPS, an internal web service, or any other TLS-backed component has somewhere to get a trusted certificate from.

What’s still missing on purpose: autoenrollment isn’t configured, so every certificate beyond this part’s manual test has to be requested by hand, and the CA is still co-located on a domain controller rather than split into the offline-root-plus-subordinate shape a production PKI would use. Both are reasonable next steps if this lab ever needs to behave more like the real infrastructure it’s modeled on.

Key takeaway: Before moving on to Part 8, confirm certutil -CAInfo reports dc01’s CA as running, and that Get-ChildItem Cert:\LocalMachine\My on dc02 shows a certificate issued by LAB-Root-CA.
Next in this series

Part 8 moves to Group Policy basics: the first real GPOs for password policy and baseline hardening, applied through the OU structure this lab has had sitting unused since Part 1.