๐Ÿš€ Noahops enterprise beta is now open โ€” Get early access โ†’
Back to blog

ECS Fargate Task Stuck? Here's How to Debug It (Without Guessing)

ECS Fargate task stuck in PENDING or PROVISIONING is one of the most frustrating startup infrastructure problems. There's no error. No crash. The task just sits there.

The reason it feels so opaque: ECS is doing a lot of work silently before your container code ever runs. If anything in that chain fails, ECS often doesn't surface the error in an obvious place. It just waits.

This guide explains what ECS is actually doing at each stage, which failures are silent vs. visible, and how to fix the five most common causes โ€” in the order you're most likely to hit them.


What ECS Is Doing When a Task Gets "Stuck"

Before debugging, it helps to know what PROVISIONING and PENDING actually mean.

When you deploy a Fargate service, ECS runs through this sequence:

  1. PROVISIONING โ€” ECS allocates a network interface (ENI) and attaches it to your task. This is the networking handshake. If your subnet has no available IPs, or your security group is invalid, the task can hang here.

  2. PENDING โ€” ECS is pulling your container image, injecting secrets, and configuring the task runtime. If your ECR image is unreachable, your execution role lacks permissions, or a secret can't be fetched, the task hangs here.

  3. RUNNING โ€” Your container code is live. Health checks start. ALB registers the task as a target.

Most "stuck" tasks are failing between PROVISIONING โ†’ PENDING or PENDING โ†’ RUNNING. The fix lives in one of five places.


Step 0: Look at Service Events First

Before anything else, check the service events. This is where ECS surfaces what it's actually waiting for.

aws ecs describe-services \
  --cluster your-cluster \
  --services your-service \
  --query 'services[0].events[:10].{Time:createdAt,Message:message}' \
  --output table

If ECS has a specific complaint โ€” subnet IP exhaustion, capacity issues, IAM role errors โ€” it shows up here. Read the messages carefully. They're not always obvious, but they point you at the right layer.

If service events don't show anything useful, move through the five checks below in order.


Failure Mode 1: Subnet IP Exhaustion

Symptom: Task stuck in PROVISIONING. Service events say something like "insufficient IP addresses in subnet" or the task just hangs silently.

What's happening: In Fargate, every task gets its own ENI with a dedicated private IP. If your subnet runs out of IP addresses โ€” which happens faster than you'd expect with /24 subnets and aggressive scaling โ€” new tasks can't start.

Check it:

aws ec2 describe-subnets \
  --subnet-ids subnet-abc123 \
  --query 'Subnets[0].{AvailableIPs:AvailableIpAddressCount,CIDR:CidrBlock}'

A /24 subnet gives you 251 usable IPs. If you're running 20 tasks across three subnets, that's fine. But if you have 200 tasks in one subnet plus ENIs from other services, you'll hit zero fast.

Fix: Either use multiple subnets spread across AZs (recommended โ€” you want multi-AZ anyway for availability), or resize to a /23 or /22 if your VPC CIDR has room.

NoahOps provisions VPC environments with pre-sized subnets per stage so this doesn't catch you mid-deploy.


Failure Mode 2: Missing Outbound Port 443

Symptom: Task stuck in PENDING. Image never pulls. No obvious error in service events.

What's happening: Fargate needs outbound HTTPS (port 443) to reach:

  • ECR (to pull your container image)
  • AWS services (Secrets Manager, SSM Parameter Store, CloudWatch)
  • Any external API your app calls at startup

If your task security group blocks outbound 443, or your subnet has no route to the internet (no NAT Gateway for private subnets, no Internet Gateway for public subnets), the task silently hangs waiting for the image pull that will never complete.

Check it:

# Check your security group outbound rules
aws ec2 describe-security-groups \
  --group-ids sg-abc123 \
  --query 'SecurityGroups[0].IpPermissionsEgress'

Look for a rule allowing outbound TCP 443 to 0.0.0.0/0. If it's missing, that's your problem.

For private subnets, also verify your route table has a route to a NAT Gateway:

aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=subnet-abc123 \
  --query 'RouteTables[0].Routes'

You should see a 0.0.0.0/0 route pointing at a NAT Gateway (nat-...). If it points at nothing or an Internet Gateway, your private subnet tasks can't reach ECR.

Fix: Add an outbound rule allowing TCP 443 to 0.0.0.0/0 in your task security group. For private subnets, add a NAT Gateway in a public subnet and update the route table.


Failure Mode 3: Execution Role Missing Permissions

Symptom: Task stuck in PENDING. Service events may say "ECS was unable to assume the role" or the task just dies without reaching RUNNING.

What's happening: There are two IAM roles in ECS โ€” don't confuse them:

  • Execution role โ€” used by ECS itself to pull your image from ECR, write logs to CloudWatch, and fetch secrets before your container starts.
  • Task role โ€” used by your application code at runtime (S3 access, DynamoDB calls, etc.).

If your execution role is misconfigured, ECS can't even get the image or inject your secrets. Your container code never runs.

What the execution role needs at minimum:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

If you're also using Secrets Manager:

{
  "Effect": "Allow",
  "Action": [
    "secretsmanager:GetSecretValue",
    "kms:Decrypt"
  ],
  "Resource": "arn:aws:secretsmanager:us-east-1:123456789:secret:your-secret*"
}

Check it:

aws iam list-attached-role-policies --role-name ecsTaskExecutionRole
aws iam get-role --role-name ecsTaskExecutionRole \
  --query 'Role.AssumeRolePolicyDocument'

The trust policy should allow ecs-tasks.amazonaws.com to assume the role. If it says ecs.amazonaws.com only, ECS can't use it for Fargate tasks.

