Secure-by-default Amazon SQS queue pair — an SSE-encrypted primary queue plus an automatic dead-letter queue, dedicated redrive and redrive-allow policies, and an optional access policy, all from a single composite call. Built for the AWS provider v6.x.
- 📬 Provisions an
aws_sqs_queuekeystone (this) plus an automatic dead-letter queue (dlq) — a complete, DLQ-backed queue pair from one module call instead of a bare queue a caller has to wire up by hand. - 🔁 Dedicated redrive resources —
aws_sqs_queue_redrive_policyandaws_sqs_queue_redrive_allow_policy— used instead of the inlineredrive_policy/redrive_allow_policyJSON arguments, per the provider's own documented preference. - 🔒 Encryption at rest always on — SSE-SQS (Amazon-managed keys) by default, or SSE-KMS when a customer-managed key ARN is supplied — applied identically to both the primary queue and the DLQ.
- ☠️ DLQ + redrive ON by default (
enable_dlq = true,max_receive_count = 5) — poison messages land somewhere inspectable instead of looping forever or silently expiring. - 🎯
byQueueredrive-allow scoping by default — never AWS's ownallowAlldefault — only the ARNs you list (or this queue's own ARN, as a safe fallback) may redrive into the DLQ. - 🗓️ DLQ retention defaults to the 14-day maximum (
1209600seconds), longer than the primary queue's 4-day AWS default, per AWS guidance that a DLQ should outlive its source so redriven messages aren't dropped before investigation. - 🧵 Supports both standard and FIFO queues via a single
fifo_queueswitch applied identically to the primary queue and the DLQ, so queue-type parity — an API-enforced requirement — can never drift. - 🏷️ Universal tagging —
var.tagsflows to both queues;tags_allsurfaced as an output.
💡 Why it matters: A queue without a DLQ is a queue that silently drops or infinitely retries poison messages — in a regulated FI, that's a lost audit trail on a financial event. This module makes the safe topology (DLQ + redrive + encryption) the path of least resistance, not an opt-in afterthought.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
terraform-aws-sqs is a Phase 2 App Integration & Messaging module. It consumes nothing to function — a queue with no KMS key and no policy is already a complete, secure-by-default deployment — but it wires optionally to terraform-aws-kms for a CMK, and it is a common fan-out target for terraform-aws-sns subscriptions.
flowchart LR
kms["terraform-aws-kms"]
sns["terraform-aws-sns"]
sqs["terraform-aws-sqs"]
kstream["terraform-aws-kinesis-stream"]
kfirehose["terraform-aws-kinesis-firehose"]
msk["terraform-aws-msk"]
mq["terraform-aws-mq"]
kms -. "kms_key_arn (SSE-KMS)".-> sns
kms -. "kms_key_arn (SSE-KMS)".-> sqs
kms -. "kms_key_arn".-> kstream
kms -. "kms_key_arn".-> kfirehose
kms -. "kms_key_arn".-> msk
kms -. "kms_key_arn".-> mq
sns -- "subscription (protocol=sqs)" --> sqs
style sqs fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
flowchart TB
subgraph SQSMOD["terraform-aws-sqs"]
q["aws_sqs_queue.this<br/>(keystone — primary/source queue)"]
dlq["aws_sqs_queue.dlq<br/>(for_each keyed on enable_dlq, default ON)"]
rp["aws_sqs_queue_redrive_policy.this<br/>queue_url = this.id<br/>deadLetterTargetArn = dlq.arn"]
rap["aws_sqs_queue_redrive_allow_policy.dlq<br/>queue_url = dlq.id<br/>sourceQueueArns (byQueue, default)"]
pol["aws_sqs_queue_policy.this<br/>queue_url = this.id<br/>(rendered only when queue_policy != null)"]
q -- "arn" --> rp
dlq -- "arn" --> rp
q -- "arn (default source)" --> rap
dlq -- "queue_url" --> rap
q -- "queue_url" --> pol
end
style q fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
| Resource | Role | Cardinality |
|---|---|---|
aws_sqs_queue.this |
Keystone — the primary/source queue | 1 |
aws_sqs_queue.dlq |
Dead-letter queue, same queue type as this |
0–1 (enable_dlq, default true) |
aws_sqs_queue_redrive_policy.this |
Binds this to the DLQ with maxReceiveCount |
0–1 (mirrors enable_dlq) |
aws_sqs_queue_redrive_allow_policy.dlq |
Scopes which source queue ARNs may redrive into the DLQ | 0–1 (mirrors enable_dlq) |
aws_sqs_queue_policy.this |
Optional caller-supplied JSON access policy on this |
0–1 (queue_policy != null) |
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 |
No provider {} block is declared inside the module — the caller's configured provider (region/credentials) is inherited.
Least-privilege actions the Terraform identity needs to create, read, update, and delete everything this module manages:
| Action | Required for | Notes |
|---|---|---|
sqs:CreateQueue, sqs:DeleteQueue |
Primary queue + DLQ lifecycle | Two queues per call when enable_dlq = true (default) |
sqs:GetQueueAttributes, sqs:GetQueueUrl |
Read-back on every plan/apply | Drift detection reads queue attributes |
sqs:SetQueueAttributes |
Applying RedrivePolicy, RedriveAllowPolicy, and any other attribute change (retention, visibility timeout, encryption) |
Both aws_sqs_queue_redrive_policy and aws_sqs_queue_redrive_allow_policy call SetQueueAttributes under the hood — there is no separate IAM action per resource |
sqs:TagQueue, sqs:UntagQueue, sqs:ListQueueTags |
Tagging both queues | — |
sqs:AddPermission, sqs:RemovePermission |
aws_sqs_queue_policy |
Only needed when queue_policy is supplied |
kms:DescribeKey |
Validating a caller-supplied CMK | Only when kms_key_arn is set |
kms:CreateGrant, kms:GenerateDataKey, kms:Decrypt |
Producers/consumers reading/writing encrypted messages at runtime | Only when kms_key_arn is set — these apply to the runtime principals (Lambda, ECS tasks, etc.), not just the Terraform identity, and must also be present in the CMK's key policy |
ℹ️ SQS creates no service-linked role, and this module requires no
iam:PassRole. A caller-suppliedqueue_policymay itself grantsqs:SendMessageto a service principal (e.g.sns.amazonaws.com), but granting that in a resource policy does not require the Terraform identity to holdiam:PassRole.
- No service-linked role required for SQS.
- FIFO naming constraint: when
fifo_queue = true,name(anddlq_name, if set explicitly) must end in.fifo— enforced byvariables.tfvalidation at plan time, not just a convention. - DLQ queue-type parity is API-enforced, not a convention. SQS rejects a redrive policy where the source and dead-letter queue are not both standard or both FIFO. This module always derives the DLQ's
fifo_queuefromvar.fifo_queue, so parity cannot drift. - DLQ same-account/same-Region requirement. Per AWS's Developer Guide, the dead-letter queue must be in the same AWS account and Region as its source queue — this module creates both together under one provider, so this is automatically satisfied.
- Redrive policy propagation is eventually consistent —
SetQueueAttributescalls (used by both dedicated redrive resources) can take up to 60 seconds to be reflected on all SQS hosts afterapplycompletes. - Message retention ceiling and the enqueue-timestamp gotcha:
message_retention_secondsmaxes out at 1,209,600 seconds (14 days) on both queues. A message's retention clock is based on its original enqueue timestamp and does not reset when the message moves to the DLQ — if the DLQ's retention is shorter than (or too close to) the time already spent on the source queue, a redriven message can expire almost immediately. Setdlq_message_retention_seconds >= message_retention_seconds(enforced by validation, and the module's defaults already satisfy it: 14 days vs. the AWS default 4 days). - AWS's own inline-argument default for
redrive_allow_policyisallowAll(any queue in the account/Region may target a DLQ) if you set nothing. This module deliberately overrides that default tobyQueue, scoped to the source queue's own ARN unless you list others. - Don't put a DLQ on a FIFO queue if strict ordering matters — a redrive breaks the original message ordering; AWS's own guidance is to avoid DLQs on FIFO queues where exact order is a hard requirement. Weigh this against
enable_dlq = falsefor such queues. - Quotas: 120,000 in-flight (received-but-undeleted) messages per standard queue, 20,000 per FIFO queue (both soft, raisable); queue name length 1–80 characters (
.fifosuffix counts toward the limit);redrive_allow_source_queue_arns(byQueue) supports at most 10 ARNs (hard SQS limit, enforced by validation); FIFO throughput is 300 TPS per API action per message group in standard FIFO mode, higher withfifo_throughput_limit = "perMessageGroupId"(high-throughput mode). - Region: SQS is a regional, provider-inherited service — no
regionvariable in this module and nous-east-1global-resource coupling (unlike CloudFront/ACM/WAFv2).
terraform-aws-sqs/
├── providers.tf # terraform{} + required_providers (aws >= 6.0, < 7.0); no provider block
├── variables.tf # name, fifo_queue, queue behavior, encryption, DLQ/redrive, tags, timeouts
├── main.tf # locals (DLQ/policy for_each maps) + aws_sqs_queue.this/.dlq + redrive resources
├── outputs.tf # id + arn, name, url, dlq_id/arn/name (null when enable_dlq=false), tags_all
├── README.md # this file
└── SCOPE.md # in/out-of-scope, IAM, prerequisites, emits, gotchas, design decisions
module "orders_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "orders"
tags = {
Environment = "prod"
CostCenter = "infra"
}
}This one call creates the orders queue, encrypted with SSE-SQS, plus an orders-dlq dead-letter queue with a maxReceiveCount of 5 and a byQueue redrive-allow policy scoped to orders' own ARN — the full secure-by-default topology, no extra wiring required.
⚠️ Pin the source with?ref=v1.0.0— never a branch. Wirekms_key_arnfromterraform-aws-kmsfor a customer-managed key, andqueue_policyfrom anaws_iam_policy_documentdata source for cross-account/service access — see the Example Library.
Optional, by-reference only — this module does not depend on any sibling module to function:
| Input | Type | Source module |
|---|---|---|
kms_key_arn |
string (optional, default null) |
terraform-aws-kms (CMK arn) — omit to use SSE-SQS Amazon-managed encryption |
redrive_allow_source_queue_arns |
list(string) (optional, default []) |
Other terraform-aws-sqs instances' arn output, or any same-account queue ARN |
queue_policy |
string (optional JSON, default null) |
Typically rendered from an aws_iam_policy_document data source in the caller's root module |
| Output | Description | Consumed by |
|---|---|---|
id |
URL of the primary queue (https://sqs.<region>.amazonaws.com/<account-id>/<name>) — note this is a URL, not the ARN |
App/producer/consumer configuration, Lambda event source mappings |
arn |
ARN of the primary queue (arn:aws:sqs:<region>:<account-id>:<name>) — the cross-resource reference type |
terraform-aws-iam-policy, terraform-aws-sns (subscription/topic policy), Lambda event source mapping, redrive_allow_source_queue_arns on sibling DLQ modules |
name |
Queue name | Tagging, monitoring, dashboards |
url |
Alias of id |
Same consumers as id, for callers who prefer the explicit name |
dlq_id |
URL of the DLQ (null when enable_dlq = false) |
Consumer tooling that reads the DLQ directly for replay/inspection |
dlq_arn |
ARN of the DLQ (null when enable_dlq = false) |
redrive_allow_source_queue_arns on other modules, CloudWatch alarms on DLQ depth |
dlq_name |
Name of the DLQ (null when enable_dlq = false) |
Tagging, monitoring |
tags_all |
All tags incl. provider default_tags, computed on this |
Governance/audit |
1 · Minimal standard queue
module "notifications_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "notifications"
}Creates notifications (SSE-SQS encrypted) plus notifications-dlq with default redrive (max_receive_count = 5) and a byQueue redrive-allow policy scoped to notifications' own ARN.
2 · FIFO queue with content-based deduplication
module "payment_events" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "payment-events.fifo"
fifo_queue = true
content_based_deduplication = true
}The DLQ is automatically named payment-events-dlq.fifo and inherits fifo_queue = true — parity cannot drift.
3 · High-throughput FIFO queue (perMessageGroupId)
module "trade_events" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "trade-events.fifo"
fifo_queue = true
deduplication_scope = "messageGroup"
fifo_throughput_limit = "perMessageGroupId"
}4 · Tags — merge with provider default_tags
# Caller's provider block owns default_tags; resource tags win on key conflict.
provider "aws" {
region = "us-east-1"
default_tags {
tags = { Owner = "platform", ManagedBy = "terraform" }
}
}
module "orders_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "orders"
tags = {
Environment = "prod"
Owner = "commerce-team" # overrides default_tags Owner on both queues
}
}
# module.orders_queue.tags_all => { Owner="commerce-team", ManagedBy="terraform", Environment="prod" }5 · Customer-managed KMS (SSE-KMS)
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
name = "casey-sqs"
}
module "npi_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "loan-applications"
kms_key_arn = module.kms.arn # SSE-KMS on both this and the DLQ; SSE-SQS is dropped
}6 · DLQ + redrive with a custom max_receive_count
module "flaky_consumer_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "webhook-deliveries"
max_receive_count = 10 # allow more retries before declaring a message poison
visibility_timeout_seconds = 120
}7 · Redrive-allow policy scoping multiple source queues
module "shared_dlq" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "shared-poison-queue"
enable_dlq = false # this queue IS a DLQ; it does not need its own DLQ
redrive_allow_policy_type = "byQueue"
redrive_allow_source_queue_arns = [
module.orders_queue.arn,
module.payments_queue.arn,
]
}ℹ️ Point sibling
terraform-aws-sqsinstances'redrive_policy-equivalent (their ownenable_dlq = falseand a manualaws_sqs_queue_redrive_policyin the root, or use each queue's own DLQ) atmodule.shared_dlq.arnif a single shared DLQ across teams is the intended topology — see SCOPE.md Design decisions for the tradeoff.
8 · Secure-by-default opt-out — no DLQ (documented exception)
module "ephemeral_test_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "ci-smoke-test"
enable_dlq = false # opt-out of the secure baseline — document the exception
}Tradeoff: poison messages now loop up to visibility_timeout_seconds intervals until message_retention_seconds expires, then are silently dropped with no DLQ to inspect. Acceptable for short-lived CI queues; not recommended for production.
9 · Queue access policy (cross-account / service principal)
data "aws_iam_policy_document" "sns_to_sqs" {
statement {
sid = "AllowSnsFanOut"
effect = "Allow"
actions = ["sqs:SendMessage"]
principals {
type = "Service"
identifiers = ["sns.amazonaws.com"]
}
resources = [module.orders_queue.arn]
condition {
test = "ArnEquals"
variable = "aws:SourceArn"
values = [module.orders_topic.arn] # terraform-aws-sns
}
}
# !! Version = "2012-10-17" is set automatically by aws_iam_policy_document,
# but if you hand-roll JSON with jsonencode instead, you MUST include it
# explicitly or AWS will hang indefinitely creating/updating the policy.
}
module "orders_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "orders"
queue_policy = data.aws_iam_policy_document.sns_to_sqs.json
}10 · for_each pattern — multiple queues from a map
locals {
queues = {
orders = { max_receive_count = 5 }
payments = { max_receive_count = 3 }
refunds = { max_receive_count = 5 }
}
}
module "queues" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
for_each = local.queues
name = each.key
max_receive_count = each.value.max_receive_count
tags = { Domain = each.key }
}
output "queue_arns" {
value = { for k, m in module.queues : k => m.arn }
}11 · Reducing empty-receive cost with long polling
module "batch_jobs_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "batch-jobs"
receive_wait_time_seconds = 20 # long polling — fewer empty ReceiveMessage calls
visibility_timeout_seconds = 900 # sized to the consumer's expected max processing time
}12 · Temporarily disabling redrive without removing the DLQ (denyAll)
module "orders_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "orders"
redrive_allow_policy_type = "denyAll" # DLQ resource stays; no queue may redrive into it
}13 · Import an existing queue
import {
to = module.orders_queue.aws_sqs_queue.this
identity = {
url = "https://sqs.us-east-1.amazonaws.com/123456789012/orders"
}
}
module "orders_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "orders"
#... match the existing queue's configuration before running plan/apply
}14 · End-to-end composition — SNS fan-out to SQS with a Lambda consumer (finale)
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
name = "casey-messaging"
}
module "orders_topic" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sns?ref=v1.0.0"
name = "orders"
kms_key_arn = module.kms.arn
}
module "orders_queue" {
source = "git::https://github.com/microsoftexpert/terraform-aws-sqs?ref=v1.0.0"
name = "orders"
kms_key_arn = module.kms.arn
queue_policy = data.aws_iam_policy_document.sns_to_sqs.json
tags = { Environment = "prod", App = "order-processing" }
}
data "aws_iam_policy_document" "sns_to_sqs" {
statement {
sid = "AllowSnsFanOut"
effect = "Allow"
actions = ["sqs:SendMessage"]
resources = ["arn:aws:sqs:*:*:orders"] # avoid a cycle on module.orders_queue.arn — see Architecture Notes
principals {
type = "Service"
identifiers = ["sns.amazonaws.com"]
}
condition {
test = "ArnEquals"
variable = "aws:SourceArn"
values = [module.orders_topic.arn]
}
}
}
resource "aws_sns_topic_subscription" "orders_to_queue" {
topic_arn = module.orders_topic.arn
protocol = "sqs"
endpoint = module.orders_queue.arn
}
resource "aws_lambda_event_source_mapping" "orders_consumer" {
event_source_arn = module.orders_queue.arn
function_name = module.orders_processor.arn # terraform-aws-lambda (Phase 7)
batch_size = 10
}💡 The queue policy resource is created before the queue's own ARN can be safely self-referenced in some plan orders; using the well-known ARN format (
arn:aws:sqs:*:*:orders) or a two-pass apply avoids a dependency cycle. See Architecture Notes for the chicken/egg pattern.
ℹ️ High-level grouping — see
variables.tffor full heredoc schemas and validation rules:
- Core:
name - Queue type (FIFO):
fifo_queue,content_based_deduplication,deduplication_scope,fifo_throughput_limit - Queue behavior:
delay_seconds,max_message_size,message_retention_seconds,receive_wait_time_seconds,visibility_timeout_seconds - Encryption:
kms_key_arn,kms_data_key_reuse_period_seconds - Access policy:
queue_policy - DLQ / redrive:
enable_dlq,dlq_name,max_receive_count,dlq_message_retention_seconds,redrive_allow_policy_type,redrive_allow_source_queue_arns - Universal:
tags,timeouts
- Primary:
id(queue URL),arn(queue ARN — the cross-resource reference type) - Primary-queue extras:
name,url(alias ofid) - DLQ (conditional —
try(..., null),nullwhenenable_dlq = false):dlq_id,dlq_arn,dlq_name - Tags:
tags_all
ℹ️ No outputs are marked
sensitive— this module emits no secrets.
id/urlvs.arnformat differ.idandurlare the queue URL:https://sqs.<region>.amazonaws.com/<account-id>/<name>— this is what SDKs use forSendMessage/ReceiveMessageand what Lambda event source mappings historically referenced via ARN instead.arnisarn:aws:sqs:<region>:<account-id>:<name>— the cross-resource reference type for IAM policies, SNS subscriptions, KMS grants, and Lambda event source mappings. Never parse one out of the other; use the matching output.- Force-new fields:
nameandfifo_queueare both immutable — SQS has no in-place rename and no standard↔FIFO conversion. Changing either destroys and recreates the primary queue and (sincedlq_name/fifo_queuemirror it) typically the DLQ as well. In-flight messages are lost on recreate. tags↔tags_all↔default_tags:var.tagsis written to bothaws_sqs_queue.thisandaws_sqs_queue.dlq.tags_all(surfaced fromthisonly) is the provider-computed union of resource tags over the provider'sdefault_tags, with resource tags winning on key conflict.default_tagsis the caller's provider-block concern — never set inside this module.aws_sqs_queue_policy,aws_sqs_queue_redrive_policy, andaws_sqs_queue_redrive_allow_policyare not taggable.- Eventual consistency on redrive propagation. Both dedicated redrive resources call
SetQueueAttributes, which can take up to 60 seconds to propagate across all SQS hosts. A message sent immediately afterapplymay not yet honor the new redrive policy. visibility_timeout_secondsvs. consumer processing time. If a consumer takes longer to process and delete a message thanvisibility_timeout_seconds(default 30s), SQS makes the message visible again and a second consumer may pick it up — the classic duplicate-processing failure mode. Size this to at least the consumer's expected maximum processing time; the module cannot infer it from a not-yet-existing consumer module.- Message retention counts from the original enqueue time, not the redrive time. When SQS moves a message to the DLQ, its retention clock is not reset — a message that spent 3 days on a 4-day-retention source queue arrives at the DLQ with only 1 day left unless the DLQ's own retention is set (and counted) generously. This module's default (
dlq_message_retention_seconds = 1209600, the 14-day max) and its validation (dlq_message_retention_seconds >= message_retention_seconds) exist specifically to avoid silently losing a redriven message. - Dedicated-resource-vs-inline-argument decision.
aws_sqs_queueaccepts inlineredrive_policy/redrive_allow_policyJSON arguments, but the live provider docs state Terraform "will only perform drift detection of its value when present in a configuration" and that the dedicated resources are preferred. This module usesaws_sqs_queue_redrive_policyandaws_sqs_queue_redrive_allow_policyexclusively — this decouples the redrive relationship's lifecycle from the queue resource itself (togglingenable_dlqdoes not touchaws_sqs_queue.this's own diff) and keepsmain.tfinternally consistent with the same dedicated-resource pattern used foraws_sqs_queue_policy. - The
queue_policychicken/egg pattern. A policy that must reference "this queue's own ARN" inside a condition scoped to a specific SNS topic is straightforward (module.orders_queue.arnis knowable after the queue is planned). But if the policy document is built before the module call in the same root (as in the finale example), reference the well-known ARN format directly, or split into two applies, to avoid a dependency cycle between the queue and its own policy. - No
us-east-1constraint. SQS is a regional service — the module inherits the caller's provider region and declares noregionvariable.
Secure-by-default posture and the explicit opt-out for each:
| Hardened default | Behavior | Opt-out / control |
|---|---|---|
| Encryption at rest | SSE-SQS (Amazon-managed, sqs_managed_sse_enabled = true) when kms_key_arn is null; SSE-KMS (kms_master_key_id) when a CMK is supplied |
Not fully disable-able — SQS server-side encryption cannot be turned off entirely by this module; the only choice is Amazon-managed vs. customer-managed key |
| Dead-letter queue + redrive | Enabled (enable_dlq = true) with a configurable max_receive_count (default 5) |
enable_dlq = false — removes the DLQ, redrive policy, and redrive-allow policy entirely (document the exception) |
| Redrive-allow scoping | byQueue — only listed ARNs (defaulting to this queue's own ARN) may redrive into the DLQ |
redrive_allow_policy_type = "allowAll" — widens blast radius for cross-team queue reuse; not recommended |
| DLQ message retention | Defaults to the maximum, 14 days (1209600 seconds), longer than the source queue's 4-day default |
Override dlq_message_retention_seconds (validated to stay >= the source queue's retention) |
| Queue access policy | No policy by default (queue_policy = null) — SQS's implicit default (only the queue-owner account may access it) is already deny-by-default for cross-account access |
Supply queue_policy to open cross-account or service-principal (SNS/EventBridge/S3) access explicitly |
| Public/cross-account exposure | Not possible by accident — SQS has no "public bucket"-equivalent flag; access is entirely policy-driven and this module ships no policy by default | N/A — any queue_policy with a wildcard Principal is a reviewable risk |
Other principles: the DLQ is modeled as a second aws_sqs_queue inside the same composite (not a separate module call) because a DLQ has no independent lifecycle meaning apart from the queue it protects; fifo_queue is a single module-level switch so queue-type parity between the primary queue and DLQ can never drift; queue_policy is kept as an opaque JSON string (not a deeply-typed object) because IAM/resource policy documents are naturally expressed via aws_iam_policy_document or jsonencode in the caller's root module.
terraform init -backend=false
terraform validate
terraform fmt -check
terraform plan # requires valid AWS credentials (profile / SSO / OIDC) + a region
terraform apply
terraform output
⚠️ plan/applyrequire valid AWS credentials and a configured region (provider block /AWS_PROFILE/ SSO / OIDC). Always pin the module source with?ref=v1.0.0, never a branch.
terraform init -backend=false && terraform validate— schema + reference integrity.terraform fmt -check— formatting.terraform planagainst a sandbox account — confirm the DLQ, redrive policy, and redrive-allow policy all appear whenenable_dlq = true(default), and that they disappear cleanly whenenable_dlq = false.- After
apply, confirmarnanddlq_arnare both non-null (whenenable_dlq = true) and thattags_allreflects the expected merge withdefault_tags. - Functional test: send more than
max_receive_countmessages without deleting them from a test queue and confirm they land in the DLQ within the ~60-second propagation window. - Destroy test in a throwaway account — SQS has no ENI/NAT-style destroy-ordering hazard, so teardown should be immediate and clean.
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Outputs:
arn = "arn:aws:sqs:us-east-1:123456789012:orders"
id = "https://sqs.us-east-1.amazonaws.com/123456789012/orders"
url = "https://sqs.us-east-1.amazonaws.com/123456789012/orders"
name = "orders"
dlq_arn = "arn:aws:sqs:us-east-1:123456789012:orders-dlq"
dlq_id = "https://sqs.us-east-1.amazonaws.com/123456789012/orders-dlq"
dlq_name = "orders-dlq"
tags_all = { "Environment" = "prod", "ManagedBy" = "terraform" }
terraform applyhangs with "timeout while waiting" onaws_sqs_queue_policy: the suppliedqueue_policyJSON is missing"Version": "2012-10-17"at the top level. This is a documented AWS behavior, not a Terraform bug — always setVersionexplicitly when hand-rolling JSON withjsonencode(theaws_iam_policy_documentdata source sets it automatically).InvalidParameterValueon queue name:fifo_queue = truerequiresname(anddlq_name, if set) to end in.fifo;fifo_queue = falserequires it must not. The module validates this at plan time, but a mismatch introduced via-varoverrides or.tfvarswill still fail here first.- Tag drift / unexpected tag values: Caused by
default_tagsoverlap.tags_allmerges resource tags over providerdefault_tagswith resource tags winning — if a value differs from what you set invar.tags, adefault_tagsentry is colliding on the same key. - Credential-chain failures (
NoCredentialProviders/ExpiredToken): No valid credentials resolved. SetAWS_PROFILE, refresh SSO, or confirm the OIDC role assumption in CI. The module never takes credentials as variables. AccessDeniedonSetQueueAttributes: Missing thesqs:SetQueueAttributesaction — required not just for basic attribute changes but also for both dedicated redrive resources (they call the same API under the hood).us-east-1/ region errors: Not applicable — SQS is a regional, provider-inherited service with no global-resource coupling. If you see a region-related error, check the provider block's ownregion, not this module.- Messages landing in the DLQ sooner than expected, or not retained as long as expected: Remember retention counts from the message's original enqueue time, not from when it was redriven — a message near the source queue's
message_retention_secondslimit has correspondingly little time left once it reaches the DLQ. Keepdlq_message_retention_secondscomfortably abovemessage_retention_seconds(the module validates>=, but "equal" leaves zero margin). - Duplicate message processing:
visibility_timeout_secondsis shorter than the consumer's actual processing time, so SQS re-exposes the message to a second consumer before the first one deletes it. Raisevisibility_timeout_secondsto comfortably exceed worst-case processing time. - Redrive doesn't seem to be working immediately after
apply:SetQueueAttributes-based propagation can take up to ~60 seconds. Retry the test after a short wait before assuming misconfiguration.
- Terraform Registry —
hashicorp/awsprovider:aws_sqs_queue,aws_sqs_queue_policy,aws_sqs_queue_redrive_policy,aws_sqs_queue_redrive_allow_policy - AWS — Using dead-letter queues in Amazon SQS (Amazon SQS Developer Guide)
- AWS — Amazon SQS FIFO queues and high-throughput FIFO queues (Developer Guide)
- AWS — Amazon SQS server-side encryption (SSE-SQS / SSE-KMS) (Developer Guide)
- AWS — Amazon SQS quotas (per-Region and per-queue limits)
- —
terraform-aws-kms,terraform-aws-sns,terraform-aws-iam-policy(sibling modules)
🧡 "Infrastructure as Code should be standardized, consistent, and secure."