3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS CDKでNext.jsを本番構成にする【第3回】〜ACM証明書でALBをHTTPS化する〜

3
Last updated at Posted at 2026-03-23

はじめに

HTTPのままでもアプリにアクセスすることはできますが、
この状態では通信が暗号化されておらず、安全とは言えません。

実運用では、HTTPS化(SSL/TLSの適用)は必須となります。

本記事では、AWS Certificate Manager(ACM)で証明書を発行し、
ALBに設定することで、https://app.example.com でアクセスできるようにします。

本シリーズの構成

1. VPC編
2. Route53 独自ドメイン編
3. ACM HTTPS化編(本記事)
4. WAFセキュリティ編
5. ECS Auto Scaling編
6. CI/CD編

今回のゴール

  • 独自ドメインでHTTPSアクセスできるようにする
  • HTTPアクセスをHTTPSに統一する

HTTPS化の構成

※ この図はHTTPS化の通信フローに絞った簡略図です。実際の本番構成では、ALBは複数AZのPublic Subnet、ECSは複数AZのPrivate Subnetに配置します。

前提条件

  • Route53で独自ドメイン設定済み
  • 前回までのCDKコード(Network / ECS / ALB / Service)が作成済み
  • ALB経由でアプリへHTTPアクセスできる状態である

3-1. ACM証明書をCDKで作成する

ACM証明書は、アプリケーションとは独立したライフサイクルで管理するため、専用のスタック(CertificateStack)で管理します。

対象ファイル
lib/certificate-stack.ts

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as route53 from 'aws-cdk-lib/aws-route53';

interface CertificateStackProps extends cdk.StackProps {
  hostedZone: route53.IHostedZone;
}

export class CertificateStack extends cdk.Stack {
  public readonly certificate: acm.Certificate;

  constructor(scope: Construct, id: string, props: CertificateStackProps) {
    super(scope, id, props);

    this.certificate = new acm.Certificate(this, 'AppCertificate', {
      domainName: 'app.example.com',
      validation: acm.CertificateValidation.fromDns(props.hostedZone),
    });
  }
}

3-2. ALBの設定を更新する

ACMで作成した証明書を利用し、ALBでHTTPS通信を受け付けるように設定します。
あわせて、HTTPアクセスはHTTPSへリダイレクトするようにします。

対象ファイル
lib/alb-stack.ts

■ Propsの追加

証明書とホストゾーンを外部から受け取れるようにします。

import * as acm from 'aws-cdk-lib/aws-certificatemanager';

interface AlbStackProps extends cdk.StackProps {
  vpc: ec2.IVpc;
  hostedZone: route53.IHostedZone;
  certificate: acm.ICertificate;
}

■ HTTPS(443)を追加で許可する

既存のHTTP(80)に加えて、
HTTPS(443)へのアクセスを許可します。

this.albSecurityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(443),
  'Allow HTTPS from Internet'
);

■ リスナー設定

HTTPはHTTPSへリダイレクトします。
HTTPSでは証明書を用いて通信を暗号化し、リクエストをECSへ転送します。

// HTTP → HTTPSリダイレクト
alb.addListener('HttpListener', {
  port: 80,
  defaultAction: elbv2.ListenerAction.redirect({
    protocol: 'HTTPS',
    port: '443',
    permanent: true,
  }),
});

// HTTPSリスナー
alb.addListener('HttpsListener', {
  port: 443,
  certificates: [props.certificate],
  defaultTargetGroups: [this.targetGroup],
});

■ ホストゾーンの参照方法を変更する

ホストゾーンはbin側から受け取ったものを使用します。

new route53.ARecord(this, 'AppDomainRecord', {
  zone: props.hostedZone,
  recordName: 'app',
  target: route53.RecordTarget.fromAlias(
    new targets.LoadBalancerTarget(alb)
  ),
});

これにより、ALBはHTTPS公開に対応し、次の構成になります。

  • HTTPS通信を受け付ける
  • HTTPアクセスをHTTPSへ統一する
  • 独自ドメインで安全にアクセスできる

3-3. CDKの構成を接続する

bin側でホストゾーンを一度だけ参照し、CertificateStackAlbStackに渡します。

また、CertificateStackで作成した証明書をAlbStackへ渡して利用します。

これにより、ホストゾーンや証明書といった共通リソースを一元管理でき、
スタック間の依存関係が明確になります。

対象ファイル
bin/cdk-nextjs-infra.ts
※ 本シリーズでは CDKプロジェクト名を cdk-nextjs-infra としています。

import * as route53 from 'aws-cdk-lib/aws-route53';
import { CertificateStack } from '../lib/certificate-stack';

const hostedZone = route53.HostedZone.fromLookup(networkStack, 'HostedZone', {
  domainName: 'example.com',
});

const certificateStack = new CertificateStack(app, 'NextjsInfraCertificateStack', {
  env,
  hostedZone,
});

new AlbStack(app, 'NextjsAlbStack', {
  env,
  vpc: networkStack.vpc,
  hostedZone,
  certificate: certificateStack.certificate,
});

