ACM エクスポート証明書を使った EC2 上の SSL 証明書自動更新

記事タイトルとURLをコピーする

こんにちは、マネージドサービス部 AWS サポート課の坂口です。

今回は AWS Certificate Manager(ACM)のエクスポート可能なパブリック証明書を利用し、EC2 インスタンス上の nginx で使用する証明書を自動更新する方法についてご紹介します。

はじめに

EC2 インスタンス上で SSL終端を行っている場合、SSL/TLS 証明書の管理と更新は運用上の大きな課題となります。
従来は Let's Encrypt などを使用して 90 日ごとに手動または自動更新を行う必要がありましたが、2025 年 6 月に ACM がエクスポート可能なパブリック証明書機能をリリースしたことで、より簡単に証明書を管理できるようになりました。

本記事では、ACM で発行した証明書を EC2 にデプロイし、証明書更新時に EventBridge と Lambda を使って自動的に EC2 の証明書を更新する構成を構築します。

アーキテクチャ概要

今回構築する構成は以下の通りです。

前提条件

・Route 53 でドメインを管理していること(DNS 検証用)
・EC2 インスタンスに SSM Agent がインストールされていること
・nginx がインストール済みであること

ステップ 1:エクスポート可能なパブリック証明書の発行

コンソールから発行する場合

1.ACM コンソールを開き「証明書をリクエスト」を押下します

2.「パブリック証明書をリクエスト」を選択し「次へ」を押下します

3.ドメイン名を入力します(例:example.com)

4.「エクスポートを有効にする」にチェックを入れます

5.検証方法で「DNS 検証」を選択し「リクエスト」を押下します

6.Route 53 に CNAME レコードを作成し、検証を完了させます

AWS CLI から発行する場合

# 証明書をリクエスト
aws acm request-certificate \
  --domain-name example.com \
  --validation-method DNS \
  --options Export=ENABLED \
  --key-algorithm RSA_2048 \
  --region ap-northeast-1

ステップ 2:初回の証明書エクスポートとデプロイ

コンソールからエクスポートする場合

1.ACM コンソールを開きエクスポートする証明書にチェックを入れます

2.「その他のアクション」を選択し「エクスポート」を押下します

3.パスフレーズを入力後「PEM エンコーディングを生成」を押下し、証明書をダウンロードします  ※ txt 拡張子としてダウンロードされるため注意が必要です

AWS CLI からエクスポートする場合

1.パスフレーズファイルを作成します

echo -n '<パスフレーズ>' | base64 > passphrase.txt

2.証明書をエクスポートします

# 証明書
aws acm export-certificate \
  --certificate-arn <証明書のARN> \
  --passphrase file://passphrase.txt \
  --region ap-northeast-1 | jq -r '.Certificate' > certificate.pem

# 証明書チェーン
aws acm export-certificate \
  --certificate-arn <証明書のARN> \
  --passphrase file://passphrase.txt \
  --region ap-northeast-1 | jq -r '.CertificateChain' > chain.pem

# 暗号化された秘密鍵
aws acm export-certificate \
  --certificate-arn <証明書のARN> \
  --passphrase file://passphrase.txt \
  --region ap-northeast-1 | jq -r '.PrivateKey' > encrypted-private.key

3.証明書を結合します

cat certificate.pem chain.pem > fullchain.pem

4.秘密鍵を復号化します

openssl rsa -in encrypted-private.key -out private.key -passin pass:<パスフレーズ>

EC2 に証明書を配置する

1.EC2 上で証明書を配置します

sudo mkdir /etc/nginx/ssl
sudo mv fullchain.pem /etc/nginx/ssl/
sudo mv private.key /etc/nginx/ssl/
sudo chmod 600 /etc/nginx/ssl/private.key

2.nginx 設定ファイルを編集します

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/private.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}

4.nginx 設定をテストし、リロードします

sudo nginx -t
sudo systemctl reload nginx

ステップ 3:自動更新の仕組み構築

Secrets Manager にパスフレーズを保存する

1.AWS Secrets Manager コンソールを開き「新しいシークレットを保存する」を押下します

2.シークレットタイプで「その他のシークレットのタイプ」を選択します

3.キー/値のペアで以下を入力します

キー: passphrase
値: <パスフレーズ>

4.シークレット名を「acm-export-passphrase」として保存します

Lambda 関数を作成する

1.Lambda コンソールを開き「関数の作成」を押下します

2.関数名を「acm-certificate-deployment」として作成します

3.ランタイムは「Python 3.14」を選択します

4.以下のコードを貼り付けます

import json
import boto3
import base64
        
acm = boto3.client('acm')
ssm = boto3.client('ssm')
secrets = boto3.client('secretsmanager')
        
INSTANCE_IDS = ['<EC2インスタンスのARN>']
PASSPHRASE_SECRET = 'acm-export-passphrase'
        
