NLBのアクセスログがCloudWatch Logsに直接転送可能になりました
2025年11月12日、Network Load Balancer (NLB) のアクセスログにアップデートがありました。
これまでNLBのアクセスログといえば「S3に出力して、数分待ってからAthenaで分析」というのが定石でした。今回のアップデートにより、CloudFrontの標準アクセスログv2と同様、CloudWatch Logs、S3、Kinesis Data Firehose への直接配信がサポートされました。
Amazon CloudWatch supports logs for Network Load Balancer access logs | AWS What's New
これにより、CloudWatch Logs の Live Tail を使ったリアルタイムなログ確認や、CloudWatch Logs Insights を用いた高速なクエリ処理、また、JSON形式による構造化ログの利用などが可能になります。
今回はこの新機能(Logs Delivery API)を使って、CloudWatch Logs (JSON/TSV)、S3、Kinesis Data Firehose、4つの配信先にログを配信するテスト環境をCloudFormationで構築し、その挙動と試す機会がありましたので、紹介します。
検証環境
デリバリーソースごとの最大配信数は10個までを設定できる仕様を利用し、以下のフォーマットを同時に出力して検証しました。
- CloudWatch Logs (JSON)
- 推奨設定。構造化ログとして出力されるため、CloudWatch Logs Insights で検索する際に、パースが省略できます。
- CloudWatch Logs (Plain)
- 従来のTSV(タブ区切り)形式です。既存のログ監視基盤を変更せずに移行したい場合に利用します。
- Amazon S3 (Plain)
- 従来のアクセスログ互換形式(GZIP圧縮)です。
- Amazon S3 (JSON)
- 新形式。JSONL(改行区切りJSON)形式で保存されます。
- Amazon S3 (W3C)
- 新形式。W3C Extended Log File Format です。
- Amazon S3 (Parquet)
- 新形式。Apache Parquet 形式で保存されます。Athena でのスキャン料金削減やパフォーマンス向上が期待できます。
- Amazon Kinesis Data Firehose
- Splunk や Datadog などのサードパーティ製品への転送や、データレイク格納の前処理(ETL)に使用します。
構成図

CloudFormationテンプレート
検証に使用したテンプレートです。
このテンプレートをデプロイすると、NLBとテスト用EC2に加え、上記7パターンのログ配信設定が自動的にプロビジョニングされます。
AWS::Logs::Delivery は、並列実行に起因するエラーを回避するため、DependsOn でシーケンシャル処理にしています。
TSVDelivery:
Type: AWS::Logs::Delivery
DependsOn: JSONDelivery
Properties:
DeliverySourceName: !Ref DeliverySource
DeliveryDestinationArn: !GetAtt TSVDeliveryDestination.Arn
S3DeliveryJSON:
Type: AWS::Logs::Delivery
DependsOn: TSVDelivery
Properties:
DeliverySourceName: !Ref DeliverySource
DeliveryDestinationArn: !GetAtt S3DeliveryDestinationJSON.Arn

