I checked the health check logs supported by ALB by configuring them with CloudFormation
This page has been translated by machine translation. View original
On November 21, 2025, there was an upgrade that made "health check logs" available for Application Load Balancer (ALB).
Have you ever struggled to investigate why an ALB target became "Unhealthy"? Even when trying to check the target application logs, there were often difficult cases where requests weren't reaching the target, or the instance had already been terminated by Auto Scaling, making root cause identification challenging.
This time, I had the opportunity to build a test environment using CloudFormation and check the health check logs output to S3, which I'd like to share with you.
Test Environment
I created an ALB with health check logging enabled. For targets, I set up 2 EC2 instances, with one intentionally configured to fail health checks (web server not started).
Architecture Diagram

S3 Bucket Policy
I used a bucket policy to allow ALB logs to be written.
LogsBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref LogsBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AWSLogDeliveryWrite
Effect: Allow
Principal:
Service: logdelivery.elasticloadbalancing.amazonaws.com
Action: s3:PutObject
Resource:
# Permission for access logs path
- !Sub '${LogsBucket.Arn}/access-logs/AWSLogs/${AWS::AccountId}/*'
# Permission for health check logs path
- !Sub '${LogsBucket.Arn}/health-check-logs/AWSLogs/${AWS::AccountId}/*'
ALB
I added the new health check log configuration (health_check_logs) in LoadBalancerAttributes.
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
LoadBalancerAttributes:
# --- Health Check Logs Settings ---
- Key: health_check_logs.s3.enabled
Value: 'true'
- Key: health_check_logs.s3.bucket
Value: !Ref LogsBucket
- Key: health_check_logs.s3.prefix
Value: 'health-check-logs'
CloudFormation
Test Environment Creation Template
AWSTemplateFormatVersion: '2010-09-09'
Description: 'ALB Access & Health Check Logs (Separated Prefixes) Test Environment'
Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64
VpcId:
Type: AWS::EC2::VPC::Id
Description: Default VPC ID
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: At least 2 public subnets in different AZs
Resources:
# S3 Bucket for Logs
LogsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'alb-logs-${AWS::AccountId}-${AWS::Region}'
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# S3 Bucket Policy
LogsBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref LogsBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AWSLogDeliveryWrite
Effect: Allow
Principal:
Service: logdelivery.elasticloadbalancing.amazonaws.com
Action: s3:PutObject
Resource:
# アクセスログ用のパス許可
- !Sub '${LogsBucket.Arn}/access-logs/AWSLogs/${AWS::AccountId}/*'
# ヘルスチェックログ用のパス許可
- !Sub '${LogsBucket.Arn}/health-check-logs/AWSLogs/${AWS::AccountId}/*'
# Security Group for ALB
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for ALB
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
# Security Group for EC2
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 instances
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ALBSecurityGroup
# IAM Role for EC2
EC2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref EC2Role
# EC2 Instance 1 (Healthy)
EC2Instance1:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t4g.nano
IamInstanceProfile: !Ref EC2InstanceProfile
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: '0'
GroupSet:
- !Ref EC2SecurityGroup
SubnetId: !Select [0, !Ref SubnetIds]
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Healthy Instance 1 (ARM)</h1>" > /var/www/html/index.html
Tags:
- Key: Name
Value: ALB-Target-Healthy
# EC2 Instance 2 (Unhealthy)
EC2Instance2:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t4g.nano
IamInstanceProfile: !Ref EC2InstanceProfile
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: '0'
GroupSet:
- !Ref EC2SecurityGroup
SubnetId: !Select [1, !Ref SubnetIds]
Tags:
- Key: Name
Value: ALB-Target-Unhealthy
# Target Group
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: alb-logs-test-tg
Port: 80
Protocol: HTTP
VpcId: !Ref VpcId
HealthCheckEnabled: true
HealthCheckIntervalSeconds: 30
HealthCheckPath: /
HealthCheckProtocol: HTTP
Targets:
- Id: !Ref EC2Instance1
- Id: !Ref EC2Instance2
# Application Load Balancer
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
DependsOn: LogsBucketPolicy
Properties:
Name: alb-logs-test
Type: application
Scheme: internet-facing
SecurityGroups:
- !Ref ALBSecurityGroup
Subnets: !Ref SubnetIds
LoadBalancerAttributes:
# --- Access Logs Settings ---
- Key: access_logs.s3.enabled
Value: 'true'
- Key: access_logs.s3.bucket
Value: !Ref LogsBucket
- Key: access_logs.s3.prefix
Value: 'access-logs' # フォルダ分け: access-logs/
# --- Health Check Logs Settings ---
- Key: health_check_logs.s3.enabled
Value: 'true'
- Key: health_check_logs.s3.bucket
Value: !Ref LogsBucket
- Key: health_check_logs.s3.prefix
Value: 'health-check-logs' # フォルダ分け: health-check-logs/
# Listener
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
Outputs:
ALBDNSName:
Description: ALB DNS Name
Value: !GetAtt ApplicationLoadBalancer.DNSName
S3BucketName:
Description: S3 Bucket for Logs
Value: !Ref LogsBucket
Operational Check
After deployment, I confirmed connectivity to the ALB.
# Set ALB DNS name as a variable
ALB_DNS="<alb-name>-<random-id>.<region>.elb.amazonaws.com"
# Check connectivity
curl http://${ALB_DNS}
Confirming Health Check Log Output
After waiting a few minutes, I checked the S3 destination for health check logs.
Logs were generated under the health-check-logs prefix.
BUCKET_NAME="alb-logs-<account-id>-<region>"
aws s3 ls s3://${BUCKET_NAME}/health-check-logs/
Hierarchy image
s3://alb-logs-123456789012-ap-northeast-1/
├── access-logs/AWSLogs/123456789012/elasticloadbalancing/ap-northeast-1/2025/11/22/
└── health-check-logs/AWSLogs/123456789012/elasticloadbalancing/ap-northeast-1/2025/11/22/
※Both health check logs and traditional access logs are output to the same S3 bucket with different prefixes.
I confirmed that health check logs were output in the filename format health_check_log_<account-id>_elasticloadbalancing_<region>_<load-balancer-name>.<load-balancer-id>_<timestamp>_<ip-address>_<random-string>.log.gz.
2025-11-22 15:35:08 362 health-check-logs/AWSLogs/<account-id>/elasticloadbalancing/<region>/2025/11/22/health_check_log_<account-id>_elasticloadbalancing_<region>_app.<alb-name>.<alb-id>_20251122T1535Z_<ip-1>_<random>.log.gz
2025-11-22 15:35:08 358 health-check-logs/AWSLogs/<account-id>/elasticloadbalancing/<region>/2025/11/22/health_check_log_<account-id>_elasticloadbalancing_<region>_app.<alb-name>.<alb-id>_20251122T1535Z_<ip-2>_<random>.log.gz
2025-11-22 15:40:08 357 health-check-logs/AWSLogs/<account-id>/elasticloadbalancing/<region>/2025/11/22/health_check_log_<account-id>_elasticloadbalancing_<region>_app.<alb-name>.<alb-id>_20251122T1540Z_<ip-1>_<random>.log.gz
2025-11-22 15:40:08 357 health-check-logs/AWSLogs/<account-id>/elasticloadbalancing/<region>/2025/11/22/health_check_log_<account-id>_elasticloadbalancing_<region>_app.<alb-name>.<alb-id>_20251122T1540Z_<ip-2>_<random>.log.gz
I downloaded and checked the content of the log files.
# Download and extract log
aws s3 cp s3://<bucket-name>/path/to/your/log.gz .
gzip -d log.gz
cat log
The health check results (success and failure) for both EC2 instances were recorded.
> download: s3://<bucket-name>/health-check-logs/AWSLogs/<account-id>/elasticloadbalancing/<region>/YYYY/MM/DD/health_check_log_<account-id>_elasticloadbalancing_<region>_<alb-name>.<alb-id>_<timestamp>_<ip>_<random>.log.gz to /tmp/hc.log.gz
http 2025-11-22T15:35:10.504741Z 0.001469625 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:35:10.504211Z 0.002958522 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
http 2025-11-22T15:35:40.535012Z 0.001567201 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:35:40.534548Z 0.002960985 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
http 2025-11-22T15:36:10.547468Z 0.00154518 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:36:10.547032Z 0.002790372 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
http 2025-11-22T15:36:40.578086Z 0.001601312 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:36:40.577634Z 0.002930846 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
http 2025-11-22T15:37:10.608677Z 0.001456432 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:37:10.608229Z 0.00288121 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
http 2025-11-22T15:37:40.635578Z 0.001517185 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:37:40.635025Z 0.003032974 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
http 2025-11-22T15:38:10.666143Z 0.001580969 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:38:10.665657Z 0.003204206 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
http 2025-11-22T15:38:40.695716Z 0.001406524 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:38:40.695266Z 0.00285128 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
http 2025-11-22T15:39:10.716992Z 0.001626345 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:39:10.716548Z 0.002873815 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
http 2025-11-22T15:39:40.739579Z 0.001600648 172.31.x.x:80 <target-group-name> PASS 200 -
http 2025-11-22T15:39:40.739106Z 0.00284948 172.31.x.x:80 <target-group-name> FAIL 502 TargetError
Log format
| Item | Value | Description |
|---|---|---|
| Protocol | http | Health check protocol |
| Timestamp | 2025-11-22T... | Execution time |
| Processing time | 0.001469625 | Health check response time |
| Target | 172.31.x.x:80 | Target IP address and port |
| Target Group | alb-logs-test-tg | Target group name |
| Status | PASS | Health check result (PASS/FAIL) |
| HTTP Code | 200 | Response code from target |
| Reason | - | Failure reason (hyphen when successful) |
Summary
Previously, when ALB health checks failed and targets became "Unhealthy," investigations relied on target-side application logs or access logs. However, in cases where resources were exhausted preventing log creation, or instances were replaced by Auto Scaling, it was often difficult to find clues.
With the newly supported health check logs, we can now track the "root cause of failure" from the ALB's perspective in detail. In this test, the TargetError (502) due to the stopped web server was clearly recorded, allowing us to immediately determine that the problem was with the instance's internal components (or application response) rather than the network path.
These health check logs can be valuable for environments with concerns about network connectivity to targets, or for critical workloads where downtime is not allowed and detailed investigation for prevention is required. Please give it a try.