def lambda_handler(event, context):
    print(f"Event: {json.dumps(event)}")
        
    # EventBridge の resources から Certificate ARN を取得
    try:
        CERTIFICATE_ARN = event['resources'][0]
    except (KeyError, IndexError):
        raise ValueError('Certificate ARN not found in event.resources')
        
    # パスフレーズ取得
    secret = secrets.get_secret_value(SecretId=PASSPHRASE_SECRET)
    passphrase = json.loads(secret['SecretString'])['passphrase']
        
    # 証明書エクスポート
    response = acm.export_certificate(
        CertificateArn=CERTIFICATE_ARN,
        Passphrase=passphrase.encode('utf-8')
    )
        
    certificate = response['Certificate']
    certificate_chain = response['CertificateChain']
    private_key = response['PrivateKey']
        
    # EC2に配置
    for instance_id in INSTANCE_IDS:
        deploy_to_instance(instance_id, certificate, certificate_chain, private_key, passphrase)
        
    return {
        'statusCode': 200,
        'body': json.dumps(f'Certificate deployed to {len(INSTANCE_IDS)} instances')
    }
        
def deploy_to_instance(instance_id, cert, chain, key, passphrase):
    cert_b64 = base64.b64encode(cert.encode()).decode()
    chain_b64 = base64.b64encode(chain.encode()).decode()
    key_b64 = base64.b64encode(key.encode()).decode()
        
    response = ssm.send_command(
        InstanceIds=[instance_id],
        DocumentName='AWS-RunShellScript',
        Parameters={
            'commands': [
                '#!/bin/bash',
                'set -e',
                f'echo "{cert_b64}" | base64 -d > /tmp/certificate.pem',
                f'echo "{chain_b64}" | base64 -d > /tmp/chain.pem',
                f'echo "{key_b64}" | base64 -d > /tmp/encrypted-private.key',
                'cat /tmp/certificate.pem /tmp/chain.pem > /tmp/fullchain.pem',
                f'openssl rsa -in /tmp/encrypted-private.key -out /tmp/private.key -passin pass:{passphrase}',
                'sudo mkdir -p /etc/nginx/ssl',
                'sudo mv /tmp/fullchain.pem /etc/nginx/ssl/',
                'sudo mv /tmp/private.key /etc/nginx/ssl/',
                'sudo chmod 600 /etc/nginx/ssl/private.key',
                'sudo nginx -t',
                'sudo systemctl reload nginx',
                'rm -f /tmp/*.pem',
                f'echo "Certificate deployed at $(date)"'
            ]
        },
        TimeoutSeconds=300
    )
        
    print(f"Command sent to {instance_id}: {response['Command']['CommandId']}")

5.タイムアウトを 5 分に設定します

6.環境変数に証明書 ARN とインスタンス ID を設定します

IAM 権限を設定する

1.Lambda 実行ロールに以下のポリシーをアタッチします

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["acm:ExportCertificate", "acm:DescribeCertificate"],
      "Resource": "<証明書のARN>"
    },
    {
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": "<シークレットのARN>"
    },
    {
      "Effect": "Allow",
      "Action": ["ssm:SendCommand", "ssm:GetCommandInvocation"],
      "Resource": [
        "<EC2インスタンスのARN>",
        "arn:aws:ssm:ap-northeast-1::document/AWS-RunShellScript"
      ]
    }
  ]
}

2.EC2 インスタンスロールに「AmazonSSMManagedInstanceCore」ポリシーをアタッチします

EventBridge ルールを作成する

1.EventBridge コンソールを開き「ルールを作成」を押下します

2.ルール名を「acm-certificate-renewal」として作成します

3.イベントパターンで以下を設定します

{
  "source": ["aws.acm"],
  "detail-type": ["ACM Certificate Available"]
}

4.ターゲットで作成した Lambda 関数を選択します

5.ルールを有効化します

ステップ 4:動作確認

EC2 インスタンスで確認する

1.EC2 インスタンスで証明書が更新されたことを確認します

# 証明書の有効期限確認
openssl x509 -in /etc/nginx/ssl/fullchain.pem -noout -dates

# nginxの動作確認
sudo systemctl status nginx

# 外部から確認
openssl s_client -connect example.com:443 -servername example.com

Systems Manager Run Command の実行結果を確認する

1.Systems Manager コンソールを開き「Run Command」を押下します

2.コマンド履歴から実行結果を確認します

3.「出力を表示」を押下し、詳細なログを確認します

まとめ

ACM のエクスポート可能証明書を使用することで、EC2 上の nginx で使用する証明書を自動的に更新できるようになりました。

主なメリットは以下の通りです。
・証明書の一元管理が可能
・自動更新により運用負荷を削減
・ダウンタイムなしで証明書を更新

注意点としては、2025 年 6 月 17 日以降に発行された証明書のみエクスポート可能であることです。

本記事が証明書管理の自動化を検討されている方の参考になれば幸いです。

参考資料

aws.amazon.com

docs.aws.amazon.com

docs.aws.amazon.com

坂口 大樹 (記事一覧)

マネージドサービス部 AWS サポート課

2023年3月にサーバーワークス入社。

最近ラテアート始めました。