Creating A Web Certificate
Technical Background
Web certificates (SSL/TLS) provide secure encrypted connections (HTTPS) and authenticate the identity of websites. They ensure that traffic between client and server is encrypted and that clients can trust they are connecting to the correct service.
This exercise focuses on using Terraform with the ACME provider to automatically request and generate a wildcard certificate for an entire DNS zone. A wildcard certificate secures both the zone apex (e.g., example.domain.com
) and all its subdomains (e.g., *.example.domain.com
).
Info
Wildcard certificates are issued only via DNS-01 challenges, which verify domain ownership by adding a special TXT record to the DNS zone.
Terraform uses: - The ACME provider to interact with Let's Encrypt. - The rfc2136 DNS provider to automate DNS record creation for the challenge. - The local_file resource to save the generated key and certificate.
Warning
Always use the staging URL https://acme-staging-v02.api.letsencrypt.org/directory
while testing.
Production URLs are rate-limited and can block you for hours if too many requests fail.
Solution
Prerequisits
Create the same files and folder structure as done before in exercise 21 Creating A Fixed Number Of Servers.
Register ACME Provider and Account Key
- Add the provider ACME to the
/KnownHostsByModule/providers.tf
:
provider "acme" {
server_url = "https://acme-staging-v02.api.letsencrypt.org/directory"
}
- Define the required ACME version inside
required_providers
:
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
}
dns = {
source = "hashicorp/dns"
version = "~> 3.0"
}
acme = {
source = "vancluever/acme"
}
}
required_version = ">= 0.13"
}
Note
The ACME provider version must be at least v2.23.2 due to DNS-related issues.
Do not set a fixed version so Terraform always uses the latest one.
- Exchange the TLS resource to the RSA alhorithm inside the
/KnownHostsByModule/main.tf
:
resource "tls_private_key" "private_key" {
count = var.server_count
algorithm = "RSA"
}
Info
This key uniquely identifies your ACME account and is used to sign all certificate requests.
- Register the ACME account with the ACME registration resource inside the
/KnownHostsByModule/main.tf
:
resource "acme_registration" "reg" {
count = var.server_count
account_key_pem = tls_private_key.host[count.index].private_key_pem
email_address = "jr125@hdm-stuttgart.de"
}
Warning
Use a valid email address. Let's Encrypt uses it for expiry and security notifications.
Add Count
- Inside server key wrapper:
resource "local_file" "server_private_key" {
count = var.server_count
content = tls_private_key.host[count.index].private_key_pem
filename = "gen/host_private_key_${count.index}.pem"
file_permission = "600"
}
resource "local_file" "server_public_key" {
count = var.server_count
content = tls_private_key.host[count.index].public_key_openssh
filename = "gen/host_public_key_${count.index}.pub"
file_permission = "644"
}
- Update the loginUser:
resource "hcloud_ssh_key" "loginUser" {
count = var.server_count
name = "${var.loginUser_name}-${count.index}"
public_key = tls_private_key.host[count.index].public_key_openssh
}
- Cloud-init generator:
resource "local_file" "user_data" {
count = var.server_count
content = templatefile("tpl/userData.yml", {
host_ed25519_private = local_file.server_private_key[count.index].content
host_ed25519_public = local_file.server_public_key[count.index].content
devopsSSHPublicKey = hcloud_ssh_key.loginUser[count.index].public_key
volume_name = hcloud_volume.volume[count.index].name
volume_id = hcloud_volume.volume[count.index].id
})
filename = "gen/userData_${count.index}.yml"
}
- Hcloud server setup:
resource "hcloud_server" "web" {
count = var.server_count
name = local.servers[count.index]
image = "debian-12"
server_type = "cx22"
firewall_ids = [hcloud_firewall.sshFw.id]
ssh_keys = [hcloud_ssh_key.loginUser[count.index].id]
user_data = local_file.user_data[count.index].content
}
- Module setup:
module "createSshKnownHosts" {
count = var.server_count
source = "../Module/SshKnownHosts"
loginName = hcloud_ssh_key.loginUser[count.index].name
dnsZone = var.dns_zone
serverName = var.server_name
server_count = var.server_count
server_base_name = var.server_base_name
host_public_keys = [for key in tls_private_key.host : key.public_key_openssh]
servers = local.servers
}
Configure the Certificate
- Request a wildcard certificate with the ACME certificate resource:
resource "acme_certificate" "certificate" {
count = var.server_count
account_key_pem = acme_registration.reg[count.index].account_key_pem
common_name = "*.g11.sdi.hdm-stuttgart.cloud"
dns_challenge {
provider = "rfc2136"
config = {
RFC2136_NAMESERVER = "ns1.sdi.hdm-stuttgart.cloud"
RFC2136_TSIG_ALGORITHM = "hmac-sha512"
RFC2136_TSIG_KEY = "g11.key."
RFC2136_TSIG_SECRET = var.dns_secret
}
}
}
Info
- dnsZone: Base zone (e.g., g03.sdi.hdm-stuttgart.cloud)
- dns_secret: TSIG secret key for RFC2136 authentication
- subject_alternative_names: Adds *.zone to secure all subdomains.
Warning
If the DNS record does not propagate in time, Let's Encrypt validation will fail. Re-run terraform apply after fixing DNS issues.
- Save the certificate and the private key locally:
resource "local_file" "private_key" {
count = var.server_count
content = acme_certificate.certificate[count.index].private_key_pem
filename = "${path.module}/gen/private.pem"
}
resource "local_file" "certificate" {
count = var.server_count
content = acme_certificate.certificate[count.index].certificate_pem
filename = "${path.module}/gen/certificate.pem"
}
Deploy and verify
Initialize and apply Terraform inside /KnownHostsByModule
:
terraform init
terraform apply
Success
After running terraform apply, you will find: - gen/certificate.pem: The wildcard certificate - gen/private.pem: The corresponding private key - DNS TXT records are automatically created for validation
After a successful run, you have a fully automated TLS certificate generation workflow that can be reused and triggered whenever certificates need renewal.
These can now be installed on your web server.
Related Links
Understanding Web Certificates