I Tried Configuring and Checking the Health Check Logs Now Supported by ALB Using CloudFormation

I Tried Configuring and Checking the Health Check Logs Now Supported by ALB Using CloudFormation

Testing the ALB health check logs feature added in the November 2025 update. I built an environment with CloudFormation that stores access logs and health check logs separately, then examined the actual health check logs output to S3.
2025.11.23

This page has been translated by machine translation. View original

On November 21, 2025, an upgrade was released that makes "health check logs" available for the Application Load Balancer (ALB).

https://aws.amazon.com/jp/about-aws/whats-new/2025/11/application-load-balancer-health-check-logs/

Have you ever struggled to investigate the cause when an ALB target became "Unhealthy"? Even when trying to check the application logs on the target side, it was often difficult to identify the cause because requests were not reaching the target, or the instance had already been terminated by Auto Scaling.

This time, I had the opportunity to build a test environment using CloudFormation and check the health check logs actually output to S3, so I'd like to share it with you.

Test Environment

I created an ALB with health check logs enabled. I prepared two EC2 instances as targets, and configured one of them to intentionally fail the health check (web server not running).

Architecture Diagram

AWS architecture diagram

S3 Bucket Policy

I used a bucket policy to allow ALB to write logs.

  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:
              # Allow path for access logs
              - !Sub '${LogsBucket.Arn}/access-logs/AWSLogs/${AWS::AccountId}/*'
              # Allow path for health check logs
              - !Sub '${LogsBucket.Arn}/health-check-logs/AWSLogs/${AWS::AccountId}/*'

https://dev.classmethod.jp/articles/alb-access-log-s3-bucket-policy-tips/

ALB

In LoadBalancerAttributes, I added the health check log setting (health_check_logs), which is the new feature this time.

  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

Template for creating the test environment

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

Verification

After deployment, I confirmed connectivity to the ALB.

# Set the ALB DNS name to a variable
ALB_DNS="<alb-name>-<random-id>.<region>.elb.amazonaws.com"
# Connectivity check
curl http://${ALB_DNS}

Checking the Health Check Log Output

After waiting a few minutes, I checked the S3 output destination of the health check logs.
Logs had been generated under the health-check-logs prefix.

BUCKET_NAME="alb-logs-<account-id>-<region>"
aws s3 ls s3://${BUCKET_NAME}/health-check-logs/ 

Hierarchy overview

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 the health check logs and the conventional access logs are output to the same S3 bucket with different prefixes.

I was able to confirm that the health check logs were output in the file name 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 the log file and checked its contents.

# Download and decompress the 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 the two 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

Field Value Description
Protocol http Health check protocol
Timestamp 2025-11-22T... Execution date and 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 Name of the target group it belongs to
Status PASS Health check result (PASS/FAIL)
HTTP code 200 Response code from the target
Reason - Failure reason (hyphen on success)

Summary

Until now, when an ALB health check failed and a target became "Unhealthy", the investigation depended on application logs and access logs on the target side. However, when logs could not be recorded due to resource exhaustion, or the instance itself was replaced by Auto Scaling or the like, it was sometimes difficult to obtain any clues.

With the newly supported health check logs, it is now possible to trace the "root cause of failure" in detail as seen from the ALB side. In this test as well, a TargetError (502) caused by the web server being stopped was clearly recorded, and I could immediately determine that the problem was not in the network path but inside the instance (or the application response).

In environments where there are concerns about network connectivity to targets, or for critical workloads where downtime is unacceptable and a detailed investigation to prevent recurrence is required, these health check logs may be useful. Please give them a try.


AWS re:Invent 2025まとめ情報

クラスメソッドは2025/12/1〜5にラスベガスで開催されたAWSカンファレンスイベント「re:Invent」を今年も特集しました。ぜひ一度ご覧ください。 re:Invent 2025ポータルサイトを見るAWS re:Inventはこちら

Share this article

AWSのお困り事はクラスメソッドへ