Windows Server for beginners. Part 8: DHCPv4
All parts of the series “Windows Server for beginners”The DHCP protocol for IPv4 — the four messages Discover, Offer, Request, Ack, the option format, lease renewal and relaying — is covered in part 11 of the networking series. Here we're talking about the server side in a domain: how the DHCP Server role works in Windows, how a server in a domain differs from a server in a router, how two servers share one scope without conflicts, and how they register client names in DNS without breaking the secure updates from the previous part.
In a domain DHCP is traditionally installed on the controllers: these are two services that need the same availability and together take a few dozen megabytes of memory. We'll do the same in the lab: now on dc1, and in the second half of the part on dc2, once it appears in part 10. If the organization has dedicated infrastructure servers, DHCP goes there, and everything below stays the same except for one point about the DnsUpdateProxy group.
Installation and authorization
PS C:\> Install-WindowsFeature -Name DHCP -IncludeManagementTools
Success Restart Needed Exit Code Feature Result
------- -------------- --------- --------------
True No Success {DHCP Server, DHCP Server Tools}
The role is installed and the DHCPServer service is running, but it won't hand out addresses until two steps are done, which Server Manager hides behind the "Complete DHCP configuration" link.
Security groups. Two local groups: DHCP Administrators (full control of the server without server administrator rights) and DHCP Users (read-only). A controller has no local groups, so they're created as domain groups:
PS C:\> Add-DhcpServerSecurityGroup -ComputerName dc1.corp.example.com
PS C:\> Restart-Service DHCPServer
Authorization in the directory. Before answering clients, a Windows DHCP server running on a domain member checks whether it's on the forest's list of authorized servers. The list is objects of class dHCPClass in CN=NetServices,CN=Services,CN=Configuration,DC=corp,DC=example,DC=com. An unauthorized server writes event 1046 to the log and stays silent. Authorization can be done by Enterprise Admins (or whoever has been delegated rights on this container):
PS C:\> Add-DhcpServerInDC -DnsName dc1.corp.example.com -IPAddress 192.168.1.10
PS C:\> Get-DhcpServerInDC
IPAddress DnsName
--------- -------
192.168.1.10 dc1.corp.example.com
This protects against an accidental DHCP server: an admin set up the role on a test machine in the domain, and it started handing out addresses to the whole office. Authorization doesn't protect against a malicious server or a home router brought into the office: they know nothing about the directory and won't check it. Only switch features work against them (DHCP snooping, covered in the networking series).
The final touch is to tell Server Manager that configuration is complete, otherwise it'll show a warning forever:
PS C:\> Set-ItemProperty 'HKLM:\SOFTWARE\Microsoft\ServerManager\Roles\12' -Name ConfigurationState -Value 2
How the server is built
The server stores its configuration and leases in the database %SystemRoot%\System32\dhcp\dhcp.mdb — the same ESE engine as the directory and Exchange — and makes a copy of it in dhcp\backup by itself every hour. The configuration is built from four levels, each overriding the previous:
Server defaults for all scopes: DNS server, domain and NTP options,
│ DNS registration settings, conflict detection, logging
├─ Scope an address range in one subnet: bounds, mask, exclusions, lease,
│ │ its own options (the gateway always goes here)
│ ├─ Policy a subset of the scope's clients by condition (class, MAC, relay agent):
│ │ its own options or its own sub-range
│ └─ Reservation a fixed address for a single client, its own options
└─ Superscope several scopes on one physical segment
Checking the server settings:
PS C:\> Get-DhcpServerSetting
IsDomainJoined : True
IsAuthorized : True
DynamicBootp : True
RestoreStatus : False
ConflictDetectionAttempts : 0
NpsUnreachableAction : Full
NapEnabled : False
ActivatePolicies : True
ConflictDetectionAttempts is how many times the server pings an address before handing it out. Zero means it doesn't ping; Set-DhcpServerSetting -ConflictDetectionAttempts 2 protects against handing out an address someone configured by hand, at the cost of a couple of seconds' delay per lease. On a network where static addresses are excluded from the scope, it isn't needed.
The scope
A scope is a range in one subnet. In the lab, addresses .1–.49 are taken by the router and servers, and we'll give .50–.250 to clients:
PS C:\> Add-DhcpServerv4Scope -Name 'Office' -StartRange 192.168.1.50 -EndRange 192.168.1.250 -SubnetMask 255.255.255.0 -LeaseDuration 8.00:00:00 -State Active
PS C:\> Get-DhcpServerv4Scope
ScopeId SubnetMask Name State StartRange EndRange LeaseDuration
------- ---------- ---- ----- ---------- -------- -------------
192.168.1.0 255.255.255.0 Office Active 192.168.1.50 192.168.1.250 8.00:00:00
ScopeId is the subnet address, and it identifies the scope in all other cmdlets. If there are addresses within the range that mustn't be handed out (network equipment with static addresses), they're excluded: Add-DhcpServerv4ExclusionRange -ScopeId 192.168.1.0 -StartRange 192.168.1.200 -EndRange 192.168.1.210.
The lease is 8 days by default. The client renews it at half its term (after 4 days) and, if the server doesn't respond, keeps using the address until it expires. A long lease means less traffic and resilience to a server failure; a short one (hours) frees addresses faster in networks with a high turnover of devices, such as guest Wi-Fi. For a wired office network, 8 days is a good choice.
Options are what the client gets besides the address. The gateway is set at the scope level (each subnet has its own), and everything else is conveniently set at the server level so it doesn't have to be repeated in every scope:
PS C:\> Set-DhcpServerv4OptionValue -DnsServer 192.168.1.10 -DnsDomain corp.example.com
PS C:\> Set-DhcpServerv4OptionValue -ScopeId 192.168.1.0 -Router 192.168.1.1
PS C:\> Get-DhcpServerv4OptionValue -ScopeId 192.168.1.0 -All | Format-Table OptionId, Name, Value -AutoSize
OptionId Name Value
-------- ---- -----
3 Router {192.168.1.1}
6 DNS Servers {192.168.1.10}
15 DNS Domain Name {corp.example.com}
51 Lease {691200}
The named parameters -DnsServer, -Router and -DnsDomain have a generic form -OptionId N -Value ... for any option from the Get-DhcpServerv4OptionDefinition list:
| Option | Name | Purpose |
|---|---|---|
| 3 | Router | default gateway |
| 6 | DNS Servers | must point to the controllers: a client using the router's DNS won't find the domain |
| 15 | DNS Domain Name | suffix for short names and for DNS registration |
| 42 | NTP Servers | time source for devices that aren't domain members (domain members get time from the controller by themselves) |
| 44, 46 | WINS/NBNS | needed only by very old clients; don't set |
| 51 | Lease | lease duration; set as a scope property, not an option |
| 60, 43 | Vendor class, Vendor specific | data for specific hardware: IP phones, access points |
| 66, 67 | Boot Server, Bootfile Name | network boot (PXE): server address and boot loader name |
| 81 | Client FQDN | the client's name for DNS registration; the client and server exchange it automatically |
| 119 | Domain Search List | the search suffix list; the Windows client doesn't read it, Linux and macOS do |
| 121 | Classless Static Routes | additional routes; the Windows client applies them and ignores option 3 in that case |
| 150 | TFTP Server | TFTP server address for Cisco IP phones |
TFTP (Trivial File Transfer Protocol, RFC 1350), which is behind options 66 and 150, is the simplest file transfer protocol: over UDP, port 69, without authentication and without a file listing on the server. The client requests a file by name, and the server sends it in 512-byte blocks, waiting for an acknowledgment of each block before sending the next; a block shorter than 512 bytes marks the end of the file. The protocol is so small that it fits into a network card's ROM, which is why the PXE boot loader and IP phones use it to download a boot loader, firmware or configuration.
Option 6 is the place where an administrator one day adds a second controller, and it's easy to forget: clients learn about dc2 only when it appears in the option.
A reservation is a permanent address for a device whose DHCP you won't turn off yourself: printers, access points, scanners. It's bound to the MAC address (more precisely, to the client identifier, which for most Ethernet devices equals the MAC; exceptions below):
PS C:\> Add-DhcpServerv4Reservation -ScopeId 192.168.1.0 -IPAddress 192.168.1.31 -ClientId '00-11-22-33-44-55' -Name 'printer-3' -Description 'Printer, 3rd floor'
PS C:\> Get-DhcpServerv4Reservation -ScopeId 192.168.1.0
IPAddress ScopeId ClientId Name Type Description
--------- ------- -------- ---- ---- -----------
192.168.1.31 192.168.1.0 00-11-22-33-44-55 printer-3 Both Printer, 3rd floor
A reservation's address can lie inside the scope's range: the server won't give it to anyone else. This is more convenient than a static setting on the device itself because all addresses are visible in one place, and the device survives a change of DNS servers without anyone having to visit it.
The client identifier is option 61 of the request, and it isn't always the MAC address. Windows, printers and most embedded devices put the hardware type and the MAC in it (the byte 01 and six bytes of the address), but a client is free to send any string, and the Windows server matches reservations against that string, not against the MAC address in the packet header. Ubuntu Server, for example, behaves differently, as do other systems where systemd-networkd obtains the address (on Ubuntu it's configured by netplan): by default they send an RFC 4361 identifier — the byte ff, four bytes of IAID (an interface number systemd computes itself) and a DUID, the machine identifier from DHCPv6, covered in detail in the next part. systemd builds the DUID from /etc/machine-id, and in the lease table such a client looks like this:
IPAddress ClientId HostName
--------- -------- --------
192.168.1.57 ff-b6-22-0e-1a-00-02-00-00-ab-11-6c-3f-19-a0-5e-d4-81-27 srv-linux1.corp.example.com
After ff and the IAID comes the DUID: 00-02 is its type, DUID-EN (by enterprise number), 00-00-ab-11 is number 43793, assigned to systemd, followed by a hash of the machine-id. Three consequences follow:
- a reservation by MAC address won't work for such a machine: the server won't find a match and will give out an address from the pool. You need to reserve by the identifier from the lease table —
-ClientIdinAdd-DhcpServerv4Reservationaccepts a string of any length; - reinstalling the system changes the machine-id and with it the identifier: the machine will get a new address even though the network card is the same;
- virtual machines cloned from a template without resetting
/etc/machine-idsend the same identifier and fight over one address.
If you need MAC binding, it's enabled on the client. In netplan it's one line on the interface:
network:
version: 2
ethernets:
eth0:
dhcp4: true
dhcp-identifier: mac
After netplan apply, the next request will come with the identifier 01 plus the MAC address, and the MAC-based reservation will work.
Policies
Since Windows Server 2012 you can give different clients within a scope different things — by condition:
PS C:\> Add-DhcpServerv4Policy -Name 'IP phones' -ScopeId 192.168.1.0 -Condition OR -VendorClass EQ, 'Cisco Systems, Inc. IP Phone*'
PS C:\> Set-DhcpServerv4OptionValue -ScopeId 192.168.1.0 -PolicyName 'IP phones' -OptionId 150 -Value 192.168.1.40
PS C:\> Add-DhcpServerv4PolicyIPRange -ScopeId 192.168.1.0 -Name 'IP phones' -StartRange 192.168.1.220 -EndRange 192.168.1.250
Conditions are the vendor class (option 60), the user class (option 77), a MAC address with a mask, the client identifier, the relay agent address, the name from option 81. The phones will get addresses from their own sub-range and the configuration server address, and other clients won't notice anything. Classes are declared by the client itself, so policies are no good for security, but they're fine for handing out device-specific settings.
DNS registration
In the previous part the Windows client registered its own A record. The DHCP server can do it on the client's behalf, and in a domain that's needed for three reasons: non-Windows clients can't do secure updates, the Windows client by default asks the server to register its PTR record, and someone has to delete the record of a device that has vanished from the network when its lease expires.
The agreement is made through option 81: the client reports its full name and flags — "register the A record yourself" or "register only the PTR for me". The server settings:
PS C:\> Get-DhcpServerv4DnsSetting
DynamicUpdates : OnClientRequest
DeleteDnsRROnLeaseExpiry : True
UpdateDnsRRForOlderClients : False
DnsSuffix :
DisableDnsPtrRRUpdate : False
NameProtection : False
DynamicUpdates:OnClientRequest— do what the client asks for in option 81 (Windows asks for the PTR);Always— always register A and PTR regardless of the client;Never.UpdateDnsRRForOlderClients— register records for clients without option 81 (printers, old Linux). Turn it on.DeleteDnsRROnLeaseExpiry— delete records when the lease expires. This is a second zone cleanup mechanism besides aging, and a tidier one.NameProtection— name protection: next to the A record the server creates aDHCIDrecord with a hash of the client identifier and doesn't let another client take the same name. It prevents a guest's laptop that calls itselfdc1from hijacking the name.
Now the main subtlety. Records created by the DHCP server in a secure zone belong to the account it performed the update under — by default the computer account DC1$. When a second server and failover appear, records created by dc1 can't be updated by dc2: it's a different owner. The solution takes two steps.
Step 1: a dedicated account for updates, one for all DHCP servers in the domain:
PS C:\> New-ADUser -Name 'svc-dhcp-dns' -SamAccountName svc-dhcp-dns -UserPrincipalName svc-dhcp-dns@corp.example.com -AccountPassword (Read-Host -AsSecureString) -Enabled $true -PasswordNeverExpires $true -Description 'DHCP → DNS dynamic updates'
PS C:\> Set-DhcpServerDnsCredential -ComputerName dc1.corp.example.com -Credential (Get-Credential CORP\svc-dhcp-dns)
PS C:\> Get-DhcpServerDnsCredential -ComputerName dc1.corp.example.com
UserName DomainName
-------- ----------
svc-dhcp-dns corp.example.com
An ordinary account without any rights: being an authenticated user is enough to create a record in a zone with secure updates. Now all records from both servers will belong to svc-dhcp-dns, and either of them can update and delete them. A DHCP server on a controller without such an account logs warning 1056 about exactly this at every startup.
Step 2: the DnsUpdateProxy group. A built-in domain group created for this scenario: records created by its members get empty owner rights — the first one outside the group to update such a record becomes its owner. The idea is that the DHCP server creates a record for a freshly installed machine, and the machine later joins the domain and takes the record over. The correct use is to put the svc-dhcp-dns account into DnsUpdateProxy. The wrong one is to put the computer account of the controller running DHCP there: then the records the controller registers for itself (SRV records, its A record) will also end up without an owner, and anyone will be able to overwrite them. If DHCP runs on a member server, it's acceptable to put its computer account into the group as well.
PS C:\> Add-ADGroupMember -Identity DnsUpdateProxy -Members svc-dhcp-dns
With name protection (NameProtection) enabled, the group isn't needed and even gets in the way: protection is built on the DHCID record, and the owner must remain the DHCP server.
Fault tolerance
A single DHCP server is a point of failure with a delayed effect: when it goes down, the network keeps working until the leases expire, that is, up to four days for half of the clients, while new devices get no addresses right away. Before Windows Server 2012 this was solved by splitting a scope 80/20 between two servers with identical settings; since 2012 there's failover: two servers serve one scope and exchange leases over TCP 647.
Two modes:
- Load Balance — both servers are active, and requests are split by a hash of the client's MAC address in a given proportion (usually 50/50). For two servers on the same network.
- Hot Standby — one is active, the second answers only when the first is unavailable; the standby gets a share of addresses (5% by default) it can use until it switches to the Partner Down state. For a central server and a backup in a branch office.
Once dc2 appears with the role installed and authorized, the relationship is created from one side, and the scope is copied to the partner automatically:
PS C:\> Add-DhcpServerv4Failover -ComputerName dc1.corp.example.com -PartnerServer dc2.corp.example.com -Name 'Office-failover' -ScopeId 192.168.1.0 -LoadBalancePercent 50 -MaxClientLeadTime 1:00:00 -StateSwitchInterval 00:30:00 -AutoStateTransition $true -SharedSecret 'long random string'
PS C:\> Get-DhcpServerv4Failover
Name : Office-failover
PartnerServer : dc2.corp.example.com
Mode : LoadBalance
LoadBalancePercent : 50
ServerRole :
ReservePercent :
MaxClientLeadTime : 01:00:00
StateSwitchInterval : 00:30:00
State : Normal
ScopeId : {192.168.1.0}
AutoStateTransition : True
EnableAuth : True
Parameters whose meaning is worth understanding:
MCLT (Maximum Client Lead Time) is how far a server may extend a client's lease beyond what its partner knows about. While the partner is online, every lease is replicated immediately, and MCLT plays no role. When the connection between the servers is lost (the Communication Interrupted state), a server hands out leases only for the MCLT, one hour, so that when the connection is restored the partner doesn't find addresses taken for longer than it thinks. That's why during an outage clients renew their leases every half hour — that's normal.
State Switch Interval is how long after losing contact a server considers its partner dead and goes into Partner Down, taking the whole pool. Without AutoStateTransition the transition is made by hand: Set-DhcpServerv4Failover -Name Office-failover -PartnerDown. An automatic transition after 30 minutes suits two servers on the same network; for servers in different sites, where a link outage doesn't mean the partner is dead, manual is better.
Shared secret is the key used to sign messages between the servers (EnableAuth). It's set once at creation.
What is replicated: leases, reservations, exclusions, scope options and policies. What isn't: server-level settings (server options, the DNS account, filters). Those are configured on both servers separately. After changing scope settings on one server, they have to be pushed to the partner explicitly:
PS C:\> Invoke-DhcpServerv4FailoverReplication -ComputerName dc1.corp.example.com -Name 'Office-failover' -Force
The snap-in reminds you about this after every change; scripts forget, and the scopes start to drift apart. To check the state: Get-DhcpServerv4Failover on both servers should show State: Normal, and Get-DhcpServerv4ScopeStatistics should show matching numbers.
And remember option 6: once the second server appears, it should contain two DNS addresses — 192.168.1.10, 192.168.1.11. Failover won't do that for you.
Other subnets
A client in subnet 192.168.2.0/24 broadcasts its Discover, and the packet doesn't reach dc1 in 192.168.1.0/24: routers don't forward broadcasts. A relay agent is needed on that subnet's router: it accepts the broadcast request, writes its own address in the client's subnet into the giaddr field and sends the request to the server as unicast. From giaddr the server knows which subnet the request came from and picks the scope — that's why a scope for 192.168.2.0/24 must exist on the server even though the server itself has no address in that subnet. The networking series describes this from the protocol side; on the router it's ip helper-address 192.168.1.10 on Cisco, dhcp-relay on MikroTik, dhcrelay or isc-dhcp-relay on Linux, and the Routing and Remote Access role with the DHCP Relay agent on Windows Server.
The opposite situation is two subnets on one physical segment (the addresses ran out, and a second range was added on top of the same VLAN). For it there's a superscope: Add-DhcpServerv4Superscope -SuperscopeName 'Office-both' -ScopeId 192.168.1.0, 192.168.100.0. The server will treat both scopes as one pool for requests from that segment.
A hands-on look
Who got what:
PS C:\> Get-DhcpServerv4Lease -ScopeId 192.168.1.0 | Format-Table IPAddress, ClientId, HostName, AddressState, LeaseExpiryTime -AutoSize
IPAddress ClientId HostName AddressState LeaseExpiryTime
--------- -------- -------- ------------ ---------------
192.168.1.31 00-11-22-33-44-55 printer-3 ActiveReservation
192.168.1.50 00-15-5d-01-02-10 ws1.corp.example.com Active 23.10.2026 9:01:55
192.168.1.51 a4-83-e7-1b-2c-3d lap-alice.corp.example.com Active 22.10.2026 17:40:12
192.168.1.52 Active 15.10.2026 10:11:00
A lease with no identifier or name, with AddressState: Active for a short term, is a BAD_ADDRESS: the server offered an address, the client answered with Decline (the address is already taken by someone), and the server marked it as bad for the lease period. Several of these in a row are a sign of a second DHCP server on the network or of static addresses inside the scope.
Scope statistics and free addresses:
PS C:\> Get-DhcpServerv4ScopeStatistics -ScopeId 192.168.1.0
ScopeId Free InUse PercentageInUse Reserved Pending SuperscopeName
------- ---- ----- --------------- -------- ------- --------------
192.168.1.0 186 15 7.46 1 0
PS C:\> Get-DhcpServerv4FreeIPAddress -ScopeId 192.168.1.0 -NumAddress 3
192.168.1.53
192.168.1.54
192.168.1.55
The audit log is text files %SystemRoot%\System32\dhcp\DhcpSrvLog-Mon.log … -Sun.log, one per day of the week, overwritten once a week. Lines with event codes:
PS C:\> Get-Content C:\Windows\System32\dhcp\DhcpSrvLog-Thu.log | Select-Object -Last 6
10,10/15/26,09:01:55,Assign,192.168.1.50,ws1.corp.example.com,00155D010210,,...
30,10/15/26,09:01:55,DNS Update Request,192.168.1.50,ws1.corp.example.com,,,...
32,10/15/26,09:01:56,DNS Update Successful,192.168.1.50,ws1.corp.example.com,,,...
11,10/15/26,09:40:12,Renew,192.168.1.51,lap-alice.corp.example.com,A483E71B2C3D,,...
13,10/15/26,10:11:00,Conflict,192.168.1.52,,,,...
51,10/15/26,10:15:03,Authorization succeeded,,corp.example.com,,,...
| Code | Event |
|---|---|
| 10, 11, 12 | address assigned, renewed, released by the client |
| 13 | conflict: the address is already in use (BAD_ADDRESS) |
| 14 | the scope is exhausted, the request is rejected |
| 15 | denied: a policy, a filter, or the scope is inactive |
| 30, 31, 32 | DNS update request sent, failed, succeeded |
| 50–57 | authorization check: 50 — domain unreachable, 51 — authorized, 52 — upgraded, 54 — not authorized, server stopped |
Code 31s in a row after 30s mean records aren't being registered, and the cause is almost always the update account or its absence.
The exchange in packets. pktmon filter add -p 67 -p 68, then ipconfig /release and ipconfig /renew on ws1:
No. Source Destination Protocol Info
1 0.0.0.0 255.255.255.255 DHCP DHCP Discover - Transaction ID 0x3a7f2c1e
2 192.168.1.10 192.168.1.50 DHCP DHCP Offer - Transaction ID 0x3a7f2c1e
3 0.0.0.0 255.255.255.255 DHCP DHCP Request - Transaction ID 0x3a7f2c1e
4 192.168.1.10 192.168.1.50 DHCP DHCP ACK - Transaction ID 0x3a7f2c1e
A Discover from a Windows client contains option 61 (client identifier: the MAC), 12 (host name ws1), 81 (full name and the flags "S=1, register the PTR"), 60 (MSFT 5.0), 55 (the list of requested options: 1, 3, 6, 15, 31, 33, 43, 44, 46, 47, 119, 121, 249, 252). The ACK contains everything the server could provide from what was requested, plus 51 (lease duration), 58 and 59 (the renewal points T1 and T2: half and 7/8 of the lease), and 81 confirming the registration. Option 252 is the address of the WPAD proxy auto-configuration file, and the fact that Windows requests it is one of the reasons the name wpad is on the DNS block list from the previous part.
The authorized servers in the directory, directly:
PS C:\> Get-ADObject -SearchBase 'CN=NetServices,CN=Services,CN=Configuration,DC=corp,DC=example,DC=com' -Filter 'objectClass -eq "dHCPClass"' -Properties dhcpServers | Select-Object Name, dhcpServers
Name dhcpServers
---- -----------
DhcpRoot {i192.168.1.10$rcid1$dc1.corp.example.com$...}
dc1.corp.example.com
A DhcpRoot object listing all servers, and one object per server. This is what Add-DhcpServerInDC created, and what the DHCP service checks at startup and every hour.
A backup of the server's whole configuration in a single file comes in handy when moving the role to another server in part 19:
PS C:\> Export-DhcpServer -ComputerName dc1.corp.example.com -File C:\Temp\dhcp-dc1.xml -Leases -Force
-Leases includes the current leases in the export too, so the new server carries on from the same place.
IPv4 addresses are handed out. IPv6 addressing works differently — the prefix comes from the router, and the machine makes up the interface identifier itself — and DHCP's place in it is different too.
Next — part 9: DHCPv6. Two modes, stateful and stateless, and the M and O flags in Router Advertisement that enable them; the DUID instead of the MAC address; IPv6 scopes and options on a Windows server; reservations by DUID; why failover isn't available for IPv6; and when DHCPv6 isn't needed at all, because SLAAC with RDNSS is enough.
Only registered users can leave a comment.
Sign in or register to leave a comment.