Debugging AWS Lambda + ALB 503 Errors: A Step‑by‑Step Guide to Event Format Mismatches
When I first put an AWS Lambda behind an Application Load Balancer (ALB), everything looked correct:
- The Lambda worked perfectly when tested directly.
- The ALB target group was configured for Lambda.
- Listener rules and routing looked fine.
- Permissions were in place.
But calling the Lambda through the ALB kept returning:
503 Service Unavailable
Meanwhile, the same Lambda worked through a Function URL and API Gateway v2. The root cause turned out to be a subtle event format mismatch between what the Lambda handler expected and what ALB actually sends.
This post walks through the exact debugging steps, how I identified the mismatch, and how to fix it.
Step 1: Verify Lambda Function Health
Estimated Time: 2–3 minutes
Action: Test the Lambda function directly.
- Use a Function URL or the Lambda console test feature.
- Confirm that the function executes successfully.
Expected Outcome: Lambda works fine in isolation.
Key Insight:
If the Lambda works when tested directly, the issue is in the integration layer (ALB), not the Lambda code itself.
Step 2: Check ALB Target Group Configuration
Estimated Time: 5 minutes
Action: Verify your ALB target group.
- Target type:
lambda - Lambda function registered as target
- Health checks: disabled (normal for Lambda)
- Target status:
unavailable(expected for Lambda targets)
Key Insight:
“Unavailable” status with disabled health checks is normal for Lambda targets — it is not the cause of 503 errors.
Step 3: Verify ALB Permissions
Estimated Time: 3 minutes
Action: Check the Lambda resource policy to ensure ALB can invoke the function:
aws lambda get-policy --function-name my-lambda-function
Verify:
- The ALB has
lambda:InvokeFunctionpermission. - The ALB ARN is present in the policy.
Key Insight:
Missing permissions would usually result in 403 or 502 errors, not 503. Still, it’s worth ruling out early.
Step 4: Analyze ALB Listener Rules
Estimated Time: 10 minutes
Action: Check routing configuration on the ALB listener.
For example:
- Rule priority:
155 - Path pattern:
/plugin/* - Host header:
api.example.com - Target group:
MY-LAMBDA-TG
Key Insight:
ALB routing is strict — both path and host header must match exactly. A mismatch here can easily cause requests to hit the wrong target or no target at all.
Step 5: Test with Correct Routing
Estimated Time: 5 minutes
Action: Hit the ALB with the correct host and path.
curl -H "Host: api.example.com" https://my-alb-123456.eu-west-2.elb.amazonaws.com/plugin/test
Result: In my case, this still returned 503, even though routing looked correct.
Key Insight:
Correct routing alone does not guarantee success. If the Lambda works directly but fails through the ALB, start suspecting an event format mismatch.
Step 6: Compare Working vs Non‑Working Scenarios
Estimated Time: 5 minutes
Pattern Recognition:
- Function URL: works
- API Gateway v2: works
- ALB: fails (503)
Key Insight:
The working integrations (Function URL and API Gateway v2) share the same event format, which ALB does not use. That difference becomes important.
Step 7: Examine the Lambda Handler Signature
Estimated Time: 3 minutes
Action: Inspect the Lambda handler to see what type of event it expects.
For example (pseudo-steps):
aws lambda get-function --function-name my-lambda-function
# Then inspect / decompile the handler class if needed
javap -cp . -public com.example.demo.LambdaRequestHandler
Handler Signature Example (Java):
public class LambdaRequestHandler implements RequestHandler<APIGatewayV2HTTPEvent, LambdaResponse> {
@Override
public LambdaResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
// ...
}
}
Key Insight:
The handler here expects APIGatewayV2HTTPEvent, which is the event format used by API Gateway HTTP APIs and Function URLs — not the ApplicationLoadBalancerRequestEvent style payload that ALB sends.
Step 8: Analyze CloudWatch Logs
Estimated Time: 2 minutes
Action: Look for exceptions in CloudWatch Logs.
aws logs filter-log-events \
--log-group-name "/aws/lambda/my-lambda-function" \
--filter-pattern "Exception" \
--max-items 5
Example Error:
NullPointerException: Cannot invoke "String.hashCode()" because "<localX>" is null
at com.example.demo.LambdaRequestHandler.handleRequest(LambdaRequestHandler.java:77)
Root Cause:
Because the handler expects a specific JSON structure (APIGatewayV2HTTPEvent), it tries to access fields that don’t exist in the ALB event → resulting in NullPointerException and ultimately a 503 from ALB.
Step 9: Identify the Event Format Mismatch
Estimated Time: 2 minutes
Let’s compare the incoming event shapes.
Expected by Lambda (API Gateway v2 / Function URL):
{
"version": "2.0",
"routeKey": "POST /plugin",
"rawPath": "/plugin/payment",
"httpMethod": "POST",
"headers": { "...": "..." },
"body": "..."
}
Sent by ALB:
{
"requestContext": { "elb": { /* ... */ } },
"httpMethod": "POST",
"path": "/plugin/payment",
"headers": { "...": "..." },
"body": "..."
}
Problem:
The JSON structure is different. Fields your code assumes exist (e.g., version, routeKey, rawPath) are not present in the ALB event. Accessing them directly leads to NullPointerException and a 503 at the ALB level.
Step 10: Verify with Test Events
Estimated Time: 3 minutes
You can simulate both event types in the Lambda console.
ALB Test Event (will fail if your handler expects API Gateway v2):
{
"requestContext": {
"elb": {
"targetGroupArn": "arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-target-group/1234567890abcdef"
}
},
"httpMethod": "POST",
"path": "/plugin/payment",
"headers": {
"content-type": "application/json"
},
"body": "{\"test\": \"data\"}"
}
API Gateway v2 Test Event (will work with APIGatewayV2HTTPEvent handler):
{
"version": "2.0",
"routeKey": "POST /plugin",
"rawPath": "/plugin/payment",
"httpMethod": "POST",
"headers": {
"content-type": "application/json"
},
"body": "{\"test\": \"data\"}"
}
Result:
If the API Gateway v2 event works but the ALB event fails with an exception, you’ve confirmed the event format mismatch as the root cause.
The Fix: Update the Lambda Handler to Support ALB
Once you know the issue is an event format mismatch, you have two main options:
Option 1: Change the Handler Input Type to ALB’s Event
For Java, for example:
Before (expects API Gateway v2):
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
public class LambdaRequestHandler implements RequestHandler<APIGatewayV2HTTPEvent, LambdaResponse> {
@Override
public LambdaResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
// Your existing logic
}
}
After (expects ALB event):
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent;
public class LambdaRequestHandler implements RequestHandler<ApplicationLoadBalancerRequestEvent, LambdaResponse> {
@Override
public LambdaResponse handleRequest(ApplicationLoadBalancerRequestEvent event, Context context) {
// Adapt your logic to read from ALB's event shape:
// event.getPath(), event.getHttpMethod(), event.getHeaders(), event.getBody(), etc.
}
}
This makes the function compatible with ALB’s event structure.
Option 2: Use an Adapter Layer
If you need to support multiple event sources (e.g., API Gateway v2 and ALB) with the same business logic:
- Accept the raw event (or use a common internal model).
- Detect which event type you received (ALB vs API Gateway v2).
- Map it into a unified internal request object.
- Pass that object to your core handler logic.
Conceptually:
public class LambdaEntryPoint implements RequestHandler<Map<String, Object>, LambdaResponse> {
@Override
public LambdaResponse handleRequest(Map<String, Object> rawEvent, Context context) {
InternalRequest request = EventAdapter.toInternalRequest(rawEvent);
return CoreHandler.handle(request);
}
}
This pattern decouples your core business logic from AWS-specific event shapes.
Quick Diagnosis Checklist for Lambda + ALB 503 Errors
| Step | Action |
|---|---|
| Function Health | Test Lambda directly |
| Permissions | Confirm ALB invoke policy |
| Routing Rules | Path & host header check |
| Event Format | Compare working vs failing integration |
| Logs | Check for NPE or other stack traces |
Common Gotchas:
- Ignoring ALB event format.
- Assuming Lambda target health status matters for 503s.
- Missing
Hostheader or path mismatch. - Testing with the wrong event data in the console.
- Handler tightly coupled to one event type (e.g., API Gateway v2 only).
Key Takeaways
- Lambda + ALB 503 errors can also happen due to event format mismatch, not just networking or permissions.
- The handler signature dictates the expected input. If it expects
APIGatewayV2HTTPEvent, it won’t magically understand ALB’s event format. - Function URLs and API Gateway v2 share the same event format, so a handler that works there may still fail behind an ALB.
-
ALB sends a different JSON structure (e.g.,
requestContext.elb, different path fields), which can break your code if you assume API Gateway-style fields. - CloudWatch logs reveal stack traces and NPEs — always check them first when debugging 503s.
Top comments (0)