Homelab · Part 4

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.

Quick idea: This part promotes dc01 into the forest’s first domain controller with 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.
Install-ADDSForest

Creates the forest root domain and promotes the first server into its first domain controller.

Install-ADDSDomainController

Joins and promotes an additional server into an existing domain, replicating from a source DC.

Global Catalog

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.

Series scope: This post continues from Part 1 — Planning the Lab Like Real Infra and Part 3 — Networking with pfSense. It uses Part 1’s domain name 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.

One DC

A reboot, a failed patch, or a crashed VM takes down authentication, DNS, and policy for the whole lab at once.

Two DCs

Either can be rebooted, patched, or rebuilt while the other keeps the domain functioning normally.

Cost of One DC

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 / IPdc01 / 10.10.10.10
dc02 hostname / IPdc02 / 10.10.10.11
Gateway / prefix10.10.10.1 / /24, from Part 1’s static reservation .1.20
Forest / domain namelab.example.com, NetBIOS LAB
Domain / forest functional levelWin2025 — 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
Practical rule: Rename and set the static IP before installing the AD DS role. Renaming a server after it is already a domain controller is a disruptive, multi-step operation of its own — it is nearly free to get right now and painful to fix later, the same lesson Part 1 applied to the domain name itself.

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
Key point: No -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
Healthy output: 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
Important: Skipping 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
Healthy output: 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
DNS53 TCP/UDPLocating domain controllers and resolving AD-integrated DNS records.
Kerberos88 TCP/UDPAuthentication ticket requests.
Kerberos password change464 TCP/UDPPassword changes going through the KDC.
LDAP389 TCP/UDPDirectory queries and updates.
LDAP SSL636 TCPEncrypted LDAP.
Global Catalog / GC SSL3268 / 3269 TCPForest-wide searches against the global catalog.
SMB445 TCPSYSVOL and NETLOGON share access, and RPC over SMB.
RPC endpoint mapper135 TCPNegotiates the dynamic high port used for the actual replication traffic.
Production note: The RPC endpoint mapper on 135 only opens the door — the real replication traffic lands on a randomly assigned high port unless the range is fixed. If a future part of this series puts a domain controller behind a pfSense boundary instead of on the same VLAN, that dynamic range needs a rule too, not just port 135.

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.

Key takeaway: Before moving on to Part 5, confirm Get-ADDomainController -Filter * lists both dc01 and dc02, repadmin /replsummary shows 0 failures between them, and dcdiag /v passes on both.
Next in this series

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.