Fix: Attach the AmazonECSTaskExecutionRolePolicy managed policy as a baseline, then add any extra permissions for your secrets. Make sure the trust policy includes ecs-tasks.amazonaws.com.


Failure Mode 4: Secret or Parameter Store Not Found

Symptom: Task reaches PENDING but fails before RUNNING. CloudWatch logs may show nothing (because the container never started). Service events may say something about secrets.

What's happening: ECS injects secrets before your container starts. If a secret ARN is wrong, the secret doesn't exist, or the execution role can't access it, the task fails before your app code runs.

Common mistakes:

  • Typo in the secret ARN in your task definition
  • Secret is in a different region than your ECS task
  • Secret was deleted but task definition still references it
  • Execution role has secretsmanager:GetSecretValue but the secret's resource-based policy blocks it

Check it: Copy the exact ARN from your task definition and describe it:

aws secretsmanager describe-secret \
  --secret-id arn:aws:secretsmanager:us-east-1:123456789:secret:your-secret-abc123

If it returns an error, the secret doesn't exist or the ARN is wrong. Make sure the region in the ARN matches your ECS cluster region.

Fix: Update the task definition with the correct ARN. If the secret exists in another region, you either need to replicate it or run your task in the same region as the secret.


Failure Mode 5: Health Check Killing Tasks Too Early

Symptom: Task reaches RUNNING briefly, then gets killed and restarted in a loop. ALB shows targets as unhealthy. Service events say "task stopped with exit code 0" or similar.

What's happening: Your container is healthy, but the ALB health check is hitting it before your app is ready to serve traffic. ECS sees the task as failing and kills it. Then it starts another one. Loop.

This is especially common with:

  • Node.js apps with a slow startup (connecting to DB, warming cache)
  • Java/JVM apps that take 10โ€“30 seconds to initialize
  • Apps that need to run migrations on startup

The two health checks at play:

Your task definition has a container health check (optional). Your ALB target group has its own health check (required if you're using a load balancer). They work independently โ€” a task can pass the container check but fail the ALB check, or vice versa.

Fix for ALB health check:

Increase the healthCheckGracePeriodSeconds on your ECS service. This tells ECS to wait before considering ALB health check failures as a reason to kill the task.

aws ecs update-service \
  --cluster your-cluster \
  --service your-service \
  --health-check-grace-period-seconds 60

Set it to slightly longer than your slowest startup time. For most apps, 30โ€“60 seconds is enough. For JVM apps, you might need 90โ€“120.

Also check your ALB target group health check settings:

aws elbv2 describe-target-groups \
  --target-group-arns arn:aws:elasticloadbalancing:...

Make sure the health check path returns 200 during startup (or use a dedicated /health endpoint that returns 200 immediately, separate from your full readiness check).


Quick Decision Tree

Task stuck in PROVISIONING?
  โ†’ Check subnet available IPs
  โ†’ Check security group outbound 443

Task stuck in PENDING?
  โ†’ Check execution role permissions (ECR, logs, secrets)
  โ†’ Check secret ARNs are correct and in the right region

Task reaches RUNNING then loops?
  โ†’ Increase healthCheckGracePeriodSeconds
  โ†’ Check ALB health check path and timing

The Fastest Way to Get Logs When Nothing Shows Up

If the task fails before your application starts, CloudWatch logs will be empty. The container never ran. In this case, check task-level details directly:

# Get the stopped task ARN
aws ecs list-tasks \
  --cluster your-cluster \
  --service-name your-service \
  --desired-status STOPPED \
  --query 'taskArns[0]'

# Get the stop reason
aws ecs describe-tasks \
  --cluster your-cluster \
  --tasks <task-arn> \
  --query 'tasks[0].{StopCode:stopCode,Reason:stoppedReason,Containers:containers[0].reason}'

stoppedReason and the container-level reason field are where ECS puts the actual error. These two fields, combined with service events, will tell you which of the five failure modes you're hitting.


How NoahOps Handles This

ECS debugging is miserable the first few times because the errors are spread across three different places: service events, task details, and CloudWatch. You have to know where to look before you know what you're looking for.

NoahOps deploys Fargate environments with pre-validated VPC networking, properly scoped execution roles, and auto-wired Secrets Manager injection โ€” so these five failure modes are handled before your first deploy. When a task does fail, the dashboard surfaces the stop reason and service events in one place, without navigating five AWS console screens.

Request a free demo at noahops.com.


FAQ

Why does my task show no logs when it fails?

If the task fails before RUNNING (during image pull or secret injection), the container never started, so there's nothing to log. Check task-level stoppedReason and service events instead.

My task runs locally with Docker but fails on ECS. Why?

The most common causes are: (1) your local image uses env vars you hardcoded for local dev but ECS expects from Secrets Manager, (2) your app binds to localhost instead of 0.0.0.0, so the ALB health check can't reach it, (3) the image isn't pushed to ECR (you're testing with a local build).

How long should I wait before assuming a task is stuck?

PROVISIONING should resolve in under 60 seconds. PENDING can take 2โ€“3 minutes for a large image pull. If you're past 5 minutes with no transition, it's stuck.

Can I get CloudWatch logs even if the task failed before starting?

Not container logs โ€” but you can look at ECS agent logs if you have EC2 instances. For Fargate, the task-level stoppedReason field is your main source of truth for pre-start failures.

Should I use public or private subnets for Fargate?

Private subnets with a NAT Gateway is the standard production setup โ€” it keeps your containers off the public internet. Public subnets work (and are cheaper โ€” no NAT Gateway cost) but require assignPublicIp: ENABLED in your task networking config. For compliance workloads, private subnets are required.

Ready to ship to AWS?

Try NoahOps free โ€” VPC, ECS, RDS, CI/CD in your own AWS account in 15 minutes.