[Virtual Event] The AI Readiness gap: Where infrastructure teams are getting AI wrong

Register ➡️

Terraform

How to Encrypt Terraform State Files

terraform state encryption

Your Terraform state files contain the attributes and metadata of the resources and data sources that make up your Terraform configurations. This includes sensitive data such as passwords, database connection strings, certificate private keys, and more. By default, Terraform stores this information in plain text in your state file, so anyone with read access to that file can see it.

You can use ephemeral values and ephemeral resources to keep some of this sensitive data out of your state file in the first place. But even data that isn’t classified as sensitive is often something you’d rather not share in plain text. After all, the state file reveals a lot about your infrastructure.

In this post, we’ll explore how to encrypt your Terraform state file, both at rest and in transit. We’ll also look at an encryption feature that OpenTofu offers, and Terraform doesn’t.

What we’ll cover:

  1. What is Terraform state file encryption?
  2. How to encrypt Terraform state
  3. Terraform vs OpenTofu state encryption
  4. Core best practices for Terraform state file encryption

What is Terraform state file encryption?

In simple terms, encryption turns a human-readable piece of information into something unintelligible using a secret key. With the same key (or a closely related one), you can reverse the transformation and turn the unintelligible data back into something readable.

In the context of Terraform state files, encryption means protecting the data they contain by rendering it unreadable to unauthorized users. If someone walked into a data center where you store your state files, pulled a hard drive out of a rack, and walked out with it, they still shouldn’t be able to read your state file.

The Terraform state file contains metadata, resource and data source attributes, check block results, and output values. Resource and data source attributes can include sensitive information such as API keys, certificate private keys, and passphrases. You can declare input variables and outputs as sensitive, which tells Terraform not to print them as plain text in logs. But this doesn’t change how Terraform stores those values in the state file.

This is why state file encryption matters: you need to protect the sensitive data inside the state file.

Two types of encryption matter here:

  • Encryption in transit is when you encrypt the state file as it is transmitted over a network (e.g. during a Terraform operation in your CI/CD pipeline). This keeps the state file from moving in a readable format that someone could intercept.
  • Encryption at rest is when you encrypt the state file data where it’s stored, usually the Terraform state backend (e.g., Amazon S3 or Google Cloud Storage).

How to encrypt Terraform state

In the following sections, we’ll cover how to encrypt Terraform state. If you’re worried about a complicated, math-heavy procedure, you will be glad to know that most of the work is handled for you.

Two important concepts used in the following sections are:

  • A managed Terraform state backend is a backend based on a managed cloud service. Three popular examples are Amazon S3, Azure Storage, and Google Cloud Storage (GCS). This category also includes automation platforms that manage state storage for you, such as Spacelift and HCP Terraform.
  • A self-managed Terraform state backend is a backend that you’re responsible for setting up and configuring. Examples include HashiCorp Consul, HTTP, Kubernetes, and PostgreSQL. Note that there could be managed offerings of these backends as well, e.g., Amazon RDS for PostgreSQL or Azure Kubernetes Service.

Encryption in transit

Encryption in transit protects against someone reading your data as it moves over a network, a threat known as eavesdropping. The standard way to encrypt data in transit is with TLS and HTTPS. Communicating over HTTPS doesn’t stop someone from listening in on the traffic, but the data they intercept will be unintelligible.

If you use a managed Terraform state backend, encryption in transit is managed for you.

There’s no setup to enable it, and no way to turn it off. Every interaction with the backend requires an HTTPS connection, whether from the CLI, Terraform, or a graphical user interface.

If you use a self-managed backend, you’re responsible for configuring encryption in transit. This means configuring TLS certificates and enabling all relevant encryption-in-transit settings for the selected backend.

The exact steps vary between backends. For example, for HashiCorp Consul, you can use a built-in certificate authority (CA) to generate the required certificates and distribute them to all servers and clients in your Consul cluster. Finally, you would add configuration similar to the following for the servers (and similarly for the clients):

verify_incoming = true
verify_outgoing = true
verify_server_hostname = true
ca_file = "consul-agent-ca.pem"
cert_file = "dc1-server-consul-0.pem"
key_file = "dc1-server-consul-0-key.pem"

Read the documentation for your self-managed backend to learn which steps are required to enable encryption in transit.

Encryption in transit should be end-to-end. Even if you have your own private network, you should still use encryption in transit inside it. This is part of the thinking behind a zero-trust network.

Encryption at rest for state backends

All the officially supported Terraform state backends support encryption at rest in some form, but it may not be enabled by default.

For managed Terraform state backend services (Amazon S3, GCS, Azure Storage, etc.), encryption at rest is always enabled by default, and there is usually no option to disable it.

This doesn’t mean you can’t adjust the backend’s encryption configuration, though. One example is Amazon S3, which automatically applies server-side encryption with keys managed by S3. You can configure your S3 bucket to use a custom AWS KMS key for object encryption.

A minimal example of configuring your own KMS key for your S3 state storage bucket looks like this:

resource "aws_s3_bucket" "state" {
  bucket_prefix = "terraform-state-"
}

