TerraformPilot

DevOps

Terraform for Windows Server on AWS EC2: Complete Guide

Provision Windows Server 2025 on AWS EC2 with Terraform. Includes AMI selection, password retrieval, RDP, IIS, and joining Active Directory.

LLuca Berton1 min read

Windows Server is one of the strongest Terraform pairings for enterprise infrastructure. EC2 supports Windows Server 2016 through 2025 with first-class AMIs, and Terraform can provision instances, decrypt the Administrator password, join AWS Managed AD, and bootstrap roles via user data PowerShell.

This guide shows the complete pattern.

Quick Pattern (TL;DR)

#
data "aws_ami" "windows_2025" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["Windows_Server-2025-English-Full-Base-*"]
  }
}
 
resource "aws_instance" "win" {
  ami                    = data.aws_ami.windows_2025.id
  instance_type          = "t3.large"
  key_name               = aws_key_pair.win.key_name
  get_password_data      = true
  vpc_security_group_ids = [aws_security_group.rdp.id]
 
  tags = { Name = "win-app-1" }
}
 
output "admin_password" {
  value     = rsadecrypt(aws_instance.win.password_data, file("./win.pem"))
  sensitive = true
}

Security Group for RDP

#
resource "aws_security_group" "rdp" {
  name        = "rdp-from-corp"
  description = "RDP access from corporate CIDR"
  vpc_id      = var.vpc_id
 
  ingress {
    from_port   = 3389
    to_port     = 3389
    protocol    = "tcp"
    cidr_blocks = [var.corp_cidr]
  }
 
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Bootstrap with PowerShell User Data

#

EC2 runs PowerShell wrapped in <powershell> tags on first boot:

resource "aws_instance" "iis" {
  ami           = data.aws_ami.windows_2025.id
  instance_type = "t3.large"
  key_name      = aws_key_pair.win.key_name
 
  user_data = <<-EOF
    <powershell>
    Install-WindowsFeature -Name Web-Server -IncludeManagementTools
    Add-Content -Path C:\inetpub\wwwroot\index.html -Value "Hello from Terraform"
    </powershell>
  EOF
 
  tags = { Name = "iis-1" }
}

Join AWS Managed Microsoft AD

#
resource "aws_directory_service_directory" "ad" {
  name     = "corp.example.com"
  password = var.ad_password
  edition  = "Standard"
  type     = "MicrosoftAD"
 
  vpc_settings {
    vpc_id     = var.vpc_id
    subnet_ids = var.private_subnet_ids
  }
}
 
resource "aws_ssm_document" "domain_join" {
  name          = "domain-join"
  document_type = "Command"
 
  content = jsonencode({
    schemaVersion = "2.2"
    parameters    = {}
    mainSteps = [{
      action = "aws:domainJoin"
      name   = "join"
      inputs = {
        directoryId    = aws_directory_service_directory.ad.id
        directoryName  = aws_directory_service_directory.ad.name
        dnsIpAddresses = aws_directory_service_directory.ad.dns_ip_addresses
      }
    }]
  })
}
 
resource "aws_ssm_association" "join" {
  name = aws_ssm_document.domain_join.name
 
  targets {
    key    = "tag:Domain"
    values = ["corp"]
  }
}

Tag the EC2 with Domain = "corp" to auto-join on boot.

Best Practices

#
  • Use SSM Session Manager instead of opening RDP to the internet.
  • Always tag with OS=Windows, PatchGroup= to drive AWS Systems Manager Patch Manager.
  • Bake AMIs with EC2 Image Builder rather than long user-data scripts.
  • Use Spot for non-prod workstations and lab fleets.
#
#Terraform#Windows Server#AWS#EC2#Active Directory

Share this article