const hostedZone = route53.HostedZone.fromLookup(networkStack, '...

HostedZone.fromLookup() は環境情報(account / region)をもとに解決されるため、env を持つStackスコープで実行するのが安全です。
そのため、ここではAppではなく既存のStack(networkStack)をスコープとして指定しています。

3-4. デプロイ

ServiceStackをデプロイします。

cdk deploy NextjsInfraServiceStack --profile <プロファイル名>

NextjsInfraServiceStack は他のスタックに依存しているため、
このコマンドで関連するスタックもあわせてデプロイされます。

本記事で作成したスタックに含まれるAWSリソースは、削除するまで料金が発生します。
検証が不要になった場合は、以下のコマンドでスタックを削除してください。

cdk destroy <スタック名> --profile <プロファイル名>

3-5. 動作確認

ブラウザで以下のURLへアクセスします。

https://app.example.com

確認ポイント

  • アドレスバーに 「この接続は保護されています」 と表示される
  • HTTPアクセスがHTTPSへリダイレクトされる

最終コード(今回追加・修正したファイル)

lib/certificate-stack.ts

コード全体
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as route53 from 'aws-cdk-lib/aws-route53';

interface CertificateStackProps extends cdk.StackProps {
  hostedZone: route53.IHostedZone;
}

export class CertificateStack extends cdk.Stack {
  public readonly certificate: acm.Certificate;

  constructor(scope: Construct, id: string, props: CertificateStackProps) {
    super(scope, id, props);

    this.certificate = new acm.Certificate(this, 'AppCertificate', {
      domainName: 'app.example.com',
      validation: acm.CertificateValidation.fromDns(props.hostedZone),
    });
  }
}

lib/alb-stack.ts

コード全体
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as route53 from 'aws-cdk-lib/aws-route53';
import * as targets from 'aws-cdk-lib/aws-route53-targets';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';

interface AlbStackProps extends cdk.StackProps {
  vpc: ec2.IVpc;
  hostedZone: route53.IHostedZone;
  certificate: acm.ICertificate;
}

export class AlbStack extends cdk.Stack {
  public readonly targetGroup: elbv2.ApplicationTargetGroup;
  public readonly albSecurityGroup: ec2.SecurityGroup;

  constructor(scope: Construct, id: string, props: AlbStackProps) {
    super(scope, id, props);

    this.albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
      vpc: props.vpc,
      allowAllOutbound: true,
      description: 'Security group for public ALB',
    });

    this.albSecurityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(80),
      'Allow HTTP from Internet'
    );

    this.albSecurityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(443),
      'Allow HTTPS from Internet'
    );

    const alb = new elbv2.ApplicationLoadBalancer(this, 'NextjsAlb', {
      vpc: props.vpc,
      internetFacing: true,
      securityGroup: this.albSecurityGroup,
    });

    this.targetGroup = new elbv2.ApplicationTargetGroup(this, 'NextjsTargetGroup', {
      vpc: props.vpc,
      port: 3000,
      protocol: elbv2.ApplicationProtocol.HTTP,
      targetType: elbv2.TargetType.IP,
      healthCheck: {
        path: '/',
        healthyHttpCodes: '200',
      },
    });

    alb.addListener('HttpListener', {
      port: 80,
      defaultAction: elbv2.ListenerAction.redirect({
        protocol: 'HTTPS',
        port: '443',
        permanent: true,
      }),
    });

    alb.addListener('HttpsListener', {
      port: 443,
      certificates: [props.certificate],
      defaultTargetGroups: [this.targetGroup],
    });

    new route53.ARecord(this, 'AppDomainRecord', {
      zone: props.hostedZone,
      recordName: 'app',
      target: route53.RecordTarget.fromAlias(
        new targets.LoadBalancerTarget(alb)
      ),
    });

    new cdk.CfnOutput(this, 'AlbDnsName', {
      value: alb.loadBalancerDnsName,
    });
  }
}

bin/cdk-nextjs-infra.ts

コード全体
import * as cdk from 'aws-cdk-lib';
import * as route53 from 'aws-cdk-lib/aws-route53';
import { EcrStack } from '../lib/ecr-stack';
import { NetworkStack } from '../lib/network-stack';
import { EcsStack } from '../lib/ecs-stack';
import { CertificateStack } from '../lib/certificate-stack';
import { AlbStack } from '../lib/alb-stack';
import { ServiceStack } from '../lib/service-stack';

const app = new cdk.App();

const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
};

new EcrStack(app, 'NextjsInfraEcrStack', {
  env,
});

const networkStack = new NetworkStack(app, 'NextjsInfraNetworkStack', {
  env,
});

const ecsStack = new EcsStack(app, 'NextjsEcsStack', {
  env,
  vpc: networkStack.vpc,
});

const hostedZone = route53.HostedZone.fromLookup(networkStack, 'HostedZone', {
  domainName: 'example.com', // 自分のドメインに置き換えてください
});

const certificateStack = new CertificateStack(app, 'NextjsInfraCertificateStack', {
  env,
  hostedZone,
});

const albStack = new AlbStack(app, 'NextjsInfraAlbStack', {
  env,
  vpc: networkStack.vpc,
  hostedZone,
  certificate: certificateStack.certificate,
});

new ServiceStack(app, 'NextjsInfraServiceStack', {
  env,
  cluster: ecsStack.cluster,
  taskDefinition: ecsStack.taskDefinition,
  targetGroup: albStack.targetGroup,
  vpc: networkStack.vpc,
  albSecurityGroup: albStack.albSecurityGroup,
});

ここまでの成果

これにより、ALBはHTTPS(443)でリクエストを受け付けられるようになり、
HTTP(80)へのアクセスもHTTPSへ自動リダイレクトされるようになりました。

その結果、独自ドメインで暗号化通信を行える構成が整いました。

次回

次回は「WAFをALBに適用してセキュリティを強化」します。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?