You learn an Active Directory domain by building it and breaking it. A VM on Proxmox lets you do that without risk: you create the domain, organize users and computers, apply a group policy and, if something goes wrong, roll back to a snapshot. This guide covers Windows Server 2022 or 2025 and a lab with a single domain controller and one client. All names and addresses are examples.
Lab requirements
The VM hosts the domain controller (DC) and needs few resources: two vCPUs and a few GiB of RAM are enough for a domain with no load. On Windows guests it is worth installing the VirtIO drivers and the QEMU guest agent, which allows consistent backups from the hypervisor. The lab network should be isolated from the one carrying real services, for example on a dedicated VLAN.
Before promotion you need a final hostname, a static IP address and a DNS pointing at the server itself: the name cannot be changed painlessly after promotion. Time matters too, because Kerberos by default tolerates at most five minutes of skew between the DC and clients.
| Item | Example value |
|---|---|
| Domain | ad.example.com |
| NetBIOS name | LAB |
| DC hostname | dc1 |
| DC address | 192.0.2.10/24 |
| Gateway | 192.0.2.1 |
Get-NetAdapter
New-NetIPAddress -InterfaceAlias "Ethernet" -IPAddress 192.0.2.10 -PrefixLength 24 -DefaultGateway 192.0.2.1
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 192.0.2.10
Rename-Computer -NewName "dc1" -RestartNote
ad.example.com is a domain reserved for documentation. On a real network you use a subdomain (for example ad) of a domain you actually own.
Installing the AD DS role
Install-WindowsFeature AD-Domain-Services -IncludeManagementToolsThe command installs the role and the management tools, including the ActiveDirectory PowerShell module. The server is not a domain controller yet: promotion is still missing.
Promoting to domain controller
Choosing the domain name
Choose the name first: changing it later is a complex operation. Avoid the "local" suffix or other made-up suffixes: "local" is reserved for multicast DNS (RFC 6762) and causes resolution conflicts with macOS, Linux and recent Windows clients, while an unregistered suffix prevents you from getting public certificates and can coincide with someone else's domain. The recommended practice is a subdomain of a domain you own, such as ad.example.com.
Creating the forest
The Directory Services Restore Mode (DSRM) password is requested interactively and never appears in the script.
$dsrm = Read-Host -Prompt "Credenziale DSRM" -AsSecureString
Test-ADDSForestInstallation -DomainName "ad.example.com" -InstallDns -SafeModeAdministratorPassword $dsrm
Install-ADDSForest -DomainName "ad.example.com" -DomainNetbiosName "LAB" -InstallDns -SafeModeAdministratorPassword $dsrm -ForceTest-ADDSForestInstallation runs only the preliminary checks, without making changes. Install-ADDSForest promotes the server and reboots it. The forest and domain functional levels can stay at their defaults, which are the maximum the operating system supports.
Important
The DSRM credential is needed to restore the DC and cannot be recovered any other way: keep it in a secrets manager, not in a text file or a script.
Integrated DNS
Active Directory depends on DNS: clients find the DC through SRV records, for example _ldap._tcp.dc._msdcs.ad.example.com, published by the DC itself. With -InstallDns the ad.example.com zone is created AD-integrated and replicated with the directory. The DC's DNS is the only one domain clients should use. Resolution of external names is delegated to a forwarder:
Add-DnsServerForwarder -IPAddress 198.51.100.53
Resolve-DnsName -Name "_ldap._tcp.dc._msdcs.ad.example.com" -Type SRVIf the second command returns the record, the DC's registration in DNS is correct.
OUs, users and groups
Group policies are linked to organizational units (OUs), not to the default Users and Computers containers, so it is worth building your own structure. Permissions are assigned to groups, not to individual users.
$base = "DC=ad,DC=example,DC=com"
New-ADOrganizationalUnit -Name "Laboratorio" -Path $base
New-ADOrganizationalUnit -Name "Utenti" -Path "OU=Laboratorio,$base"
New-ADOrganizationalUnit -Name "Gruppi" -Path "OU=Laboratorio,$base"
New-ADOrganizationalUnit -Name "Computer" -Path "OU=Laboratorio,$base"
New-ADGroup -Name "GG-Tecnici" -GroupScope Global -GroupCategory Security -Path "OU=Gruppi,OU=Laboratorio,$base"
$iniziale = Read-Host -Prompt "Credenziale iniziale per mario.rossi" -AsSecureString
New-ADUser -Name "Mario Rossi" -SamAccountName "mario.rossi" -UserPrincipalName "[email protected]" -Path "OU=Utenti,OU=Laboratorio,$base" -AccountPassword $iniziale -ChangePasswordAtLogon $true -Enabled $true
Add-ADGroupMember -Identity "GG-Tecnici" -Members "mario.rossi"Read-Host -AsSecureString avoids writing credentials into the script and the shell history. The credential must meet the domain policy, otherwise New-ADUser fails, and -ChangePasswordAtLogon forces a change at first sign-in. The classic scheme assigns users to global groups and global groups to domain local groups that receive the permissions on resources.
An example GPO
The demonstration GPO disables Autoplay on all drives and is linked only to the Computer OU.
New-GPO -Name "Esempio-Disattiva-Autoplay" -Comment "GPO dimostrativa"
Set-GPRegistryValue -Name "Esempio-Disattiva-Autoplay" -Key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -ValueName "NoDriveTypeAutoRun" -Type DWord -Value 255
New-GPLink -Name "Esempio-Disattiva-Autoplay" -Target "OU=Computer,OU=Laboratorio,DC=ad,DC=example,DC=com" -LinkEnabled YesTip
Try new GPOs on a test OU, not at the root of the domain. On the client, gpupdate /force forces them to apply and gpresult /r lists the GPOs actually applied.
Joining a client to the domain
The client must use the DC as its DNS and must be a Windows edition that supports joining (Home editions do not). The account requested by Get-Credential is a user with the rights to join computers to the domain.
Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses 192.0.2.10
Add-Computer -DomainName "ad.example.com" -Credential (Get-Credential) -RestartNew computers end up in the Computers container: to bring them within the scope of the GPO they must be moved to the dedicated OU.
Get-ADComputer -Identity "client1" | Move-ADObject -TargetPath "OU=Computer,OU=Laboratorio,DC=ad,DC=example,DC=com"Verification
dcdiag /q
Get-ADDomain | Select-Object DNSRoot, NetBIOSName, DomainMode, PDCEmulator
Get-ADDomainController -Filter * | Select-Object Name, IPv4Address, IsGlobalCatalogdcdiag /q shows only errors: no output means no failed tests. Without /q each test reports its outcome:
Starting test: Connectivity
......................... DC1 passed test ConnectivitySnapshots and domain controller backup
A snapshot is handy before a promotion or a test, but it is not a backup: it lives on the same storage as the VM and shares its fate.
Warning
In a domain with several DCs, rolling a controller back to an earlier state can cause a USN rollback: the restored DC's directory no longer realigns with its replicas. Proxmox exposes the VM-Generation ID (the vmgenid option), which Windows DCs use to detect a restore, but the rule still stands: restore a DC only with a supported procedure, not with a simple rollback.
For an application-level backup, use a system state backup, on a volume other than the system volume:
Install-WindowsFeature Windows-Server-Backup
wbadmin start systemstatebackup -backupTarget:E: -quietA backup of the whole VM goes alongside the system state backup, it does not replace it: the 3-2-1 backup strategy with Proxmox Backup Server describes how to organize copies and test restores. To watch the DC over time you can use windows_exporter, as described in monitoring with Prometheus and Grafana in an LXC.