Homelab Build — Part 4 — Domain Controllers
With pfSense enforcing the Servers segment in Part 3, this part gives that segment its first real occupants: a Windows Server domain controller that creates the forest, then a second domain controller so the lab survives losing either one.
Install-ADDSForest, then adds dc02 as a second domain controller with Install-ADDSDomainController — the two commands that turn a naming plan and a VLAN into an actual Active Directory forest.
Creates the forest root domain and promotes the first server into its first domain controller.
Joins and promotes an additional server into an existing domain, replicating from a source DC.
A partial, forest-wide searchable index every domain controller hosts by default.
What This Part Covers
Part 1 reserved lab.example.com as the forest name and dc01/dc02 as the domain controller hostnames. Part 3 turned the Servers segment from a row in a table into a real VLAN with a gateway, a DHCP scope, and a firewall rule. Both were preparation. This part is where a domain controller actually exists: dc01 gets promoted first, creating the forest from nothing, and dc02 follows as a second domain controller replicating from the first.
Think of the forest root domain as pouring the foundation of a building. Every part after this one — DNS in Part 5, certificates in Part 7, Group Policy in Part 8 — gets built on top of it. A foundation poured wrong is expensive to fix once walls stand on it; that is exactly why Part 1 spent an entire post on the domain name and addressing scheme before any of this ran.
lab.example.com (NetBIOS LAB), hostname prefixes dc01/dc02, and the Servers segment 10.10.10.0/24 with static range .1–.20. For what a forest, domain, and domain controller actually are conceptually, see Active Directory — Part 2 — Domains, Trees & Forests and Active Directory — Part 3 — Domain Controllers; this post does not repeat that ground and goes straight to standing one up.
Why a Second Domain Controller?
A forest with exactly one domain controller works, right up until that one server is unavailable. Every domain-joined machine in the lab — the pfSense box aside — authenticates through Kerberos against a domain controller, resolves names against AD-integrated DNS running on a domain controller, and applies Group Policy from SYSVOL replicated between domain controllers. One DC means all three of those are a single point of failure.
A reboot, a failed patch, or a crashed VM takes down authentication, DNS, and policy for the whole lab at once.
Either can be rebooted, patched, or rebuilt while the other keeps the domain functioning normally.
Every later part of this series — DNS, certificates, Group Policy — inherits the single point of failure if it isn’t fixed here.
This is also the first part of the series where SYSVOL and the AD database actually replicate between two servers, which makes it the natural place to introduce repadmin and dcdiag — the tools every later troubleshooting post in the Active Directory category assumes are already familiar.
Prerequisites Before Promoting dc01
Both VMs need a static address inside the Servers VLAN’s reserved range before AD DS goes anywhere near them — DHCP addresses are fine for client testing later in this series, but not for domain controllers. dc01 and dc02 both build on a fresh Windows Server 2025 VM on the same Proxmox host from Part 2, attached to the SERVERS VLAN interface pfSense created in Part 3.
| Item | Value for This Lab |
|---|---|
| dc01 hostname / IP | dc01 / 10.10.10.10 |
| dc02 hostname / IP | dc02 / 10.10.10.11 |
| Gateway / prefix | 10.10.10.1 / /24, from Part 1’s static reservation .1–.20 |
| Forest / domain name | lab.example.com, NetBIOS LAB |
| Domain / forest functional level | Win2025 — every DC in this lab runs Windows Server 2025, so there is no reason to target an older level |
# On dc01: assign the static IP and point DNS at pfSense for now —
# this gets replaced with AD-integrated DNS once the forest exists
New-NetIPAddress -InterfaceAlias "Ethernet" -IPAddress 10.10.10.10 -PrefixLength 24 -DefaultGateway 10.10.10.1
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 10.10.10.1
# Rename the computer to match Part 1's naming convention, then restart
Rename-Computer -NewName "dc01" -Restart
Creating the Forest — Promoting dc01
With the hostname and static IP set, installing AD DS is a two-step process: add the Windows feature, then run the cmdlet that actually creates the forest. Install-ADDSForest installs and configures the DNS Server service by default, so there is no separate DNS role step here.
# Install the AD DS role and its management tools (dcdiag, ADUC, the ActiveDirectory module)
Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools
# Create the forest root domain — prompts interactively for the DSRM password,
# which is the preferred way to supply it rather than passing plain text
Install-ADDSForest -DomainName "lab.example.com" -DomainNetbiosName "LAB" -InstallDns -DomainMode Win2025 -ForestMode Win2025 -Force
-CreateDnsDelegation here. That parameter creates a delegation record in a parent DNS zone so external resolvers can find this domain — it is meant for a real child domain of a domain you control on the public internet. Part 1 deliberately chose lab.example.com as a namespace that is never delegated publicly, so there is no parent zone to delegate from.
The command reboots the server automatically once installation finishes — leave -NoRebootOnCompletion out, since a domain controller that has finished configuring but hasn’t rebooted does not function correctly as either a member server or a DC.
Verifying dc01
After the reboot, confirm the forest actually exists and dc01 considers itself healthy before touching dc02. These are read-only checks — nothing here changes the domain.
# Confirm dc01 is visible as a domain controller, including global catalog status
Get-ADDomainController -Filter * | Select-Object HostName, Site, IPv4Address, IsGlobalCatalog
# Run the full domain controller health check
dcdiag /v
Get-ADDomainController should list dc01.lab.example.com with IsGlobalCatalog set to True — every domain controller is a global catalog by default unless installed with -NoGlobalCatalog. dcdiag /v should report every test, including Advertising, NetLogons, and Services, as passed.
One detail worth knowing rather than debugging later: with AD-integrated DNS, dc01’s own preferred DNS server address is automatically set to the loopback address 127.0.0.1 once the forest is created, so it always resolves its own zone even if its other IP settings change later.
Adding dc02 as a Second Domain Controller
dc02 starts as a plain workgroup VM — it does not need to be domain-joined first. Install-ADDSDomainController joins the computer to the domain and promotes it in the same operation, provided it can resolve the domain in DNS and has credentials with rights to join it.
# On dc02: assign the static IP, but point DNS at dc01 —
# dc02 has to find the domain in DNS before it can join it
New-NetIPAddress -InterfaceAlias "Ethernet" -IPAddress 10.10.10.11 -PrefixLength 24 -DefaultGateway 10.10.10.1
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 10.10.10.10
# Rename to match the naming convention, then restart
Rename-Computer -NewName "dc02" -Restart
# Install the AD DS role — same step as dc01, and required even though dc02 isn't domain-joined yet
Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools
# Join and promote dc02 as an additional domain controller, replicating from dc01
Install-ADDSDomainController -DomainName "lab.example.com" -Credential (Get-Credential -UserName "LAB\Administrator" -Message "Domain Admin credentials for dc02 promotion") -InstallDns -SiteName "Default-First-Site-Name" -Force
Set-DnsClientServerAddress to point at dc01 first is the single most common reason this step stalls — dc02 has to resolve lab.example.com‘s SRV records to find a domain controller to join through, and pfSense’s DNS forwarder in Part 3 has no idea the domain exists.
-SiteName "Default-First-Site-Name" is explicit here for clarity, but it is also the default: this lab is a single Proxmox host in one location, so both domain controllers land in the same site automatically without needing to touch AD Sites and Services yet. Like dc01, dc02 is a global catalog server by default — nothing here needs -NoGlobalCatalog.
Once dc02 has rebooted and is running its own DNS Server service, update both machines’ DNS client settings so each treats itself as primary and the other as a fallback — this is ordinary operational practice, not something either cmdlet configures automatically:
# On dc01: loopback stays primary, dc02 becomes the fallback
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 127.0.0.1,10.10.10.11
# On dc02: its own loopback becomes primary, dc01 becomes the fallback
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 127.0.0.1,10.10.10.10
Verifying Replication Between dc01 and dc02
A second domain controller is only useful if it is actually replicating. Run these from either DC once dc02 has rebooted from promotion.
# Show inbound replication status for every partition, from every domain controller
repadmin /showrepl * /csv
# Summarize replication success/failure across the whole forest, sorted by staleness
repadmin /replsummary * /bysrc /bydest /sort:delta
# Run the replication-specific dcdiag test against both DCs
dcdiag /test:Replications /v
# Confirm both domain controllers are now visible, each as its own global catalog
Get-ADDomainController -Filter * | Select-Object HostName, Site, IPv4Address, IsGlobalCatalog
repadmin /replsummary should show 0 failures for both dc01 and dc02 against each other, and dcdiag /test:Replications should report the test as passed on both. Get-ADDomainController -Filter * should now return two rows.
Replication right after a promotion can briefly show a naming context as not yet available while dc02 finishes its initial sync — that is expected and normally clears within a few minutes. A result that stays failed after that is the actual problem, not the initial sync itself.
Ports pfSense Doesn’t Need to Open — Yet
dc01 and dc02 sit on the same SERVERS VLAN, so replication traffic between them never crosses pfSense at all — it stays on the same broadcast domain, and the SERVERS ruleset from Part 3 never evaluates it. That changes the moment a domain controller ends up on a different segment or site, which is worth planning for even though it does not apply yet in this lab.
| Protocol | Port | Used For |
|---|---|---|
| DNS | 53 TCP/UDP | Locating domain controllers and resolving AD-integrated DNS records. |
| Kerberos | 88 TCP/UDP | Authentication ticket requests. |
| Kerberos password change | 464 TCP/UDP | Password changes going through the KDC. |
| LDAP | 389 TCP/UDP | Directory queries and updates. |
| LDAP SSL | 636 TCP | Encrypted LDAP. |
| Global Catalog / GC SSL | 3268 / 3269 TCP | Forest-wide searches against the global catalog. |
| SMB | 445 TCP | SYSVOL and NETLOGON share access, and RPC over SMB. |
| RPC endpoint mapper | 135 TCP | Negotiates the dynamic high port used for the actual replication traffic. |
Domain Controller Promotion Issues
| Symptom | Likely Cause | Fix |
|---|---|---|
Install-ADDSForest fails prerequisite checks |
The server’s own IP settings don’t yet have a working DNS server configured, or the hostname doesn’t meet the 15-character NetBIOS limit from Part 1. | Confirm Set-DnsClientServerAddress was run first, and recheck the hostname against Part 1’s naming convention before retrying. |
DSRM password rejected during Install-ADDSForest |
The password doesn’t meet the domain’s password complexity requirements, or was left blank. | Supply a password that meets standard complexity rules — upper, lower, number, and symbol — the same as any domain account password. |
dc02’s Install-ADDSDomainController hangs or fails resolving the domain |
dc02’s DNS client is still pointed at pfSense instead of dc01, so it can’t find lab.example.com‘s SRV records. |
Re-run Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 10.10.10.10 on dc02 and retry. |
| Promoting a newer Windows Server DC into an existing older domain fails outright | The target domain’s functional level is below what that DC’s OS version requires — Windows Server 2025 domain controllers need at least the Windows Server 2016 domain functional level. | Check the current level with Get-ADDomain | Select-Object DomainMode and raise it with Set-ADDomainMode before retrying. Not relevant to this lab’s fresh Win2025 domain, but a real trap when reusing this guide against an older forest. |
repadmin /replsummary shows failures between dc01 and dc02 that don’t clear |
DNS on one DC still points only at itself or at pfSense, so it can’t resolve the other DC’s hostname to replicate against it. | Confirm both DCs’ DNS client settings list each other, then re-run repadmin /replsummary after a few minutes. |
| A domain controller stops advertising itself after a reboot | The Netlogon service failed to start, or its SRV records were never registered in DNS. | Covered in depth in Domain Controller Not Advertising — Missing SRV Records. |
Final Thoughts
Everything before this part was preparation: a plan, a hypervisor, a firewall. This part is where the lab becomes an actual Active Directory environment — a forest that exists, a domain controller that answers Kerberos and LDAP requests, and a second one that means the first can be rebooted without taking the domain down with it.
What’s still missing on purpose: DNS is running with pfSense’s default forwarders and no scavenging configured, and there’s no certificate authority, no Group Policy beyond the defaults, and no file shares. Each of those is a deliberate gap this series fills one part at a time, starting with DNS next.
Get-ADDomainController -Filter * lists both dc01 and dc02, repadmin /replsummary shows 0 failures between them, and dcdiag /v passes on both.
Part 5 turns pfSense’s forwarded DNS into proper AD-integrated DNS on dc01 and dc02: forwarders, root hints, and the scavenging and aging settings that keep stale records from piling up as VMs in this lab get rebuilt.