検証環境作成テンプレート (クリックして展開)
- TLSリスナーを使用するため、事前にACMで証明書を作成、パラメータで証明書のARNを指定してください。
- Default VPC環境での動作を想定しています。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'NLB with all logging destinations (CloudWatch JSON/TSV, S3 x4 formats, Firehose) - Default VPC'
Parameters:
CertificateArn:
Type: String
Description: ARN of the ACM Certificate for HTTPS listener
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC ID for resources
Subnet1:
Type: AWS::EC2::Subnet::Id
Description: First subnet for NLB
Subnet2:
Type: AWS::EC2::Subnet::Id
Description: Second subnet for NLB
Resources:
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP traffic
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
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
TestEC2:
Type: AWS::EC2::Instance
Properties:
ImageId: !Sub '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64}}'
InstanceType: t4g.nano
IamInstanceProfile: !Ref EC2InstanceProfile
SecurityGroupIds:
- !Ref EC2SecurityGroup
UserData:
Fn::Base64: |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>NLB Logging Test - $(hostname)</h1>" > /var/www/html/index.html
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub '${AWS::StackName}-tg'
Port: 80
Protocol: TCP
VpcId: !Ref VpcId
HealthCheckEnabled: true
HealthCheckProtocol: TCP
Targets:
- Id: !Ref TestEC2
Port: 80
NetworkLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub '${AWS::StackName}-nlb'
Type: network
Scheme: internet-facing
Subnets:
- !Ref Subnet1
- !Ref Subnet2
TLSListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
LoadBalancerArn: !Ref NetworkLoadBalancer
Port: 443
Protocol: TLS
Certificates:
- CertificateArn: !Ref CertificateArn
SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06
DeliverySource:
Type: AWS::Logs::DeliverySource
Properties:
Name: !Sub '${AWS::StackName}-source'
ResourceArn: !Ref NetworkLoadBalancer
LogType: NLB_ACCESS_LOGS
NLBLogGroupPolicy:
Type: AWS::Logs::ResourcePolicy
Properties:
PolicyName: !Sub '${AWS::StackName}-policy'
PolicyDocument: !Sub |
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowLogDelivery",
"Effect": "Allow",
"Principal": {
"Service": "delivery.logs.amazonaws.com"
},
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"${JSONLogGroup.Arn}",
"${TSVLogGroup.Arn}"
],
"Condition": {
"StringEquals": {
"aws:SourceAccount": "${AWS::AccountId}"
}
}
}
]
}
# 1. CloudWatch Logs (JSON)
JSONLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/vendedlogs/nlb/${AWS::StackName}/json'
RetentionInDays: 3
JSONDeliveryDestination:
Type: AWS::Logs::DeliveryDestination
Properties:
Name: !Sub '${AWS::StackName}-json'
OutputFormat: json
DestinationResourceArn: !GetAtt JSONLogGroup.Arn
JSONDelivery:
Type: AWS::Logs::Delivery
Properties:
DeliverySourceName: !Ref DeliverySource
DeliveryDestinationArn: !GetAtt JSONDeliveryDestination.Arn
# 2. CloudWatch Logs (TSV)
TSVLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/vendedlogs/nlb/${AWS::StackName}/tsv'
RetentionInDays: 3
TSVDeliveryDestination:
Type: AWS::Logs::DeliveryDestination
Properties:
Name: !Sub '${AWS::StackName}-tsv'
OutputFormat: plain
DestinationResourceArn: !GetAtt TSVLogGroup.Arn
TSVDelivery:
Type: AWS::Logs::Delivery
DependsOn: JSONDelivery
Properties:
DeliverySourceName: !Ref DeliverySource
DeliveryDestinationArn: !GetAtt TSVDeliveryDestination.Arn
S3LogBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'nlb-logs-${AWS::StackName}-${AWS::AccountId}'
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
LifecycleConfiguration:
Rules:
- Id: DeleteOldLogs
Status: Enabled
ExpirationInDays: 7
S3LogBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3LogBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AWSLogDeliveryWrite
Effect: Allow
Principal:
Service: delivery.logs.amazonaws.com
Action: s3:PutObject
Resource: !Sub '${S3LogBucket.Arn}/*'
Condition:
StringEquals:
s3:x-amz-acl: bucket-owner-full-control
aws:SourceAccount: !Ref AWS::AccountId
- Sid: AWSLogDeliveryAclCheck
Effect: Allow
Principal:
Service: delivery.logs.amazonaws.com
Action: s3:GetBucketAcl
Resource: !GetAtt S3LogBucket.Arn
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
# 3. S3 (JSON)
S3DeliveryDestinationJSON:
Type: AWS::Logs::DeliveryDestination
DependsOn: S3LogBucketPolicy
Properties:
Name: !Sub '${AWS::StackName}-s3-json'
OutputFormat: json
DestinationResourceArn: !Sub '${S3LogBucket.Arn}/json'
S3DeliveryJSON:
Type: AWS::Logs::Delivery
DependsOn: TSVDelivery
Properties:
DeliverySourceName: !Ref DeliverySource
DeliveryDestinationArn: !GetAtt S3DeliveryDestinationJSON.Arn
# 4. S3 (W3C)
S3DeliveryDestinationW3C:
Type: AWS::Logs::DeliveryDestination
DependsOn: S3LogBucketPolicy
Properties:
Name: !Sub '${AWS::StackName}-s3-w3c'
OutputFormat: w3c
DestinationResourceArn: !Sub '${S3LogBucket.Arn}/w3c'
S3DeliveryW3C:
Type: AWS::Logs::Delivery
DependsOn: S3DeliveryJSON
Properties:
DeliverySourceName: !Ref DeliverySource
DeliveryDestinationArn: !GetAtt S3DeliveryDestinationW3C.Arn
# 5. S3 (Parquet)
S3DeliveryDestinationParquet:
Type: AWS::Logs::DeliveryDestination
DependsOn: S3LogBucketPolicy
Properties:
Name: !Sub '${AWS::StackName}-s3-parquet'
OutputFormat: parquet
DestinationResourceArn: !Sub '${S3LogBucket.Arn}/parquet'
S3DeliveryParquet:
Type: AWS::Logs::Delivery
DependsOn: S3DeliveryW3C
Properties:
DeliverySourceName: !Ref DeliverySource
DeliveryDestinationArn: !GetAtt S3DeliveryDestinationParquet.Arn
# 6. S3 (Plain)
S3DeliveryDestinationPlain:
Type: AWS::Logs::DeliveryDestination
DependsOn: S3LogBucketPolicy
Properties:
Name: !Sub '${AWS::StackName}-s3-plain'
OutputFormat: plain
DestinationResourceArn: !Sub '${S3LogBucket.Arn}/plain'
S3DeliveryPlain:
Type: AWS::Logs::Delivery
DependsOn: S3DeliveryParquet
Properties:
DeliverySourceName: !Ref DeliverySource
DeliveryDestinationArn: !GetAtt S3DeliveryDestinationPlain.Arn
# 7. Firehose
FirehoseRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: firehose.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: FirehoseS3Policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:ListBucket
Resource:
- !GetAtt S3LogBucket.Arn
- !Sub '${S3LogBucket.Arn}/*'
- Effect: Allow
Action:
- logs:PutLogEvents
Resource: '*'
FirehoseLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/kinesisfirehose/${AWS::StackName}'
RetentionInDays: 1
FirehoseLogStream:
Type: AWS::Logs::LogStream
Properties:
LogGroupName: !Ref FirehoseLogGroup
LogStreamName: S3Delivery
DeliveryStream:
Type: AWS::KinesisFirehose::DeliveryStream
Properties:
DeliveryStreamName: !Sub '${AWS::StackName}-firehose'
DeliveryStreamType: DirectPut
S3DestinationConfiguration:
BucketARN: !GetAtt S3LogBucket.Arn
Prefix: firehose/
ErrorOutputPrefix: firehose-errors/
RoleARN: !GetAtt FirehoseRole.Arn
BufferingHints:
IntervalInSeconds: 60
SizeInMBs: 1
CompressionFormat: GZIP
CloudWatchLoggingOptions:
Enabled: true
LogGroupName: !Ref FirehoseLogGroup
LogStreamName: !Ref FirehoseLogStream
FirehoseDeliveryDestination:
Type: AWS::Logs::DeliveryDestination
Properties:
Name: !Sub '${AWS::StackName}-firehose'
OutputFormat: json
DestinationResourceArn: !GetAtt DeliveryStream.Arn
FirehoseDelivery:
Type: AWS::Logs::Delivery
DependsOn: S3DeliveryPlain
Properties:
DeliverySourceName: !Ref DeliverySource
DeliveryDestinationArn: !GetAtt FirehoseDeliveryDestination.Arn
Outputs:
NLBDNSName:
Description: NLB DNS Name
Value: !GetAtt NetworkLoadBalancer.DNSName
TestCommand:
Description: Command to generate traffic
Value: !Sub 'curl -k https://${NetworkLoadBalancer.DNSName}'
JSONLogGroup:
Description: CloudWatch Logs (JSON)
Value: !Ref JSONLogGroup
TSVLogGroup:
Description: CloudWatch Logs (TSV)
Value: !Ref TSVLogGroup
S3Bucket:
Description: S3 Bucket (json/, w3c/, parquet/, plain/ prefixes)
Value: !Ref S3LogBucket
FirehoseStream:
Description: Kinesis Data Firehose
Value: !Ref DeliveryStream
動作確認
構築した CloudFormation スタックを使用して、NLB の新しいログ配信機能を検証しました。
デプロイ後、NLB コンソールの「統合」タブより、設定された各ログ配信先(CloudWatch Logs, S3, Firehose)が確認できます。

