linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/kb/Providers/tf-tls-provider

kb/providers ── Providers ── intermediate

tls provider: keys, self-signed certificates, CSRs

The `tls` provider generates private keys and certificates right inside HCL. `tls_private_key` makes a key pair (RSA/ECDSA/Ed25519). `tls_self_signed_cert` produces a self-signed certificate. `tls_cert_request` builds a CSR you can hand to an external CA for signing. It is handy for test fixtures and SSH keys. For production, reach for a secrets manager, not state.

view as markdownaka: terraform-tls, terraform-tls-provider

When you need the tls provider

Real cases:

  • SSH keys for EC2. You generate a pair, put the public key into aws_key_pair, and use the private key to connect or hand it to the user.
  • Test fixtures for PKI. Lessons and tests that need a real TLS connection without a real CA.
  • Self-signed certs for internal services. A service mesh with an internal CA, or ingress in a dev cluster.
  • A CSR for an external CA to sign. You generate the request, send it to Let's Encrypt, Hashicorp Vault, or a private CA, and get back a signed cert.

The provider is not meant for production secrets management. Keys land in state in plain text. That is fine for a lab and for SSH keys in one-off projects. It is not fine for signing payments.

Installation

hcl
terraform {
  required_providers {
    tls = {
      source  = "hashicorp/tls"
      version = "~> 4.0"
    }
  }
}

The provider has no configuration. It never calls an API. Every operation runs locally.

tls_private_key

hcl
resource "tls_private_key" "ssh" {
  algorithm = "ED25519"
}
output "private_pem" {
  value     = tls_private_key.ssh.private_key_pem
  sensitive = true
}
output "public_openssh" {
  value = tls_private_key.ssh.public_key_openssh
}

Algorithms:

AlgorithmSizeUse
RSA2048 / 3072 / 4096Compatible with everything. Default is 2048, which is old for production.
ECDSAP256 / P384 / P521Smaller and faster, but not supported everywhere.
ED25519256The best choice for SSH keys and modern systems.

Resource attributes:

  • private_key_pem is the PEM private key. sensitive.
  • private_key_openssh is the OpenSSH-format private key (for ssh -i).
  • public_key_pem, public_key_openssh, public_key_fingerprint_md5, public_key_fingerprint_sha256.

Tying it to aws_key_pair

hcl
resource "tls_private_key" "demo" {
  algorithm = "ED25519"
}
resource "aws_key_pair" "demo" {
  key_name   = "demo-key"
  public_key = tls_private_key.demo.public_key_openssh
}
resource "local_sensitive_file" "key" {
  filename        = "${path.module}/demo.pem"
  content         = tls_private_key.demo.private_key_pem
  file_permission = "0600"
}

After apply, the pair is ready and the file sits on disk with 0600 permissions for whoever ran it. In CI it works the same way, but the state file still holds the key, so protecting that state is critical.

For production, keys usually live in Secrets Manager or AWS Systems Manager Parameter Store, not in state.

tls_self_signed_cert

hcl
resource "tls_private_key" "ca" {
  algorithm = "RSA"
  rsa_bits  = 4096
}
resource "tls_self_signed_cert" "ca" {
  private_key_pem = tls_private_key.ca.private_key_pem
  subject {
    common_name  = "linuxlab.local"
    organization = "LinuxLab Test CA"
  }
  validity_period_hours = 8760  # 1 year
  is_ca_certificate = true
  allowed_uses = [
    "cert_signing",
    "crl_signing",
  ]
}
output "ca_cert" {
  value = tls_self_signed_cert.ca.cert_pem
}

This is an internal CA. From here you can sign CSRs with it.

tls_cert_request → tls_locally_signed_cert

A full PKI flow inside Terraform:

