Set use_lockfile = true on the S3 backend to lock state with a file in the same bucket. HashiCorp's documentation marks DynamoDB-based locking as deprecated, with removal planned for a future minor version. Locking is opt-in and off by default, so check that yours is actually on.
Why locking matters for security
A lock stops two applies from writing state at once. That is mostly about integrity, but it is also a security control. A corrupted or half-written state can cause Terraform to destroy and recreate resources, or lose track of a protected one. Locking, versioning and tight permissions together make the state a reliable record.
Two mechanisms
Lock file. With use_lockfile set, Terraform writes a .tflock file next to the state object, in the same bucket.
DynamoDB table. The older approach. The dynamodb_table and dynamodb_endpoint arguments are marked deprecated. The docs say you can set the S3 and DynamoDB arguments together to ease migration.
The documentation page I checked does not state which Terraform version added use_lockfile, so confirm that against your installed version before rolling it out.
Permissions to grant
From the backend documentation:
- The bucket needs
s3:ListBucket. - The state key needs
s3:GetObjectands3:PutObject. - With
use_lockfile, the.tflockobject also needss3:DeleteObject, along with get and put. - Terraform does not need
s3:DeleteObjecton the state file itself, so you can leave it out. - Workspaces need extra list, get, put and delete access to the workspace paths.
Denying delete on the state object is a cheap guard against accidental or malicious removal. Pair it with versioning.
Encryption and versioning
The documentation strongly recommends bucket versioning, so you can recover from deletion or human error. For encryption, encrypt turns on server side encryption of state and lock files, and kms_key_id points to a KMS key. With a KMS key, Terraform needs kms:Encrypt, kms:Decrypt and kms:GenerateDataKey.
If you use a customer-provided key, pass it through the AWS_SSE_CUSTOMER_KEY environment variable. The docs warn that setting it inside a Terraform file persists it to disk in terraform.tfstate.
Migration steps
- Add
use_lockfile = truewhile leaving the DynamoDB arguments in place. - Grant the extra permissions on the
.tflockkey. - Run a plan from two terminals and confirm the second one waits or fails on the lock.
- Remove the DynamoDB arguments once every pipeline and engineer has the new configuration.
- Delete the table and its permissions after a quiet period.
Common gaps
- Locking never turned on, because it is opt-in.
- A pipeline role that can delete state objects.
- Versioning off, so a bad write cannot be rolled back.
- Wide read access, which exposes every secret in state.
Quick check
Is locking on by default in the S3 backend?
No. HashiCorp describes it as opt-in, and use_lockfile defaults to false.
Can Terraform run without delete permission on the state file?
Yes. The documentation says Terraform does not delete the state object, so s3:DeleteObject is not required on it. The lock file object does need delete when use_lockfile is on.
Sources
Last reviewed: 19 October 2026.