resource "aws_kms_key" "state" {
  description = "KMS key for Terraform state file encryption"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "state" {
  bucket = aws_s3_bucket.state.bucket

  rule {
    apply_server_side_encryption_by_default {
      kms_master_key_id = aws_kms_key.state.arn
      sse_algorithm     = "aws:kms"
    }
  }
}

You can configure similar encryption settings for other managed Terraform backends.

If you use a self-managed Terraform state backend, you could, in theory, configure it to not use encryption at rest. As you’d expect, that’s a bad idea.

Enabling encryption at rest for a self-managed backend most often means enabling encryption on the underlying storage medium (e.g., an SSD drive). How to do that is outside the scope of this post.

A self-managed backend requires more upfront setup and more ongoing administration on your side, for encryption and other details. This can be a deciding factor when you’re choosing between a self-managed backend and a managed cloud service for your Terraform state.

If you use HCP Terraform, your state files are encrypted at rest on the underlying managed state storage. If you run the premium tier, you can enable the hold-your-own key feature to encrypt state files and plan files with a key stored in a key management system (KMS) on AWS, Azure, GCP, or HashiCorp Vault.

State file encryption at rest with OpenTofu

With OpenTofu, you can explicitly encrypt state files and plan files on top of the encryption at rest you configure at the backend level.

OpenTofu supports a few different key providers for encryption at rest. Along with a key provider, you must select an encryption method. The only one currently supported is AES-GCM.

You can configure encryption settings using HCL (or JSON), or you can set them through an environment variable named TF_ENCRYPTION. The steps to enable state file encryption differ slightly between greenfield and brownfield projects.

The following code block shows an example of how to configure encryption for a greenfield project using the AWS KMS key provider and the AES-GCM encryption method:

terraform {
  encryption {
    key_provider "aws_kms" "default" {
      kms_key_id = "e6088080-95d4-4950-95d3-32a08225ee33"
      region     = "eu-north-1"
      key_spec   = "AES_256"
    }

    method "aes_gcm" "default" {
      keys = key_provider.aws_kms.default
    }

    state {
      method = method.aes_gcm.default
    }
  }
}

The state block specifies that the state file should be encrypted with the specified method. You can also encrypt the plan output by adding a plan block.

After applying a configuration with the above encryption configuration, you end up with a state file similar to the following (the output is truncated for brevity):

{
  "serial": 1,
  "lineage": "2c3e595d-105a-a54b-a56a-51d81df287ee",
  "meta": {
    "key_provider.aws_kms.default": "eyJja…SJ9"
  },
  "encrypted_data": "vYTCK7…M2",
  "encryption_version": "v0"
}

To enable state file encryption for a brownfield project with an existing unencrypted state file you need to add an unencrypted fallback to your configuration, allowing OpenTofu to read the unencrypted file during the migration to an encrypted state file.

An example of how you configure this is shown in the following code block:

terraform {
  encryption {
    # configure your current encryption method (i.e. no encryption method)
    method "unencrypted" "migrate" {}

    key_provider "aws_kms" "default" {
      kms_key_id = "e6088080-95d4-4950-95d3-32a08225ee33"
      region     = "eu-north-1"
      key_spec   = "AES_256"
    }

    method "aes_gcm" "default" {
      keys = key_provider.aws_kms.default
    }

    state {
      method = method.aes_gcm.default

      fallback {
        # reference the current encryption method in a fallback block
        method = method.unencrypted.migrate
      }
    }
  }
}

After applying the configuration with tofu apply you can remove the unencrypted method block and the fallback block from your configuration. You can follow the same steps to switch to another encryption method.

See the OpenTofu documentation for all the details on how to configure encryption for your state files.

Core best practices for Terraform state file encryption

Keep the following best practices in mind when working with Terraform state encryption.

1. Always enable encryption at rest and encryption in transit

This almost goes without saying, but you should always enable both types of encryption for your Terraform state.

You get this for free for many of the official state backend options: Amazon S3, Azure Storage, GCS, and more. You also get this for free if you use the managed state feature of an automation platform such as Spacelift.

If you manage your own Terraform state backend (e.g., HashiCorp Consul or PostgreSQL), you could, in theory, take a few shortcuts during backend setup and disable one or both of these encryption mechanisms. This is a bad idea unless you’re running an ephemeral Terraform experiment as a learning exercise and need a throwaway backend.

A good compromise when using the PostgreSQL or Kubernetes self-managed Terraform state backend is to use a managed service such as Amazon RDS for PostgreSQL or Google Kubernetes Engine.

2. Implement network restrictions for state and encryption keys

Set up network protection mechanisms to restrict access to your state files and to the encryption keys you use to protect them. Never implicitly trust network traffic, even from within your private networks.

3. Limit access to state files and encryption keys

Anyone with the proper authentication and authorization to run terraform apply or tofu apply directly can access the sensitive data in your state file. Even if your users have no malicious intent, mistakes happen, and they can leak sensitive data.

Limit who can run apply commands for your infrastructure by centralizing your Terraform operations in a CI/CD pipeline or automation platform. Avoid granting individual users direct read and write permissions to state files and encryption keys

4. Keep sensitive data out of your encrypted state file

Use ephemeral values and ephemeral resources in your Terraform configurations to avoid storing sensitive information in the state file in the first place. This drastically reduces the impact of a leaked Terraform state file.

Support for ephemeral resources is still limited among providers, so check with yours to see what it supports.

Another option to avoid storing sensitive data in the state file is to configure short-lived dynamic credentials for any external system you interact with. This includes configuring federated credentials with workload identities for provider platforms. Use a service such as HashiCorp Vault to configure dynamic credentials for databases and third-party platforms.

5. Use custom encryption keys if possible

Even though most managed backends support encryption at rest out of the box, it could be a good idea to manage the encryption key yourself.

A custom encryption key gives you several benefits:

  • You can specify the encryption key type and which encryption algorithms are used.
  • You can set the cadence of key rotation and perform emergency rotations when needed.
  • You can configure access management for your keys at a more granular level.

How to manage Terraform state with Spacelift

Terraform is really powerful, but to achieve an end-to-end secure GitOps approach, you need to use a product that can run your Terraform workflows. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:

  • Policies (based on Open Policy Agent) – You can control how many approvals you need for runs, what kind of resources you can create, and what kind of parameters these resources can have, and you can also control the behavior when a pull request is open or merged.
  • Multi-IaC workflows – Combine Terraform with Kubernetes, Ansible, and other infrastructure-as-code (IaC) tools such as OpenTofu, Pulumi, and CloudFormation, create dependencies among them, and share outputs
  • Build self-service infrastructure – You can use Blueprints to build self-service infrastructure; simply complete a form to provision infrastructure based on Terraform and other supported tools.
  • Integrations with any third-party tools – You can integrate with your favorite third-party tools and even build policies for them. For example, see how to integrate security tools in your workflows using Custom Inputs.
  • Secure state management and locking – Because Terraform and OpenTofu state is shared, preventing concurrent writes is essential. With a Spacelift-managed state, Spacelift injects a backend configuration for each run using one-time credentials, restricts state access to active runs and tasks, and stores state encrypted in Amazon S3. You also get state history and a break-glass rollback for rare cases of state corruption, such as after provider upgrades.

Spacelift enables you to create private workers inside your infrastructure, which helps you execute Spacelift-related workflows on your end. Read the documentation for more information on configuring private workers.

Watch the video below to learn how to manage Terraform at scale with Spacelift:

terraform state at scale yt cover

Key takeaways

Your Terraform state file should be encrypted in transit and at rest. If you’re using a managed Terraform state backend (e.g., Amazon S3, Azure Storage, GCS), these encryption mechanisms are enabled by default and are managed for you.

Managed Terraform state backends offer additional encryption options that let you meet your organization’s security requirements, including managing the encryption keys yourself.

Terraform has no built-in support for state file encryption; instead, it relies entirely on the encryption built into your chosen state backend. This is one major feature that sets OpenTofu apart from Terraform: OpenTofu has built-in support for state file encryption.

A few best practices to keep in mind when managing Terraform state file encryption include:

  • Always enable all encryption features of your chosen backend.
  • Implement network restrictions for both state files and encryption keys.
  • Limit who can read your state files and the corresponding encryption keys.
  • Keep sensitive data out of the encrypted state file by using ephemeral values and ephemeral resources, and configure dynamic credentials instead of static ones that persist to state files.
  • Use custom encryption keys for more flexibility and administrative control.

If you are interested in learning more about Spacelift, create a free account today or book a demo with one of our engineers.

Manage Terraform better with Spacelift

Orchestrate Terraform workflows with policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and more.

Learn more

Frequently asked questions

  • Does Terraform encrypt state files by default?

    No. Terraform’s open-source CLI stores state in plaintext, both locally and when written to remote backends. Encryption at rest depends entirely on the backend you configure, such as S3 server-side encryption, Azure Storage default encryption, or a managed backend like Spacelift, which stores state encrypted in Amazon S3 with access restricted to active runs.

  • What's the difference between Terraform and OpenTofu state encryption?

    OpenTofu ships native client-side encryption (GA since v1.7) through an encryption block that supports AWS KMS, GCP KMS, Vault, and PBKDF2 key providers, so state and plan files are encrypted before reaching any backend. Terraform’s open-source CLI has no equivalent and relies on backend-level encryption like S3 SSE-KMS or Azure Storage service encryption.

  • Can I encrypt Terraform state without changing my backend?

    Yes, through server-side encryption offered by your existing backend, for example setting encrypt = true (optionally with kms_key_id) on an S3 backend, or relying on the default at-rest encryption in Azure Blob Storage and GCS. Terraform’s CLI itself has no native client-side encryption, so anything stronger requires switching to OpenTofu or wrapping the file with an external tool like SOPS.

Terraform State at Scale

Get the three-stage maturity model
and a quick-reference checklist
for your platform team.

terraform state at scale bottom overlay
Share your data and download the guide