hcl
# key for the service
resource "tls_private_key" "service" {
  algorithm = "ECDSA"
  ecdsa_curve = "P256"
}
# CSR
resource "tls_cert_request" "service" {
  private_key_pem = tls_private_key.service.private_key_pem
  subject {
    common_name  = "api.linuxlab.local"
    organization = "LinuxLab"
  }
  dns_names = [
    "api.linuxlab.local",
    "api-internal.linuxlab.local",
  ]
}
# signed by your own CA
resource "tls_locally_signed_cert" "service" {
  cert_request_pem   = tls_cert_request.service.cert_request_pem
  ca_private_key_pem = tls_private_key.ca.private_key_pem
  ca_cert_pem        = tls_self_signed_cert.ca.cert_pem
  validity_period_hours = 720  # 30 days
  allowed_uses = [
    "key_encipherment",
    "digital_signature",
    "server_auth",
  ]
}

After apply, you have a CA, a service key, and a signed certificate. You can load it into an ALB, a k8s ingress, or any system that accepts PEM.

tls data sources

Reading existing certificates:

hcl
# Fetch the cert chain of a remote server
data "tls_certificate" "example" {
  url = "https://example.com"
}
output "issuer" {
  value = data.tls_certificate.example.certificates[0].issuer
}

Useful for discovery: "which CA signs our ingress."

Pitfalls

  • Private keys in state. This is the fourth time it comes up, but it is the main point: state is a file full of secrets. Without an encrypted backend (S3 + KMS) it is a security incident.

  • The RSA 2048 default is outdated. In 2026 it is the minimum for compatibility, but not for security. Use RSA 4096 or ED25519.

  • Certificates do not rotate on their own. tls_self_signed_cert holds a valid period, but Terraform will not reissue it once the period expires. You need time_rotating plus lifecycle.replace_triggered_by:

    hcl
    resource "time_rotating" "year" {
      rotation_days = 350
    }
    resource "tls_self_signed_cert" "ca" {
      # ...
      lifecycle {
        replace_triggered_by = [time_rotating.year]
      }
    }
  • subject does not accept every DN type. If you need unusual fields (emailAddress, userID), they are not supported. Only common_name, organization, country, locality, province, street_address, postal_code, organizational_unit, serial_number.

  • dns_names works for wildcards. ["*.linuxlab.local"] gets signed. Browsers accept it (if the root CA is trusted).

  • You cannot drop in a CSR signed by another CA here. This is for local signing. To get a cert from Let's Encrypt, use the acme provider or an external script.

  • Use a real cert for production, not one from the tls provider. Use AWS Certificate Manager, Let's Encrypt, or an internal Vault PKI. The tls provider is for labs, tests, and SSH keys.

See also in LinuxLab

  • tls-certificates covers what an X.509 certificate is, who signs whom, and how to read openssl x509. Without that grounding the tls provider looks like magic.
  • tls-handshake covers exactly what a self-signed cert does during the TLS handshake, why the browser turns red, and what CN/SAN/SNI mean.
  • ssh-hardening: tls_private_key is often used as an SSH key (openssh_pem). The ed25519 vs rsa parameters are covered there in detail.

§ команды

bash
terraform apply -target=tls_private_key.demo

Create the key on its own, without touching anything else. Handy during first setup.

bash
terraform state show tls_private_key.demo

Shows every attribute, including private_key_pem. It is sensitive, yet stored in state in plain text.

bash
openssl x509 -in cert.pem -text -noout

Inspect the generated cert: subject, validity, SANs. The standard tool outside terraform.

bash
ssh-keygen -y -f demo.pem

Extract the public key from a PEM. Handy when you saved only the private key.

§ см. также

  • tf-utility-providersUtility providers: random, time, null, terraform_dataProviders that do not manage a cloud, they help HCL itself. `random` generates IDs and passwords. `time` handles delays and timestamp marks. `null` is the deprecated "non-resource" for triggers. `terraform_data` is the modern replacement for `null_resource`, built into Terraform. Each one removes a specific limitation of the declarative approach.
  • tf-provider-blockThe provider block: who Terraform will callThe provider block configures the plugin: which AWS region to talk to, which endpoints to use, which credentials to take. One block per provider is usually enough.
  • tf-stateState: Terraform's memory of what it createdState is the JSON file `terraform.tfstate` where Terraform records what it created in the cloud. Without it, Terraform would have no way to tell which bucket is "its own" and which belongs to something else. The file holds resource IDs, all attributes, and often secrets. It is the most sensitive part of any project.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies