AWS ALB 503 "No Healthy Targets": Fix Unhealthy Backends

An ALB returning “503 Service Unavailable — No healthy targets” means the load balancer itself is fine, but it has nowhere to send requests. The three most common causes are a missing security group rule between the ALB and the target, a misconfigured health check path or response code, and no targets registered in the target group at all. This guide walks through each one in order.

Quick answer

Run this first:

aws elbv2 describe-target-health \
  --target-group-arn <your-target-group-arn> \
  --query 'TargetHealthDescriptions[*].[Target.Id,TargetHealth.State,TargetHealth.Reason,TargetHealth.Description]' \
  --output table

What you see tells you exactly where to go next:

ResultMost likely causeJump to
Empty listNothing registered in the target groupStep 2
unhealthy + Target.FailedHealthChecksALB cannot reach the targetStep 3
unhealthy + Target.ResponseCodeMismatchALB reaches target, wrong HTTP statusStep 4
initialTarget just registered, first health check pendingWait 30–90 seconds
unusedTarget group not attached to a listener ruleCheck listener rules

Simple explanation

An Application Load Balancer sits between your users and your servers. Users send requests to the ALB, the ALB picks a healthy server from its target group, and forwards the request there.

A target group is a list of servers (EC2 instances, IP addresses, or Lambda functions) the ALB can send traffic to. The ALB regularly sends a small test request called a health check to each target. A target that passes is marked healthy and receives traffic. A target that fails is marked unhealthy and removed from the rotation.

When every target is unhealthy, or when no targets are registered at all, the ALB has nowhere to send the request. It returns 503 directly to the user. Your application may not be receiving the request at all.

A helpful mental model

Think of the ALB as a maître d’ at a restaurant. Before seating a guest, they check whether any tables are ready to serve. If every server called in sick, the maître d’ tells the guest there is nothing available right now. The ALB does the same thing: before forwarding a request, it checks which targets passed their last health check. No healthy targets means 503.

What users usually see

  • Browser or API client receives 503 Service Unavailable with body: No healthy targets are available to service this request.
  • AWS Console shows target group targets in Unhealthy status with reason codes like Health checks failed or Response code mismatch
  • A deployment completes successfully but traffic fails immediately after the new target group goes active
  • Application was working, then a security group change or code deployment broke it
  • New environment works in testing but fails in production because the security group setup differs

ALB 503 vs application 503

A 503 does not always mean the same thing. Knowing the source changes the fix entirely.

ALB-generated 503Application-generated 503
Who sends itThe ALB itselfYour application code
Body textNo healthy targets are available to service this request.Your app’s error message
Target healthAll targets unhealthy or target group emptyTargets are healthy from the ALB’s perspective
Root causeNetwork or config problem between ALB and targetApplication logic error, overload, or dependency failure
Where to startdescribe-target-healthApplication logs, APM, CloudWatch metrics

If the ALB successfully forwards requests to a target, that target appears healthy regardless of whether the app returns 500s or 503s under real traffic. A health-check-passing target returning 503 to users is an application-level problem, not a load balancer problem.

ALB vs NLB troubleshooting differences

Most of this guide applies to both ALB and NLB, but there are important differences in how security groups and health checks work.

ALBNLB
Security groupsAlways has a security groupOptional (NLBs support security groups as of late 2023, but older NLBs may not have one)
Health check sourceALB node IPs covered by the ALB security groupNLB node IPs from the NLB’s subnets
Health check protocolsHTTP / HTTPS / gRPCHTTP / HTTPS / TCP / TCP_UDP
Source IP seen by targetALB node IP (client IP in X-Forwarded-For header)Client IP for TCP, NLB node IP for TLS
No-healthy-targets behaviourALB returns 503 to the clientNLB drops the connection; clients see connection refused

For NLBs without a security group: Health check traffic originates from the NLB’s node IPs, which come from the NLB’s subnets. Target security groups must allow inbound TCP on the health check port from those subnet CIDR ranges.

For NLBs with a security group (post-2023 behaviour): You can reference the NLB security group in the target security group inbound rule, the same approach used for ALB. Check whether your NLB has a security group before deciding which rule to add.

See Internal Load Balancers for setup differences when the load balancer is not internet-facing.

How it works

Understanding the request flow makes the debugging steps obvious.

  1. Listener receives request. The ALB listener on port 443 (or 80) accepts the connection from the client.
  2. Listener rule selects a target group. The listener evaluates its rules — path, host header, query string — and routes to a matching target group.
  3. ALB checks target health. Before forwarding, the ALB checks which targets are currently marked healthy based on recent health check results.
  4. Healthy target selected. The ALB picks a healthy target using the configured routing algorithm and forwards the request.
  5. No healthy targets → 503. If all targets are unhealthy or the target group is empty, the ALB returns 503 immediately.

Health checks run on a background schedule (default: every 30 seconds). Results are not real-time. A target that just became reachable can take up to 30 seconds per check, and 90 seconds total to cross the 3-check healthy threshold.

Fast triage checklist

Work through these questions in order. Most issues resolve at the third question.

  • Are targets registered? Run describe-target-health. Empty list means nothing is registered. Go to Step 2.
  • What state are targets in? initial means wait. unused means the target group is not attached to a listener rule. unhealthy means keep going.
  • Can the ALB reach the target? Target.FailedHealthChecks is a connection-level failure. Check security groups and network. Go to Step 3.
  • Is the app responding correctly? Target.ResponseCodeMismatch means the connection works but the status code is wrong. Check the health check path and matcher. Go to Step 4.
  • Did the app just start? Wait 60–120 seconds for startup and the first clean health check cycle before investigating further.
  • Is deregistration delay creating a timing gap? Old targets draining while new ones are not yet healthy can leave brief gaps in small clusters. See Step 5.
  • Is the target group attached to the right listener rule? A target group not referenced by any rule shows all targets as unused.

Step 1: Check target health status

This is always the first command. The reason codes tell you almost everything.

# Get the target group ARN
aws elbv2 describe-target-groups \
  --names my-target-group \
  --query 'TargetGroups[0].TargetGroupArn' \
  --output text

# Check health of all registered targets
aws elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/my-target-group/abc123 \
  --query 'TargetHealthDescriptions[*].[Target.Id,TargetHealth.State,TargetHealth.Reason,TargetHealth.Description]' \
  --output table

Target states:

StateMeaning
healthyPassing health checks, receiving traffic
unhealthyRegistered but failing health checks, not receiving traffic
initialJust registered, first health check not yet complete
drainingDeregistered, waiting for in-flight requests to finish
unusedTarget group not attached to a load balancer listener rule

Reason codes for unhealthy targets:

Reason codeWhat it meansWhere to look
Target.FailedHealthChecksConnection to the target failed entirelySecurity groups, NACLs, subnet routing
Target.ResponseCodeMismatchConnected, but HTTP status code did not match expectedHealth check path, protocol, matcher config
Target.TimeoutHealth check request timed outApp startup delay, network latency, blocked return traffic
Target.NotInUseTarget group not associated with a load balancerListener rule configuration
Elb.InternalErrorALB internal errorRare; contact AWS Support if persistent

Step 2: Confirm targets are actually registered

A common cause of 503 on a brand-new setup is simply that no targets are registered. describe-target-health returning an empty list confirms this.

Manually registered targets — register an EC2 instance directly:

aws elbv2 register-targets \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/my-target-group/abc123 \
  --targets Id=i-1234567890abcdef0,Port=80

Auto Scaling Group targets — instances register automatically only if the ASG is attached to the target group. Verify the attachment:

aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names my-asg \
  --query 'AutoScalingGroups[0].TargetGroupARNs'

If the target group ARN is not in that list, the Auto Scaling Group is not linked and no instances will register automatically. Attach it:

aws autoscaling attach-load-balancer-target-groups \
  --auto-scaling-group-name my-asg \
  --target-group-arns arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/my-target-group/abc123

Listener rule check — even with healthy registered targets, traffic will not reach them if the target group is not referenced by a listener rule:

aws elbv2 describe-rules \
  --listener-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:listener/app/my-alb/abc123/def456

Look for your target group ARN in the Actions of one of the rules. If it is absent, no traffic is routed to it regardless of target health.

Step 3: Verify security groups, network path, and reachability

Target.FailedHealthChecks means the ALB cannot establish a connection to the target at all. The most common cause is a missing inbound rule on the target’s security group.

The correct security group setup for ALB:

The ALB sends health check and real traffic from its node IPs. The cleanest way to allow this is to reference the ALB security group ID in the target’s inbound rule, not an IP CIDR range.

# Find the ALB security group
aws elbv2 describe-load-balancers \
  --names my-alb \
  --query 'LoadBalancers[0].SecurityGroups'

# Allow inbound from the ALB security group on the application port
aws ec2 authorize-security-group-ingress \
  --group-id sg-0target123 \
  --protocol tcp \
  --port 80 \
  --source-group sg-0alb456
Never use ALB IP addresses in this rule

ALBs scale horizontally. When traffic spikes, AWS adds more ALB nodes with new IP addresses. An inbound rule based on IP ranges will work until the ALB scales, then silently drop health checks and requests. Always use the ALB security group ID (sg-xxxx), not a CIDR range.

For NLBs without a security group: Health check traffic comes from the NLB node IPs within the NLB’s subnets. Allow inbound from those subnet CIDRs:

aws ec2 authorize-security-group-ingress \
  --group-id sg-0target123 \
  --protocol tcp \
  --port 80 \
  --cidr 10.0.1.0/24

When to check Network ACLs and route tables:

Security groups are stateful. If inbound is allowed, return traffic is automatically allowed. Network ACLs are stateless and can block return traffic even when the inbound rule exists. Check NACLs if security groups look correct but the connection still fails.

Route tables matter when the ALB and target are in different subnets or the target is in a private subnet. See Debugging VPC Connectivity for a systematic approach to network-layer failures.

Step 4: Verify health check configuration

Target.ResponseCodeMismatch or Target.Timeout when the connection works means the health check configuration does not match what your application serves.

Check current health check settings:

aws elbv2 describe-target-groups \
  --target-group-arns arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/my-target-group/abc123 \
  --query 'TargetGroups[0].{Path:HealthCheckPath,Port:HealthCheckPort,Protocol:HealthCheckProtocol,Matcher:Matcher,Interval:HealthCheckIntervalSeconds,Threshold:HealthyThresholdCount}'

Wrong path. The default path is /. If your app returns 404 on / because the root is at /api/v1/, health checks fail with ResponseCodeMismatch. Update the path:

aws elbv2 modify-target-group \
  --target-group-arn arn:aws:elasticloadbalancing:... \
  --health-check-path /health

Best practice: create a dedicated /health endpoint that returns 200 with no business logic and no authentication required. Its only job is to confirm the process is running.

Login-protected health endpoints are a common trap

If your health check path requires authentication and returns 401 or 302 (redirect to login page), the ALB marks every target unhealthy. The health endpoint must return 200 without any auth check, every time, regardless of session state. If you cannot add a dedicated endpoint, configure the matcher to accept the redirect code — but fixing the endpoint is cleaner.

Wrong expected response code. The default matcher is 200. If your health endpoint returns 204 No Content, configure the matcher to accept it:

aws elbv2 modify-target-group \
  --target-group-arn arn:aws:elasticloadbalancing:... \
  --matcher HttpCode=200-299

Wrong port. The health check port defaults to traffic-port, the port registered with the target. If your app runs on 8080 but targets were registered on port 80, the health check fires on the wrong port. Specify the correct port explicitly if they differ.

Protocol mismatch. If your application is HTTPS-only and the health check uses HTTP, you get a connection reset or empty response. Match the health check protocol to what the target actually accepts.

Step 5: Startup timing, grace periods, and deployments

A common source of unexpected unhealthy targets is timing: the target is fine, but it was not ready when the health check first fired.

Slow application startup. If your application takes 45 seconds to start and the health check interval is 10 seconds with an unhealthy threshold of 3, the ALB marks the target unhealthy before the app finishes starting. Solutions:

  • Add a lightweight /health endpoint early in startup that returns 200 once the port is open, before full initialization completes
  • Increase HealthCheckIntervalSeconds to give the app more time between checks
  • Stagger deployments so not all targets restart at the same time

EC2 Auto Scaling health check grace period. When Auto Scaling launches a new EC2 instance, it waits for the health check grace period (default: 300 seconds) before evaluating whether to replace the instance. During this window, Auto Scaling ignores ALB health check results. This grace period is separate from the ALB health check: the ALB starts checking immediately after the target registers, independent of Auto Scaling’s timer.

Instance warmup in scaling policies. For step scaling and target tracking policies, you can configure an instance warmup period. New instances launched during a scale-out event are not counted toward the group’s current capacity until warmup ends. This prevents Auto Scaling from over-provisioning by treating still-starting instances as not yet contributing.

Deregistration delay and rolling deployments. When a target is removed from a target group, the ALB keeps routing to it for the deregistration delay (default: 300 seconds) to let in-flight requests finish. During this window the target shows as draining.

Small clusters are vulnerable to timing gaps

With only 2 targets, if one starts draining for 5 minutes and the replacement takes 2 minutes to become healthy, there is a 3-minute window where you have one draining target and one that is not yet healthy. Plan your minimum healthy capacity around this timing before deploying. Reduce the deregistration delay if deployment speed matters more than completing long-running requests.

Reduce the deregistration delay for environments where deployment speed matters:

aws elbv2 modify-target-group-attributes \
  --target-group-arn arn:aws:elasticloadbalancing:... \
  --attributes Key=deregistration_delay.timeout_seconds,Value=30

Step 6: Logs and observability

describe-target-health shows the current state. Logs and metrics show what happened over time.

CloudWatch metrics. HealthyHostCount and UnHealthyHostCount are available per target group and update every minute. They are the fastest way to see when the problem started and whether it is improving.

aws cloudwatch get-metric-statistics \
  --namespace AWS/ApplicationELB \
  --metric-name UnHealthyHostCount \
  --dimensions Name=TargetGroup,Value=targetgroup/my-target-group/abc123 \
              Name=LoadBalancer,Value=app/my-alb/xyz789 \
  --start-time 2026-05-13T10:00:00Z \
  --end-time 2026-05-13T11:00:00Z \
  --period 60 \
  --statistics Average

ALB access logs. Access logs capture every request the ALB processes and write them to S3. They help confirm whether the ALB is reaching the target: if health check requests appear in the logs with a response, the target was reachable and the issue is the response content. If they do not appear, the connection failed before any response was received.

Access logs are different from your application logs. Access logs record what the ALB saw; your application logs record what happened inside the app after receiving the request. Both are useful but they answer different questions.

Enable access logs before you need them

ALB access logs are not enabled by default. If you are actively troubleshooting, enable them now on the load balancer attributes in the console or via CLI. They will not show what happened before they were enabled, but they are essential for diagnosing the next incident. The cost is low — you pay only for the S3 storage.

See CloudWatch Logs for setting up log-based alerting once you know what to watch for.

Direct connectivity test. If you have SSH access to the target instance, simulate the health check locally to verify the application is responding:

# From the target instance
curl -v http://localhost:80/health

# From another instance in the same subnet (simulates the ALB path)
curl -v http://10.0.2.50:80/health

This confirms whether the application is running and responding correctly, independent of the ALB and security group layer.

Common mistakes

  1. Using ALB IP addresses in the target security group instead of the ALB security group ID. ALBs scale horizontally and their node IPs change. An IP-based rule that works today may break during the next traffic spike. Always reference the ALB security group ID (sg-xxxx).
  2. Health check path requires authentication. If the health check hits a route that redirects to a login page (302) or returns 401, the ALB marks every target unhealthy. Use a dedicated /health endpoint with no auth and no business logic.
  3. Wrong health check path for the application’s URL structure. The default path is /. If your app returns 404 on / because it is mounted at /api/ or /app/, health checks fail with ResponseCodeMismatch immediately.
  4. Target group not attached to a listener rule. Instances can be registered and healthy in a target group, but if no listener rule routes traffic to it, no requests reach them. Targets show as unused.
  5. Auto Scaling Group not linked to the target group. ASGs do not register instances automatically unless explicitly attached. After creating a target group, verify the ASG’s TargetGroupARNs includes it.
  6. Startup delay mismatch causing premature unhealthy marks. The ALB starts health checks immediately after registration. If the app takes 60 seconds to start and health checks run every 10 seconds with a threshold of 3, the target is marked unhealthy before it is ready. Adjust the interval, threshold, or startup sequence.
  7. Deregistration delay creating a capacity gap during rolling deployments. With a small number of targets, old instances draining for 5 minutes while replacements take 2 minutes to become healthy creates a window with no healthy capacity. Reduce deregistration delay or ensure minimum healthy capacity before starting a rollout.

When to use this guide

This is the right page if:

  • The ALB returns 503 with the message “No healthy targets are available”
  • The target group shows targets in unhealthy status
  • A new deployment broke traffic behind a load balancer
  • Health checks are failing even though the application appears to be running

This is not the right page if:

Example walkthrough: new deployment returns 503

You deploy a new version of an application. The deployment succeeds but the ALB immediately returns 503 to all users.

Check target health:

aws elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/new-tg/xyz789 \
  --output table

Three targets, all unhealthy, reason Target.FailedHealthChecks. The ALB cannot connect to them.

Find the ALB security group:

aws elbv2 describe-load-balancers \
  --names my-alb \
  --query 'LoadBalancers[0].SecurityGroups'
# ["sg-0alb123"]

Check the target security group:

aws ec2 describe-instances \
  --instance-ids i-target1 \
  --query 'Reservations[0].Instances[0].SecurityGroups'
# [{"GroupId": "sg-0newapp456"}]

aws ec2 describe-security-groups \
  --group-ids sg-0newapp456 \
  --query 'SecurityGroups[0].IpPermissions'
# []  ← empty

The new deployment used a new security group with no inbound rules. The old security group had the ALB rule, but it was not copied when the new group was created.

Fix:

aws ec2 authorize-security-group-ingress \
  --group-id sg-0newapp456 \
  --protocol tcp \
  --port 80 \
  --source-group sg-0alb123

Within 30–90 seconds, targets transition to healthy and 503 errors stop.

Frequently asked questions

How long does it take for a target to become healthy after fixing the issue?

It depends on your health check settings. With defaults (30-second interval, threshold of 3 consecutive healthy checks), a target needs at least 90 seconds after the fix before the ALB marks it healthy. During that window the ALB only sends traffic to already-healthy targets.

What is the difference between unhealthy, initial, draining, and unused?

Unhealthy means the target is registered but failing health checks. Initial means it was just registered and has not been checked yet — wait for the first check cycle. Draining means the target is being deregistered and the ALB is waiting for in-flight requests to finish before cutting traffic. Unused means the target group is not attached to any load balancer listener rule.

Can a health check endpoint return 204 or 302?

Yes, but you must configure the ALB to accept those codes. By default the ALB expects HTTP 200. Use --matcher HttpCode=200-299 to accept any 2xx response including 204. For redirects (302), it is better to fix the health endpoint to return 200 directly — a redirect to a login page will also fail the health check.

Why does a deployment fail even though instances are running?

Running is not the same as healthy. A new EC2 instance can be running and reachable but the application might still be initializing. If the ALB health check fires before the app is ready, the target goes unhealthy. The EC2 Auto Scaling health check grace period (default 300 seconds) gives instances time before Auto Scaling acts on them, but the ALB health check starts immediately after registration, independently of that grace period.

When should I troubleshoot DNS instead of target health?

If clients cannot reach the load balancer at all, getting a connection refused or NXDOMAIN error rather than a 503 response, the issue is DNS or routing, not target health. A 503 from the ALB itself confirms the ALB is reachable and the problem is behind it. See the DNS troubleshooting guide if errors appear before the ALB responds.

Last verified: 13 May 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.