By the end of this article the hierarchy exists. An enterprise issuing authority holds a certificate signed by your offline root, publishes a revocation list to both web servers by itself every week, validates clean end to end, and can issue precisely nothing until you tell it what it is allowed to issue.
That last part is deliberate and is the one place this build departs from most published guidance. Templates are designed in the next article, and until then this authority holds no capability it has not been given.
Prerequisites. A domain-joined Windows Server 2022 or 2025 machine named SUBCA with a D: volume. You are logged in as a Domain Administrator, not a local administrator, because registering an enterprise authority writes to the directory’s configuration partition and a local account cannot do it. The root built in [P 2.1], available to be powered on. The two web servers from [P 3.1] live, with pki.catsnackjack.com resolving and the SUBCA computer account already granted write access to both shares. The root’s own certificate and revocation list must already be sitting in both shares, which is [P 3.1] step 6. Without them the distribution points inside the certificate you are about to be issued resolve to nothing, and the test at the end of this article cannot pass. Removable media or a hypervisor shared folder. One file goes to the root and four come back.
Step 1. Folders and policy file
$folders = @("D:\PKI\CDP","D:\PKI\CSR","D:\PKI\DB","D:\PKI\Certs\Root","D:\PKI\Certs\Sub")
foreach ($f in $folders) { New-Item -ItemType Directory -Path $f -Force }
Get-ChildItem D:\PKI
The subordinate’s policy file is a different document from the root’s, and the differences are not cosmetic.
$policy = @"
[Version]
Signature="$Windows NT$"
[PolicyStatementExtension]
Policies = AllIssuancePolicy, InternalPolicy
Critical = FALSE
[AllIssuancePolicy]
OID = 2.5.29.32.0
[InternalPolicy]
OID = 1.2.3.4.1455.67.89.5
Notice = "CatSnackJack Certificate Authority - Internal Use Only"
[Certsrv_Server]
RenewalKeyLength = 4096
AlternateSignatureAlgorithm = 0
LoadDefaultTemplates = 0
"@
$policy | Out-File -FilePath "C:\Windows\CAPolicy.inf" -Encoding ASCII
Get-Content "C:\Windows\CAPolicy.inf"
| Setting | Value | Why |
|---|---|---|
| LoadDefaultTemplates | 0 | Here it does real work. On an enterprise authority this suppresses the default template set, so the authority starts with nothing published and can issue nothing. Everything it is later allowed to issue is granted deliberately in [P 5.1]. |
| RenewalValidityPeriod | omitted | Deliberately absent. A subordinate’s lifetime is set by the authority that signs it, so this directive is inert here. Including it with a number invites the belief that it controls something, which it does not. |
| CRL periods | omitted | Set by registry in step 5 instead, where they can be adjusted without a reinstall. |
| [CRLDistributionPoint] / [AuthorityInformationAccess] | omitted | These sections apply only to a root. A subordinate’s own distribution points come from whoever signs it, so including them here achieves nothing. |
| AlternateSignatureAlgorithm | 0 | Same reasoning as the root. The widest possible compatibility for a certificate everything must parse. |
Step 2. Install and configure, producing a request
Install the role, then configure it as an enterprise subordinate authority. Configuration produces a certificate signing request and leaves the service stopped, because it has no certificate yet.
On the Role Services page, select Certification Authority and nothing else. This build does not install Certification Authority Web Enrollment. Every request in this series that is made by hand goes through the Certificates console on a domain member, or through certreq from an administrative workstation for anything that is not domain joined, and neither needs a web site on the authority. Leaving it out keeps an authenticating HTTP endpoint off the machine holding your issuing key, which is the escalation path [P 4] The Issuing CA described, and there is then nothing to remember to harden later.
| Setting | Value | Why |
|---|---|---|
| Setup Type | Enterprise CA | Required for templates, autoenrollment, and directory-derived subjects including the security identifier extension. |
| CA Type | Subordinate CA | Signed by the root. Produces a request rather than a self-signed certificate. |
| CA Common Name | CatSnackJack Issuing CA | Permanent. Appears in the Issued By field of every end-entity certificate. Confirm the distinguished name preview before continuing. |
| Cryptographic Provider | RSA#Microsoft Software Key Storage Provider | Matches the root. Substitute a hardware provider if one is in use. |
| Key Length / Hash | 4096 / SHA256 | Matches the root. A subordinate stronger than its parent buys nothing; weaker undermines the chain. |
| Request file location | D:\PKI\CSR\ | Changed from the default system volume path. Note the exact filename; you need it in the next step. |
| Database and log path | D:\PKI\DB | Must exist first. Chosen at configuration and relocatable later, at the cost of an outage. See the note below. |
The database and log location is an operational choice rather than a permanent one, and it is worth saying so because the rest of that table genuinely is permanent. Microsoft documents a supported move: stop the certificate services service, move the files, update DBDirectory, DBLogDirectory and DBSystemDirectory in the registry, start the service, and confirm the authority still issues. It costs a maintenance window and a verified backup rather than a rebuild. Choose the location deliberately anyway, because an outage is still an outage, but do not let anyone tell you the disk you picked on day one is the disk you are stuck with.
Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
Install-AdcsCertificationAuthority `
-CAType EnterpriseSubordinateCA `
-CACommonName "CatSnackJack Issuing CA" `
-CryptoProviderName "RSA#Microsoft Software Key Storage Provider" `
-KeyLength 4096 `
-HashAlgorithmName SHA256 `
-OutputCertRequestFile "D:\PKI\CSR\SUBCA.req" `
-DatabaseDirectory "D:\PKI\DB" `
-LogDirectory "D:\PKI\DB" `
-Force
Get-ChildItem D:\PKI\CSR
Get-Service certsvc | Select-Object Name, Status
Expected result: a request file, and the certificate services service present but not running. That stopped service is correct and is not an error. Failure mode: if you ran this as a local administrator the configuration fails when it tries to register the authority in the directory, and the recovery is to remove the role configuration and repeat as a Domain Administrator.
Step 3. The signing ceremony, and what comes back
Carry the request to the root. Power it on, correct its clock before doing anything else, and submit.
# on ROOTCA
certreq -submit D:\RootCA\CSR\SUBCA.req D:\RootCA\Certs\SUBCA.cer
A standalone authority does not issue automatically. The submission returns a request identifier and leaves the request pending, which is the correct behaviour and is the point at which a human is supposed to think. Note the number it gives you. The examples below show 2 because that is what a first subordinate on a fresh root usually receives, but read yours rather than trusting mine.
# on ROOTCA, substituting your own request ID
certutil -resubmit 2
certreq -retrieve 2 D:\RootCA\Certs\SUBCA.cer D:\RootCA\Certs\SUBCAchain.p7b
certutil -dump D:\RootCA\Certs\SUBCA.cer
-retrieve takes two output paths and produces two different things. The first is the subordinate’s certificate alone. The second is a PKCS #7 holding that certificate and the root above it, and it is the file you install later. Take both. There is a certutil -ca.chain verb that appears to do this job and does not: run on the root it retrieves the root’s own chain, which on a self-signed authority is a single certificate, and installing that on the subordinate completes nothing.
Read the dump and check three things against your design document rather than your memory. The Subject reads CN=CatSnackJack Issuing CA. The validity is five years, which came from the root’s registry ceiling and not from anything the subordinate asked for. And the distribution point and authority access extensions carry http://pki.catsnackjack.com/pki/ and not a local path, a directory path, or the root’s hostname. If that last one is wrong, stop. Fix the root’s configuration, delete this request, and submit a new one. A certificate with the wrong distribution point cannot be repaired, only replaced, and replacing it after the authority has issued anything means reissuing everything.
Now collect what the subordinate needs on arrival, which is more than the certificate you came for. Sign a current revocation list first, so you carry a fresh one rather than whatever was written at installation.
# on ROOTCA
certutil -crl
dir C:\Windows\System32\CertSrv\CertEnroll
dir D:\RootCA\AIA
dir D:\RootCA\CDP
Those listings answer a question this build sheet used to leave open. The filenames they print are the names, and they are the only correct ones. The authority derived them from the same tokens it wrote into the URLs inside the certificate you just signed, so they agree by construction, and any name you supply instead will not. You will notice the certificate carries the root server’s DNS name as a prefix and the revocation list does not. That is %1_%3%4.crt against %3%8%9.crl, and it is deliberate rather than an inconsistency. The certificate appears in CertEnroll because that is where the authority wrote it at installation, before you moved its publication path. The AIA folder is where future publications land, so on a freshly built root it is often empty and that is not a fault. Copy the certificate from wherever the listing actually shows it, which is the habit this whole section is trying to build.
Copy with wildcards and retype nothing. The root’s two files go to the same export folder [P 3.1] already takes them from, because they are the same files doing a second job.
copy C:\Windows\System32\CertSrv\CertEnroll\*.crt E:\RootExport\
copy D:\RootCA\CDP\*.crl E:\RootExport\
copy D:\RootCA\Certs\SUBCA.cer E:\SubExport\
copy D:\RootCA\Certs\SUBCAchain.p7b E:\SubExport\
Four files leave the root and each does a different job.
| File | What it is | Why it travels |
|---|---|---|
| ROOTCA_CatSnackJack Root CA.crt | the root’s own certificate | the subordinate, the forest and the web servers must all trust it |
| CatSnackJack Root CA.crl | the root’s revocation list | nothing can validate the subordinate’s certificate without it |
| SUBCA.cer | the signed subordinate certificate | reading and verifying before you commit anything |
| SUBCAchain.p7b | subordinate and root together | the file you actually install |
Do not rename any of them at any point in the transfer. Not to shorten them, not to remove the spaces, not to tidy a folder. Those filenames are the final path segment of the URLs inside every certificate this hierarchy will ever issue, and a rename here breaks revocation checking for the life of the PKI.
Shut the root down.
Step 4. Arrival: trust it, publish it, verify, then install
Three parties have never seen this root: the machine you are standing on, the forest, and the web servers. All three have to be corrected before the certificate services service can start, and the order matters.
Put E:\RootExport\ into D:\PKI\Certs\Root\ and E:\SubExport\ into D:\PKI\Certs\Sub\. The web servers were dealt with in [P 3.1] step 6 and are a prerequisite of this article rather than a task in it.
Trust it locally first. The directory publication below is the right long-term mechanism and it is not immediate, because domain members pull the root into their own store on the Group Policy refresh, and that includes this machine. Waiting an hour to discover whether the service starts is not a diagnosis.
Get-ChildItem D:\PKI\Certs\Root\* -Include *.crt,*.crl |
ForEach-Object { certutil -addstore -f Root $_.FullName }
Then publish to the forest, which is what makes every other domain member trust the hierarchy without you touching a client.
Get-ChildItem D:\PKI\Certs\Root\*.crt | ForEach-Object { certutil -dspublish -f $_.FullName RootCA }
Get-ChildItem D:\PKI\Certs\Root\*.crl | ForEach-Object { certutil -dspublish -f $_.FullName }
Two publications, not three. An earlier version of this article also published the root to the enterprise authentication store with NTAuthCA, and that was wrong twice over. The store’s requirement is that the authority issuing authentication certificates is listed, which is this subordinate and not the root, and an enterprise authority publishes itself there during its own installation. Putting the root in instead grants authentication authority to the entire hierarchy, including subordinates that do not exist yet and whose purpose nobody has decided. You will read that store at the end of this step rather than writing to it now.
Verify before you commit. This one command builds the chain and actually fetches every URL inside the certificate, so it exercises the local trust you just established, the directory publication, the DNS name, the web servers, and the distribution point configuration you set on the root two articles ago.
certutil -verify -urlfetch D:\PKI\Certs\Sub\SUBCA.cer
A clean result here makes everything below a formality. A failure here names which of five things is wrong while nothing is yet committed, which is the whole reason it comes before the install rather than after it.
certutil -installCert D:\PKI\Certs\Sub\SUBCAchain.p7b
Start-Service certsvc
Get-Service certsvc | Select-Object Name, Status
certutil -store -enterprise NTAuth
Expected result: the service runs, and the enterprise authentication store lists this issuing authority, placed there by its own installation. If that store is empty, publish the issuing authority’s certificate to it deliberately, and understand that you are granting exactly the ability for certificates from this authority to authenticate users. The root does not belong in that store.
If you built this hierarchy from an earlier version of this article, your root is in that store and should come out. Removing a certificate from the enterprise authentication store is the same operation described in [P 11.1] step 5, and it is worth doing deliberately rather than leaving a grant nobody chose in place.
Failure mode: if the service refuses to start, the cause is almost always the chain, and the verify above will already have told you which part. If you skipped it, the two candidates are a root this machine does not trust yet, and a chain file that does not match the key generated in step 2 because a second request was made somewhere along the way.
Step 5. Validity ceiling, revocation schedule and auditing
certutil -setreg CA\ValidityPeriodUnits 1
certutil -setreg CA\ValidityPeriod "Years"
certutil -setreg CA\CRLPeriodUnits 1
certutil -setreg CA\CRLPeriod "Weeks"
certutil -setreg CA\CRLOverlapUnits 3
certutil -setreg CA\CRLOverlapPeriod "Days"
certutil -setreg CA\CRLDeltaPeriodUnits 0
certutil -setreg CA\AuditFilter 127
net stop CertSvc && net start CertSvc
certutil -getreg CA\CRLOverlapUnits
The validity ceiling caps every certificate this authority issues at one year regardless of what a template asks for, which is a useful backstop against a template written optimistically. Note the scope carefully, because it is wider than it sounds: this is not an end-entity setting. A subordinate authority signed by this CA is capped by the same value, so the one occasion on which you will need to change it deliberately is the day you sign somebody else’s certificate authority request. [P 7.1] Build Sheet: Signing the Cloud PKI CSR performs exactly that, and does it as a bounded ceremony rather than a permanent loosening. The revocation list publishes weekly and automatically. The overlap is three days rather than the twelve hours you will see in many builds, for the same reason the root’s is generous: the overlap is how long the estate keeps working if publication fails. Twelve hours means a failure on Friday evening is an outage by Saturday morning. Three days means it is a Monday problem, and on a weekly publication cycle it costs nothing.
Auditing needs its second switch, which is not on this machine’s authority configuration but in policy: enable success and failure auditing for certification services under object access, in local security policy or a group policy object applied to this server. With only the filter set you will have an authority that looks audited and records nothing. Prove it by causing an event and finding it, not by reading the setting back.
Step 6. Distribution points, including the two write paths
This is the configuration the whole architecture has been building toward. Clear the defaults first, as on the root, but understand that the failure they cause here is worse rather than better.
On the root, the seeded defaults point at ROOTCA, a machine that is about to be switched off for a decade, so they fail for everybody and the problem announces itself. Here they point at SUBCA, which is domain-joined, running, and perfectly reachable. Skip this step and the build works. It works on the corporate network, for domain members, in your test, on the day you do it. It fails for anything off the network, for every unjoined device, and for the managed endpoint fleet in the cloud article at the end of this series, months later, with nothing having changed and no obvious cause. A default that fails immediately is a gift. This one waits.
certutil -setreg CA\CRLPublicationURLs ""
certutil -setreg CA\CACertPublicationURLs ""
net stop CertSvc && net start CertSvc
certutil -setreg CA\CRLPublicationURLs "65:D:\PKI\CDP\%3%8%9.crl\n65:\\PKI-WEB01\PKI\%3%8%9.crl\n65:\\PKI-WEB02\PKI\%3%8%9.crl\n6:http://pki.catsnackjack.com/pki/%3%8%9.crl\n79:ldap:///CN=%7%8,CN=%2,CN=CDP,CN=Public Key Services,CN=Services,%6%10"
certutil -setreg CA\CACertPublicationURLs "1:C:\Windows\system32\CertSrv\CertEnroll\%1_%3%4.crt\n2:http://pki.catsnackjack.com/pki/%1_%3%4.crt\n2:ldap:///CN=%7,CN=AIA,CN=Public Key Services,CN=Services,%6%11"
net stop CertSvc && net start CertSvc
certutil -getreg CA\CRLPublicationURLs
certutil -getreg CA\CACertPublicationURLs
Five revocation entries and three authority entries, and the distinction between them is the point. Three of the revocation entries are write targets: a local folder and the two web server shares. The authority writes the list to all three on every publication cycle, itself, with no replication technology between them and nothing to fall out of sync. None of those three paths appears in a certificate, and none of them is ever seen by a client.
The other two entries are what clients see, and the same is true of the two extension entries in the authority certificate value below them. HTTP comes first in both, and the directory second, because relying parties walk the list in order and stop at the first location that answers. HTTP is the only scheme every relying party can use: iPhones and Android devices, macOS and Linux hosts, appliances doing network authentication, machines that were never joined to the domain, and domain members sitting in a hotel. The directory entry serves exactly one population, a domain-joined Windows client that can currently reach a domain controller, and for everyone else it is a lookup that has to fail before the useful one is tried. Put the directory first and you have charged every mobile device in the estate a timeout on every chain it builds.
This is a design position rather than a product rule, and it is worth knowing that the older convention was the reverse. Putting the directory first was reasonable when the entire population was domain-joined Windows on the corporate network. It stopped being reasonable somewhere around the point that phones started presenting certificates, and by the time you reach the cloud article at the end of this series the population validating this hierarchy is mostly not on your network at all.
As on the root, the numeric prefixes are bit masks and the two registry values do not share a vocabulary, which is why published tables of them disagree. Confirm what you have configured by reading it back with -getreg, which prints the flag names in plain language, rather than trusting any table including this one.
The percent tokens work as described in the root build sheet, and the directory paths add five that did not appear there. %2 is the authority’s short name and %7 its sanitised short name, which is what the directory objects are actually named. %10 and %11 are the object class of the distribution point and authority information containers respectively. And %6 is the distinguished name of the forest configuration partition, which is the same value you hand-fed the offline root as DSConfigDN because it could not look it up. Here the authority reads it from the directory itself, which is one of the concrete things enterprise integration buys.
If your directory is a subdomain of your public name, which is what Microsoft recommends and what most estates should be doing, every label becomes its own domain component in that distinguished name. A forest rooted at corp.example.com produces CN=Configuration,DC=corp,DC=example,DC=com, and the authority builds it for you here rather than being told.
Step 7. Publish, and confirm it landed in three places
certutil -crl
Get-ChildItem D:\PKI\CDP
Get-ChildItem \\PKI-WEB01\PKI\
Get-ChildItem \\PKI-WEB02\PKI\
Expected result: the same revocation list, with the same size and timestamp, in all three locations, written by the authority in one operation. Failure mode: if the local copy appears and the two shares do not, the authority’s computer account does not have write access to those shares, which is [P 3.1] step 2. This is the moment that permission is proven, and it fails silently at the authority rather than loudly anywhere else, so check rather than assume.
Copy-Item C:\Windows\System32\CertSrv\CertEnroll\*.crt -Destination \\PKI-WEB01\PKI\
Copy-Item C:\Windows\System32\CertSrv\CertEnroll\*.crt -Destination \\PKI-WEB02\PKI\
The revocation list reaches both shares by itself, because step 6 gave it three write targets. The authority’s own certificate has only one, the local folder, and the HTTP entry beside it is an advertisement rather than a write instruction. So the authority information access URL inside every certificate this authority issues points at a file that nothing has put there. It does not fail today, because verifying the subordinate uses the root’s authority access rather than its own. It fails the first time something outside the domain validates an end-entity certificate, which is [P 5.1] and everything after it.
This is a one-off copy rather than a recurring chore. It repeats only when this authority’s certificate is renewed, which is the same calendar entry as the year-four surprise below.
The end-to-end test
The test that proves this article is not “issue a certificate”, because this authority deliberately cannot. It is that the hierarchy validates from outside itself.
# on SUBCA
pkiview.msc
# from a non-domain machine on another network, with the root certificate trusted
# locally, and this file taken from C:\Windows\System32\CertSrv\CertEnroll\ on SUBCA
certutil -verify -urlfetch "SUBCA_CatSnackJack Issuing CA.crt"
In the enterprise PKI view, every location under both authorities should read OK: the root’s certificate and revocation list, and the issuing authority’s certificate, revocation list and every distribution point. An error here names the exact location that is failing, which makes it the fastest diagnostic in the entire product.
Then from a machine with no relationship to the domain, verify the issuing authority’s own certificate with fetching enabled. What you want to see is a chain that builds to the root, a revocation check that retrieves the list over HTTP and finds it valid, and no location that times out. That single command exercises the root build, the web servers, the DNS name, the share permissions and the distribution point configuration in one go. If it passes, the infrastructure is correct and everything after this is policy.
A worked example: the year-four surprise
The authority you just built holds a five-year certificate and issues one-year certificates. Suppose it was signed on the first of August 2026, so it expires on the first of August 2031.
A machine enrolling on the first of August 2029 receives a certificate valid until August 2030, exactly as designed. A machine enrolling on the first of February 2031 receives one valid until the first of August 2031, six months rather than twelve, because that is all the authority has left to give. By June 2031 the estate is receiving certificates measured in weeks and renewing constantly, and the first anyone hears of it is a service desk noticing unusual renewal volume.
Nothing has broken. The behaviour is correct. The mistake was made five years earlier, by not putting the renewal in a calendar. Renew this authority at its half-life, which is August 2028, and none of the above ever happens. Put that date alongside the root’s annual revocation appointment; between them they are the entire long-term maintenance schedule of this PKI.
Rollback
Before this authority has issued anything, rollback is real but not free, because it has touched the directory.
Uninstall-AdcsCertificationAuthority -Force
Uninstall-WindowsFeature ADCS-Cert-Authority
Remove-Item C:\Windows\CAPolicy.inf
Remove-Item D:\PKI\CDP, D:\PKI\CSR, D:\PKI\DB -Recurse -Force
Removing the role does not remove the authority’s registration from the directory’s configuration partition, its enrolment services object, or its published certificates and revocation lists. Those are what a later reader of this series will encounter as an orphaned authority that clients still trust and whose revocation list expired years ago. If you are abandoning an authority rather than replacing it, clean up those objects deliberately, and revoke its certificate at the root while the root still exists to do it.
Completion checklist
The service is running. The authority’s certificate reads five years, the correct common name, and HTTP distribution points. The root is published to the forest’s trusted roots, its revocation list is in the directory, and the issuing authority, not the root, appears in the enterprise authentication store, confirmed by reading the store rather than by having published anything to it. The validity ceiling is one year, the revocation list publishes weekly with a three day overlap, delta lists are off. Publication writes to the local folder and both web server shares in one operation, verified by comparing all three. Auditing is enabled on both the authority and in policy, and proven by finding a real event. The enterprise PKI view is clean for both authorities. A machine outside the domain can verify the issuing authority’s certificate with fetching enabled, retrieve the revocation list over HTTP, and report it valid. No certificate templates are published, and the authority can issue nothing.
That last line is the correct end state, and it is the only interesting thing this authority cannot yet do. The next article decides what it is allowed to issue, which turns out to be the article where a certificate authority stops being infrastructure and starts being an attack surface.
On-Prem PKI
‹ Previous: [P 4] The Issuing CA: What Enterprise Integration Buys and What It Costs
Next: [P 5] Templates and the Discipline of Issuance ›




