The Scenario
You run terraform plan and it dies immediately:
Error: no matching EC2 instances found
With the criteria passed, no matching EC2 instances were found. Please change your search criteria and try again.
The instance is right there in the AWS Console. You can see it. Terraform cannot. It's 2 AM and the deploy is blocked.
What's Actually Happening
Terraform's data "aws_instance" โ and similar data sources like aws_ami, aws_subnet, aws_security_group โ translates your filter blocks into an AWS API call. Zero resources match all conditions simultaneously, and Terraform hard-stops. No fallback. No warning. Just dead.
A block that triggers this:
data "aws_instance" "app_server" {
filter {
name = "tag:Name"
values = ["my-app-server"]
}
filter {
name = "instance-state-name"
values = ["running"]
}
}
Every filter block is an AND condition. One bad filter โ wrong tag casing, stopped instance, wrong region โ and you get zero results.
Step 1: Verify the Resource Exists With the Exact Criteria
Before touching Terraform, replicate the query in the AWS CLI. That tells you immediately whether Terraform is broken or your infrastructure just doesn't match what you think it does:
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=my-app-server" \
"Name=instance-state-name,Values=running" \
--query "Reservations[*].Instances[*].{ID:InstanceId,State:State.Name,Tags:Tags}" \
--output table
Nothing returned? Terraform is correct โ the resource doesn't match your criteria in AWS right now. Check the instance state:
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=my-app-server" \
--query "Reservations[*].Instances[*].{ID:InstanceId,State:State.Name}" \
--output table
If it shows stopped or terminated, there's your answer โ the instance-state-name filter is excluding it. Start the instance or remove that filter.
Step 2: Check Your Region
Region mismatch trips up more people than you'd expect. The instance lives in us-west-2 but the provider or shell environment is pointing at us-east-1. Empty result, no useful error message.
aws ec2 describe-instances \
--region us-west-2 \
--filters "Name=tag:Name,Values=my-app-server" \
--output table
Confirm the provider region in Terraform matches where the resource actually lives:
provider "aws" {
region = "us-west-2" # must match the resource's actual region
}
Referencing a resource in a different region than the default provider? You need an aliased provider:
provider "aws" {
alias = "us_west"
region = "us-west-2"
}
data "aws_instance" "app_server" {
provider = aws.us_west
filter {
name = "tag:Name"
values = ["my-app-server"]
}
}
Common Filter Mistakes
Tag Values Are Case-Sensitive
AWS tag values are case-sensitive. my-app-server and My-App-Server are different tags as far as the API is concerned. Fetch the actual values from your account to be sure:
aws ec2 describe-instances \
--query "Reservations[*].Instances[*].Tags[?Key=='Name'].Value" \
--output text | sort
Copy the exact value from the output. Don't retype it.
Wrong Filter Name Syntax for Tags
Tag filters require the tag: prefix โ it's not optional:
# WRONG โ "Name" is an EC2 filter for resource names, not the tag
filter {
name = "Name"
values = ["my-server"]
}
# CORRECT
filter {
name = "tag:Name"
values = ["my-server"]
}
Multiple Values Are OR, Multiple Blocks Are AND
Multiple values inside one filter block = OR. Multiple filter blocks = AND. Confuse the two and your filter quietly does something different from what you intended:
# OR: finds instances named "server-a" OR "server-b"
filter {
name = "tag:Name"
values = ["server-a", "server-b"]
}
# AND: finds instances that have the right name AND are in us-west-2a
filter {
name = "tag:Name"
values = ["my-app-server"]
}
filter {
name = "availability-zone"
values = ["us-west-2a"]
}
AMIs: Too Many Results Is Also an Error
The aws_ami data source flips the problem: filters matching multiple AMIs also throw an error. Pin it with most_recent = true:
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
Quick Fix: Isolate the Bad Filter
Comment out filters one at a time until the data source resolves. Start with the most specific filter:
data "aws_instance" "app_server" {
filter {
name = "tag:Name"
values = ["my-app-server"]
}
# Temporarily disabled to isolate the problem
# filter {
# name = "instance-state-name"
# values = ["running"]
# }
}
Once terraform plan runs cleanly, add filters back one at a time. The one that breaks it again is the culprit.
Permanent Fix: Use the Plural Data Source
Conditional infrastructure, blue/green deployments, instances that get stopped periodically โ with those patterns, the singular data source will keep exploding. Swap to aws_instances (plural) instead. Empty list, no error:
data "aws_instances" "app_servers" {
filter {
name = "tag:Name"
values = ["my-app-server"]
}
filter {
name = "instance-state-name"
values = ["running"]
}
}
locals {
app_server_id = length(data.aws_instances.app_servers.ids) > 0 ? data.aws_instances.app_servers.ids[0] : null
}
Terraform plan runs clean. Handle the null case explicitly downstream โ at least the pipeline isn't blocked.
Mandatory data sources โ shared VPCs, base AMIs, locked-down security groups โ deserve a comment. When they break at midnight, that comment is the difference between a 5-minute fix and a 45-minute investigation:
data "aws_instance" "app_server" {
# Requires: EC2 instance tagged Name=my-app-server in running state.
# Owned by the infra-base workspace โ start it there if this fails.
filter {
name = "tag:Name"
values = ["my-app-server"]
}
filter {
name = "instance-state-name"
values = ["running"]
}
}
Verifying the Fix
Target just the data source to confirm it resolves before running a full plan:
terraform plan -target='data.aws_instance.app_server'
Add a debug output to inspect what was found:
output "debug_instance_id" {
value = data.aws_instance.app_server.id
}
Run terraform output debug_instance_id after applying. A valid instance ID like i-0abc123def456 confirms the data source is resolving correctly. Delete the output before committing โ it leaks infrastructure IDs into your state file unnecessarily.

