TerraformIII · Installing and Versioning TerraformProduction Terraform
Terraform Server and Execution Environments
What you'll learn
- Describe what the open-source Terraform Server (terraform serve) provides and what it does not
- Distinguish the self-hosted server from Terraform Cloud and the OpenTofu server-side equivalent
- Identify the right auth boundary for a self-hosted state API
- Decide when to deploy a self-hosted server versus Terraform Cloud versus a hand-rolled backend
- Recognise the operational footprint of running a state API in production
Prerequisites
None — start here.
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13
The phrase “Terraform Server” has meant different things at different times. Before 1.9 it meant Terraform Cloud, the hosted SaaS from HashiCorp. From 1.9 onward there is also a self-hosted server you can run on your own infrastructure: an open-source state API that any team can deploy. This lesson is about that second meaning, and about how to choose between the three options.
What terraform serve is
Terraform 1.9 introduced the terraform serve subcommand. It runs
a local HTTP server that speaks the Terraform state protocol, the
same protocol Terraform Cloud uses. A working directory can be
configured with cloud { ... } or with a self-hosted backend that
points at the server, and Terraform will read and write state
through the HTTP API instead of a file or S3.
# CONFIGURATION: start the server on the default address
terraform serve
Terraform Server
Listening on http://[::]:9080
The server speaks HTTP/2 and HTTPS, supports workspace isolation, and authenticates requests using a bearer token. It does not run plans or applies for you. It is a state API, not a CI runner.
What the server does not do
The server is a state API. It deliberately does not include:
- Plan and apply execution. The CLI on the operator’s host (or
in the CI runner) runs
terraform planandterraform apply. The server is not in that loop. - A web UI. The protocol is HTTP and JSON; any UI is a separate product (Terraform Cloud has one; the open-source server does not).
- Sentinel or OPA policy enforcement. Policy is a separate service; the server does not enforce it.
- Cost estimation. Cost is a Cloud-only feature.
- VCS-driven runs. A VCS integration is a Cloud-only feature; the open-source server expects the operator to drive runs from the CLI or from a CI runner that talks to the protocol.
In short, the server replaces a hand-rolled backend (S3 + DynamoDB, or Consul) but it does not replace Terraform Cloud.
When to use each option
Three options, three different operational stories.
| Option | What you operate | What you give up | Best fit |
|---|---|---|---|
| Terraform Cloud (SaaS) | A subscription; nothing else | State and execution leave your network | Teams that want a managed control plane; small teams without platform staff |
Self-hosted terraform serve | A state API behind your firewall | You operate the server: HA, backups, monitoring | Teams that want workspaces without a SaaS; regulated environments |
| Hand-rolled backend (S3 + DynamoDB, Consul) | A bucket, a lock table, and the IAM around them | Workspaces are an organisational convention, not an HTTP object | Teams that have already invested in object storage and lock infrastructure |
The middle option is the new one and the focus of this lesson. If
you have already invested in S3 plus DynamoDB and your team is
comfortable with that, the migration to terraform serve is not
free. The benefit is operational: workspaces become first-class
objects that any HTTP-aware tool can address.
The auth boundary
The default install is unauthenticated and bound to localhost. That is fine for a single-engineer setup. For a team, the boundary is the production concern.
# CONFIGURATION: bind to a private interface, require a token
terraform serve \
-address=https://tf-state.internal.example.com:9080 \
-token-file=/etc/terraform/serve.token
Three rules for production auth:
- Bind to a private interface. Do not expose the port on a public address. The protocol assumes a trusted network or a reverse proxy that enforces auth.
- Use a bearer token. Generate it once, store it in a secret manager, and rotate on the same cadence as other service tokens. Do not commit it to the repository.
- Treat the state API as a privileged service. Anyone with a valid token can read every workspace’s state and can lock any workspace. State can contain secrets, depending on the configuration; the auth boundary is the secrets boundary.
For TLS, the typical pattern is to terminate TLS at a reverse
proxy (HAProxy, Envoy, nginx) and pass the bearer token through.
The -token-file flag reads the token from a file; a 0600 mode on
the file is the minimum.
OpenTofu server-side equivalent
OpenTofu 1.7 ships with tofu serve, an open-source state API
that mirrors the protocol introduced by Terraform 1.9. The
operational story is the same: a state API behind your firewall,
no execution engine, no SaaS dependency. OpenTofu’s server is
licensed under the same MPL-2.0 as the rest of OpenTofu.
If the team has standardised on OpenTofu for licence reasons, the self-hosted server story is already there.
Production failure modes
1. The server is on a public interface with no auth. Symptom: a port scan from the internet finds the state API; the attacker enumerates workspaces. Recovery: bind to a private interface; require a bearer token; audit the access log for unauthorised reads.
2. The bearer token committed to the repository. Symptom: a
secret scanner flags the token in commit history; the token is
revoked; every apply fails with 401. Recovery: rotate the token,
store it in a secret manager, scrub git history with git filter-repo
or the platform’s secret-rewriting tool.
3. The server is a single point of failure. Symptom: the host
running terraform serve crashes; every apply in the team fails.
Recovery: run the server behind a load balancer with a health
check; persist state to the same backend the server fronts; document
the manual fallback (write a local state file temporarily; never
commit it).
4. The server has no backups. Symptom: a disk failure wipes the server’s workspace metadata; workspaces are orphaned even though the underlying cloud resources still exist. Recovery: back up the server’s data directory; test the restore quarterly.
5. The team conflates the server with Terraform Cloud.
Symptom: an operator expects VCS-driven runs and finds that
terraform serve does not provide them; the operator writes a
shell script to drive plans from a webhook. Recovery: align
expectations up front. The server is a state API; execution is
still the CLI’s job.
6. The server has no rate limit. Symptom: a misconfigured CI runner floods the server with concurrent locks; other workspaces are starved. Recovery: a reverse proxy with rate limiting; the server itself does not enforce one.
Security and performance
- State contents are sensitive. A
terraform state pullagainst a workspace can return secrets in clear text depending on the provider configuration. The bearer token is the gate. - Disk I/O dominates. The server is a state store; reads and writes are bounded by the storage backend. The CPU cost of the HTTP protocol is negligible.
- Memory. A single
terraform serveprocess is bounded by the number of workspaces and the size of the state files it caches. Plan for ~256 MiB plus a few MiB per active workspace. - Network. All traffic stays inside the trusted network if the server is bound to a private interface. A reverse proxy in front of the server should terminate TLS and enforce the auth header.
Production guidance
- Run the server behind a load balancer with health checks. The server is stateless if the underlying storage is durable; HA is a function of the load balancer and the storage layer.
- Back up the server’s data directory. State files live there or in the storage backend; either way the data needs a backup story.
- Audit access. Every state read and write should land in a log that the security team can review. Treat the log as a sensitive data source; the workspace names alone reveal the team’s architecture.
- Document the fallback. When the server is down, the team needs a documented procedure for running against a local state file, restoring the server, and reconciling. The procedure is part of the deployment.
What comes next
The next lesson in this part covers the dependency lock file: the
companion mechanism to required_version that constrains the
provider plugins, not the CLI.
Verification
# READ-ONLY: confirm the server is up
curl -fsS https://tf-state.internal.example.com:9080/_health
{"status":"ok"}
# READ-ONLY: confirm the CLI can list workspaces against the server
terraform workspaces list
default
production
staging
# READ-ONLY: confirm auth is enforced
curl -i https://tf-state.internal.example.com:9080/api/v2/workspaces
A 401 with no body is the expected response. A 200 is an unauthorised-read incident.
Knowledge check · 7 questions
Q1. What does terraform serve provide?
Q2. Which of the following does terraform serve NOT include?
Q3. The open-source Terraform server is safe to bind to a public interface without authentication because the protocol uses TLS.
Q4. When is the self-hosted server a better fit than Terraform Cloud?
Q5. Which of the following are correct practices for running terraform serve in production? (Select all that apply.)
Q6. A regulated team cannot send state to Terraform Cloud. They want workspaces and team isolation. What is the right call?
Q7. Which subcommand does OpenTofu provide as the equivalent of terraform serve?
Passing score: 75%. Answers are checked in this browser.