トラフィック生成
検証用クライアントから NLB に対して curl でリクエストを送信し、ログの出力を確認しました。
for i in {1..10}; do curl -k -s https://nlb-test.elb.ap-northeast-1.amazonaws.com && echo " - Request $i"; sleep 1; done
1. CloudWatch Logs への配信
JSON 形式
出力形式を省略(または json 指定)した場合、ログは構造化された JSON 形式で出力されます。
これにより、CloudWatch Logs Insights でクエリする際に複雑な parse コマンドを記述する必要がなくなり、分析の効率が格段に向上します。
{
"id": "nlb/app/net/nlb-test/xxxxxxxxxxxxxxxx",
"timestamp": "2025-11-24T08:00:00.123Z",
"version": "1.0",
"type": "tcp",
"listener_arn": "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:listener/net/nlb-test/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx",
"client_ip": "203.0.113.10",
"client_port": 54321,
"target_ip": "10.0.1.50",
"target_port": 80,
"connection_status": "success",
"bytes_received": 150,
"bytes_sent": 500
}
Plain 形式 (TSV互換)
出力形式を plain、フィールド区切り文字を \t (タブ) と設定することで、従来のスペース区切り(TSV互換)のログとして出力されます。
新機能で出力された TSV ログと、従来の S3 出力(レガシー方式)のログを比較したところ、フィールド数や順序の一致が確認できました。既存のログ解析基盤への影響を最小限に抑えたい場合に有用です。
現在の TSV ログ (フィールド分解)
1: tls
2: 2.0
3: 2025-11-23T18:23:09
4: net/nlb-auto-tokyo/xxxxxxxxxxxxxxxx
5: xxxxxxxxxxxxxxxx
6: 203.0.113.10:57510
7: 172.31.xx.xx:443
8: 752
9: 379
10: 238
...
参考: レガシーログ (フィールド分解)
1: tls
2: 2.0
3: 2020-04-01T08:51:42
4: net/my-network-loadbalancer/xxxxxxxxxxxxxxxx
5: xxxxxxxxxxxxxxxx
6: 72.21.xx.xx:51341
7: 172.100.xx.xx:443
...
2. Amazon S3 への配信
S3 への配信では、バケット内で指定した Prefix ごとに異なるフォーマットで出力確認を行いました。
Plain 形式
従来のアクセスログと同形式のログが、GZIP 圧縮形式で出力されました。
S3 URI
s3://nlb-logs-123456789012/plain/AWSLogs/.../xxxxxxxx.log.gz
ログサンプル
tls 2.0 2025-11-24T07:00:20 net/nlb-test/xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx ...
JSON 形式
改行区切りの「JSONL」形式で、GZIP 圧縮された状態で保存されます。
各行の JSON 構造は CloudWatch Logs に保存された内容と同等でした。
S3 URI
s3://nlb-logs-123456789012/json/AWSLogs/.../xxxxxxxx.log.gz
W3C 形式
今回の検証設定では、タブ区切りを指定した plain 指定と同じ内容が出力されることを確認しました。
S3 URI
s3://nlb-logs-123456789012/w3c/AWSLogs/.../xxxxxxxx.log.gz
Parquet 形式
Apache Parquet 形式で出力されます。S3 Select を利用してクエリを実行したところ、JSON 設定の内容と同等の構造化データが取得できました。
Athena 等、Parquet 形式をサポートする分析ツールでのコスト削減やパフォーマンス向上が期待できます。
S3 URI
s3://nlb-logs-123456789012/parquet/AWSLogs/.../xxxxxxxx.log.parquet
S3 Select クエリ結果サンプル
{
"type": "tls",
"version": "2.0",
"time": "2025-11-24T07:00:20",
"elb": "net/nlb-test/xxxxxxxxxxxxxxxx",
...
}
3. Kinesis Data Firehose への配信
Firehose はデフォルト設定で利用しました。S3 出力先には、改行区切りの JSON (JSONL) が GZIP 圧縮されたファイルとして保存されていることが確認できました。これにより、Splunk や Datadog 等のサードパーティ監視基盤へのリアルタイムログ転送が容易になります。
S3 URI
s3://nlb-logs-123456789012/firehose/2025/11/24/07/nlb-test-firehose-1-2025-11-24-07-01-04-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.gz
アクセスログの長期保管に加え、保持するログのフィルタリングや、ログ項目の抽出、保存形式の最適化、パーティション管理など ETL 処理が必要な場合、Firehose をご検討ください。
注意点
新方式(Logs Delivery API)を利用する場合、出力先によってログ配信料(Vended Logs 料金)の有無が異なります。
S3 への配信は(Parquet変換を行わない限り)無料ですが、CloudWatch Logs や Firehose へ配信する場合は従量課金が発生する点にご注意ください。
「レガシー設定」のユースケース
「ログはS3にあれば十分、JSONやParquet形式への変換も不要」という場合や、CloudFormationの記述をシンプルに済ませたい場合には、従来の「レガシー設定」も依然として有効な選択肢です。
NetworkLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
# ...
LoadBalancerAttributes:
# 従来の設定(配信料無料)
- Key: access_logs.s3.enabled
Value: 'true'
- Key: access_logs.s3.bucket
Value: !Ref LogsBucket
まとめ
NLBのログがCloudWatch Logsに来るようになったことで、障害時の初動調査(Live Tailでのエラー確認など)が劇的に楽になる事が期待できます。
平時の利用は S3へのログ出力のみ、任意の出力形式で有効化。
ログのリアルタイム調査などが必要となる期間限定で、CloudWatch Logs の出力を有効にする事で、費用対効果の良い利用ができると思われます。
コストと利便性のバランスを考慮して、要件に合った適切なログ出力先を選択してご活用ください。







