Windows Server 2022 + IIS + AWS Route53でLet’s Encryptを完全自動化する実践ハンズオン

AWS

業務システムで使っているSSL証明書、まだ手動で更新していませんか?

本記事では、AWS EC2上で動くWindows Server 2022 + IIS の SSL証明書を、Let’s Encrypt で完全自動更新する仕組みを構築する手順を、実体験ベースでハンズオン形式にまとめました。

これまで有償SSL証明書(年次更新)を使ってきたサーバを、Let’s Encryptに移行するシナリオを想定しています。ACME (Automatic Certificate Management Environment) プロトコルを利用するため、証明書の有効期限管理から完全に解放されます。

実際の構築で遭遇した3つのトラブルと、その解決手順もすべて記載していますので、同じ構成での導入を検討している方の参考になれば幸いです。

Table of Contents

  1. 想定読者
  2. 環境前提
  3. 全体アーキテクチャ
    1. なぜDNS-01検証か
  4. 事前準備チェックリスト
  5. STEP 1:作業前のバックアップ
  6. STEP 2:IAMポリシーとロールの構築(最小権限)
    1. 2-1. Hosted Zone IDを確認
    2. 2-2. IAMポリシーを作成
      1. このポリシーで「できること」「できないこと」
      2. Conditionキーの仕様注意点
    3. 2-3. IAMロールを作成
    4. 2-4. EC2インスタンスにロールをアタッチ
    5. 2-5. IMDSv2を強制化
    6. 2-6. 動作確認
      1. AWS CLI v2のインストール
      2. IAMロールの認識確認
      3. 権限境界のテスト
  7. STEP 3:win-acmeのインストール
    1. 3-1. win-acme本体のダウンロード
    2. 3-2. コード署名の確認
    3. 3-3. Route53プラグインの追加ダウンロード
    4. 3-4. 起動確認
  8. STEP 4:証明書取得(対話形式)
    1. 4-1. 事前にバインディング情報を控える
    2. 4-2. wacs.exeを起動
    3. 4-3. 対話入力の流れ
    4. 4-4. 期待される成功時のログ
  9. STEP 5:動作確認
    1. 5-1. 証明書ストア確認
    2. 5-2. IISバインディング切替確認
    3. 5-3. 自動更新タスクの確認
    4. 5-4. 実TLS接続テスト
    5. 5-5. ブラウザでの確認
    6. 5-6. アプリケーションログでの確認
  10. STEP 6:テスト更新
  11. トラブルシューティング:実際に遭遇した3つの問題
    1. トラブル1:信頼ポリシーでaws:SourceArnを使うと AssumeRole が失敗する
      1. 症状
      2. 原因
      3. 解決
      4. 学び
    2. トラブル2:間違ったHosted Zone IDをポリシーに記載
      1. 症状
      2. 原因
      3. 解決
      4. 学び
    3. トラブル3:Route53プラグインのDLLが認識されない
      1. 症状
      2. 原因
      3. 解決
  12. 運用化のポイント
    1. 80番ポートの閉鎖
    2. 監視設定
    3. 旧証明書の扱い
    4. 切り戻し手順(緊急時)
  13. まとめ
  14. 参考リンク

想定読者

  • AWS EC2上のIISでSSL証明書を運用しているインフラ担当者
  • 有償SSL証明書の更新作業から解放されたい方
  • 業務基幹システムでもLet’s Encryptを使えるか検討中の方
  • IAMポリシーで最小権限を徹底したい方

環境前提

項目 内容
OS Windows Server 2022 Datacenter
Webサーバ IIS(Internet Information Services)
DNS AWS Route 53(Public Hosted Zone)
インスタンス AWS EC2
対象ドメイン 単一FQDN(例:gw.example.com
既存環境 有償SSL証明書がIISのDefault Web Siteにバインド済み

全体アーキテクチャ

[Let's Encrypt ACME v2]
        ↑ DNS-01検証
        │
[Route53 Hosted Zone] ← TXTレコード自動追加/削除
        ↑
        │ AWS API(IAMロール経由)
        │
[Windows Server 2022 EC2インスタンス]
  ├─ win-acme(WACS)         ← ACMEクライアント
  ├─ AWS CLI v2                ← 動作確認用
  ├─ タスクスケジューラ        ← 日次自動更新チェック
  └─ IIS Default Web Site      ← 443番でHTTPS提供

なぜDNS-01検証か

ACMEプロトコルにはHTTP-01とDNS-01の2つの検証方式がありますが、本構成ではDNS-01を採用します。

観点 HTTP-01 DNS-01
必要なポート 80番をインターネット公開 不要
ワイルドカード対応 不可
サービス停止 80番が他用途と競合する場合あり 完全に影響なし
Route53との相性 関係なし 抜群(IAMロールで自動化)

80番を開けたくない業務サーバではDNS-01一択です。Let’s Encryptへのチャレンジレコードを、サーバ側ではなくRoute53のTXTレコードに書き込むことで認証します。

事前準備チェックリスト

項目 内容
対象FQDN 証明書を取得したいホスト名
Route53 Hosted Zone 対象ドメインがRoute53で管理されていること
IAM操作権限 IAMポリシー・ロール作成可能なAWSコンソール権限
EC2へのアクセス RDP接続できる管理者権限
AMIスナップショット 万一の切り戻し用に作業前に取得
通知用メールアドレス Let’s Encryptからの通知受信用(推奨:個人ではなく共有ML)

STEP 1:作業前のバックアップ

万一の切り戻し用に、EC2のAMIスナップショットを取得します。

EC2コンソールから対象インスタンスを選択 → アクション → イメージとテンプレート → イメージを作成

「再起動しない」のチェックは外して整合性のあるAMIを取得します。Windowsのファイルシステム書き込み中スナップショットはリスクがあります。

STEP 2:IAMポリシーとロールの構築(最小権限)

ここが本記事の最大のポイントです。グループ基幹ドメインを扱う場合、IAMポリシーは極限まで絞り込む必要があります。

2-1. Hosted Zone IDを確認

Route 53 コンソールでHosted Zoneを開き、IDをメモします(Z で始まる文字列)。

重要:必ずAWSコンソールで実値を確認してください。 ここを誤るとSTEP 4で失敗します(後述のトラブル事例参照)。

2-2. IAMポリシーを作成

IAM → ポリシー → ポリシーを作成JSONタブ

以下のJSONを貼り付けます。ZXXXXXXXXXXXXX を実際のHosted Zone ID、_acme-challenge.gw.example.com を実際の対象FQDN(先頭に _acme-challenge. を付ける)に置換してください。

{
  "Version": "2012-10-17",
  "Id": "LetsEncryptAcmePolicy",
  "Statement": [
    {
      "Sid": "AllowListHostedZonesForDiscovery",
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:ListHostedZonesByName"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowGetChangeForPropagationCheck",
      "Effect": "Allow",
      "Action": "route53:GetChange",
      "Resource": "arn:aws:route53:::change/*"
    },
    {
      "Sid": "AllowListRecordSetsInTargetZoneOnly",
      "Effect": "Allow",
      "Action": "route53:ListResourceRecordSets",
      "Resource": "arn:aws:route53:::hostedzone/ZXXXXXXXXXXXXX"
    },
    {
      "Sid": "AllowAcmeChallengeTxtRecordUpsertAndDeleteOnly",
      "Effect": "Allow",
      "Action": "route53:ChangeResourceRecordSets",
      "Resource": "arn:aws:route53:::hostedzone/ZXXXXXXXXXXXXX",
      "Condition": {
        "ForAllValues:StringEquals": {
          "route53:ChangeResourceRecordSetsNormalizedRecordNames": [
            "_acme-challenge.gw.example.com"
          ],
          "route53:ChangeResourceRecordSetsRecordTypes": [
            "TXT"
          ],
          "route53:ChangeResourceRecordSetsActions": [
            "UPSERT",
            "DELETE"
          ]
        }
      }
    }
  ]
}

このポリシーで「できること」「できないこと」

操作 可否
_acme-challenge.gw.example.com のTXTレコードUPSERT/DELETE ✅ 可
対象FQDN以外のTXTレコード変更 ❌ 不可
対象FQDNのAレコード/CNAME変更 ❌ 不可
ゾーンApex(SPF/MX/DMARCなど)変更 ❌ 不可
他のHosted Zoneのレコード変更 ❌ 不可

つまり、万が一サーバが侵害されてもACMEチャレンジ用TXTレコード以外は触れない設計です。

Conditionキーの仕様注意点

AWS公式仕様により、以下を厳守してください:

  • NormalizedRecordNames は全て小文字、末尾ドット無し
  • RecordTypes大文字
  • ActionsCREATE | UPSERT | DELETE のいずれか、大文字
  • 複数値を持つキーは ForAllValues:StringEquals 演算子を使用

ポリシー名は LetsEncryptRoute53AcmePolicy 等、用途が分かる名前にします。

2-3. IAMロールを作成

IAM → ロール → ロールを作成

  • 信頼されたエンティティ:AWSのサービスEC2
  • 権限ポリシー:上記で作成した LetsEncryptRoute53AcmePolicy を選択
  • ロール名:LetsEncryptRoute53Role

2-4. EC2インスタンスにロールをアタッチ

EC2コンソール → 対象インスタンス → アクション → セキュリティ → IAMロールを変更 → 作成したロールを選択。

2-5. IMDSv2を強制化

EC2のメタデータサービスをIMDSv2のみに強制化します(SSRF対策)。

EC2コンソール → 対象インスタンス → アクション → インスタンス設定 → インスタンスメタデータオプションを変更

項目 設定値
インスタンスメタデータサービス 有効
IMDSv2 必須
メタデータレスポンスのホップ制限 1(ベアメタルOS構成のため)

2-6. 動作確認

EC2にRDPでログインし、管理者PowerShellで動作確認します。

AWS CLI v2のインストール

New-Item -ItemType Directory -Path "C:\Temp" -Force
Invoke-WebRequest -Uri "https://awscli.amazonaws.com/AWSCLIV2.msi" -OutFile "C:\Temp\AWSCLIV2.msi"
Start-Process msiexec.exe -ArgumentList "/i C:\Temp\AWSCLIV2.msi /quiet /norestart" -Wait

インストール後、新しいPowerShellウィンドウを開いて以下を実行:

aws --version

IAMロールの認識確認

# IMDSv2でトークン取得
$token = Invoke-RestMethod -Method PUT `
  -Uri "http://169.254.169.254/latest/api/token" `
  -Headers @{"X-aws-ec2-metadata-token-ttl-seconds" = "21600"}

# クレデンシャル取得テスト
$creds = Invoke-RestMethod `
  -Uri "http://169.254.169.254/latest/meta-data/iam/security-credentials/LetsEncryptRoute53Role" `
  -Headers @{"X-aws-ec2-metadata-token" = $token}

Write-Host "Code: $($creds.Code)"
Write-Host "Expiration: $($creds.Expiration)"

Code: Success が返ればOK。

権限境界のテスト

許可される操作(成功するはず):

# Hosted Zone一覧取得
aws route53 list-hosted-zones --region us-east-1

# 対象ゾーンのレコード参照
aws route53 list-resource-record-sets `
  --hosted-zone-id ZXXXXXXXXXXXXX `
  --max-items 5

拒否される操作(AccessDeniedになるはず):

$failTestJson = @'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "test-deny.gw.example.com",
      "Type": "A",
      "TTL": 60,
      "ResourceRecords": [{"Value": "192.0.2.1"}]
    }
  }]
}
'@
$failTestJson | Out-File -Encoding ASCII -FilePath "C:\Temp\fail-test.json"

aws route53 change-resource-record-sets `
  --hosted-zone-id ZXXXXXXXXXXXXX `
  --change-batch file://C:\Temp\fail-test.json

An error occurred (AccessDenied) というエラーが返れば、ポリシーが意図通り機能している証拠です。

重要:このテストで万一成功してしまったら、不要なAレコードが作成されています。即座に同じJSONの "Action""DELETE" に変えて削除してください。

STEP 3:win-acmeのインストール

3-1. win-acme本体のダウンロード

win-acme公式GitHub から、最新版の pluggable.zip(x64版) をダウンロードします。

重要trimmed.zip ではなく pluggable.zip を選んでください。Route53などの追加プラグインを使うには pluggable版が必須です。

PowerShellで実行する場合(バージョン番号は実際の最新版に置き換えてください):

New-Item -ItemType Directory -Path "C:\Tools\win-acme" -Force
cd C:\Temp

$winAcmeUrl = "https://github.com/win-acme/win-acme/releases/download/v2.2.9.1701/win-acme.v2.2.9.1701.x64.pluggable.zip"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $winAcmeUrl -OutFile "C:\Temp\win-acme.zip" -UseBasicParsing

Expand-Archive -Path "C:\Temp\win-acme.zip" -DestinationPath "C:\Tools\win-acme" -Force

3-2. コード署名の確認

Get-AuthenticodeSignature -FilePath "C:\Tools\win-acme\wacs.exe" | Format-List Status, SignerCertificate

Status: Valid であればOK。

3-3. Route53プラグインの追加ダウンロード

最新のwin-acmeでは、Route53プラグインは別配布になっています。本体と同じバージョンのプラグインをダウンロードします。

$pluginUrl = "https://github.com/win-acme/win-acme/releases/download/v2.2.9.1701/plugin.validation.dns.route53.v2.2.9.1701.zip"
$pluginZip = "C:\Temp\route53-plugin.zip"

Invoke-WebRequest -Uri $pluginUrl -OutFile $pluginZip -UseBasicParsing

# 一時展開
$tempExtract = "C:\Temp\route53-extract"
Expand-Archive -Path $pluginZip -DestinationPath $tempExtract -Force

# Windowsのブロック解除(重要:ダウンロードファイルは既定でブロックされる)
Get-ChildItem -Path $tempExtract -Filter "*.dll" -Recurse | Unblock-File

# win-acme本体ディレクトリへコピー
Copy-Item -Path "$tempExtract\*" -Destination "C:\Tools\win-acme\" -Recurse -Force

# 配置確認
Get-ChildItem -Path "C:\Tools\win-acme" -Filter "*Route53*"
Get-ChildItem -Path "C:\Tools\win-acme" -Filter "*AWS*"

PKISharp.WACS.Plugins.ValidationPlugins.Route53.dll および AWSSDK 関連DLLが配置されていればOK。

3-4. 起動確認

cd C:\Tools\win-acme
.\wacs.exe --version

バージョン情報が表示されればインストール成功です。

STEP 4:証明書取得(対話形式)

4-1. 事前にバインディング情報を控える

切り戻し用に、現状の証明書バインディング情報を保存します。

Import-Module WebAdministration

# 現バインディング情報を取得
$httpsBindings = Get-WebBinding | Where-Object { $_.protocol -eq "https" }
$httpsBindings | Format-List protocol, bindingInformation, sslFlags, certificateHash, certificateStoreName

# 拇印をファイル保存
$httpsBindings | ForEach-Object { $_.certificateHash } | Out-File -FilePath "C:\Temp\old-cert-thumbprint.txt" -Encoding ASCII
Get-Content C:\Temp\old-cert-thumbprint.txt

# 現証明書の詳細
foreach ($binding in $httpsBindings) {
    $cert = Get-ChildItem -Path "Cert:\LocalMachine\$($binding.certificateStoreName)\$($binding.certificateHash)" -ErrorAction SilentlyContinue
    if ($cert) {
        $cert | Format-List Subject, Issuer, NotBefore, NotAfter, Thumbprint, FriendlyName
    }
}

出力される 拇印(Thumbprint) は必ずメモまたはファイル保管してください。

4-2. wacs.exeを起動

cd C:\Tools\win-acme
.\wacs.exe

メインメニューで M(Create certificate with full options)を選択し、対話形式で進めます。

4-3. 対話入力の流れ

以下、画面と入力内容の対応表です。

段階 入力内容
メインメニュー M
Domain determination 2(Manual input)
Host gw.example.com(実際のFQDN)
Friendly name 任意の識別名(例:GW_LE_AutoRenew
Split into multiple certificates 4(Single certificate)
Validation method [dns] Create verification records in AWS Route 53 の番号
IAM role name LetsEncryptRoute53Role空欄不可、空欄にするとアクセスキーモードになる
Private key type 2(RSA、互換性最重視)
Certificate store 4(Windows Certificate Store)
Store name 3(Default = WebHosting、IIS専用ストア)
Another store 5(No additional store steps)
Installation step 1(Create or update bindings in IIS)
Site 1(Default Web Site)
Add another installation step 3(No additional installation steps)
Open Terms of Service PDF n
Agree with terms y
Notification email 通知用メールアドレス(共有ML推奨)
Specify task user n(SYSTEMアカウントで実行)
重要:「IAM role name」を空欄でEnterすると、アクセスキーモードに切り替わってしまいます。必ずIAMロール名を正確に入力してください。

4-4. 期待される成功時のログ

[gw.example.com] Authorizing using dns-01 validation (Route53)
Creating TXT record _acme-challenge.gw.example.com with value xxxxx
[gw.example.com] Record xxxxx successfully created
[gw.example.com] Preliminary validation succeeded
[gw.example.com] Authorization result: valid
Requesting certificate gw.example.com
Saving certificate to Windows Certificate Store (WebHosting)
Committing 1 https binding changes to IIS while updating site 1
Adding Task Scheduler entry with the following settings
 - Name win-acme renew (acme-v02.api.letsencrypt.org)
Next renewal due after [約55日後の日付]
Certificate [Friendly name] created

Certificate ... created まで出れば取得完了です。Q で wacs.exe を終了します。

STEP 5:動作確認

5-1. 証明書ストア確認

Get-ChildItem -Path "Cert:\LocalMachine\WebHosting" | 
  Where-Object { $_.Subject -like "*gw.example.com*" } |
  Format-List Subject, Issuer, NotBefore, NotAfter, Thumbprint, FriendlyName

Issuer: CN=R10 または CN=R11CN=R12 等(Let’s Encryptの中間CA)になっていれば成功です。

5-2. IISバインディング切替確認

Import-Module WebAdministration
$binding = Get-WebBinding | Where-Object { $_.protocol -eq "https" }
$oldThumbprint = (Get-Content C:\Temp\old-cert-thumbprint.txt).Trim()

Write-Host "新拇印: $($binding.certificateHash)"
Write-Host "旧拇印: $oldThumbprint"

if ($binding.certificateHash -ne $oldThumbprint) {
    Write-Host "✓ バインディングがLet's Encrypt証明書に切り替わりました" -ForegroundColor Green
}

5-3. 自動更新タスクの確認

Get-ScheduledTask -TaskName "win-acme*" | Format-List TaskName, State, Description
Get-ScheduledTask -TaskName "win-acme*" | Get-ScheduledTaskInfo | 
  Format-List LastRunTime, NextRunTime, LastTaskResult
LastTaskResult: 267011 は「まだ実行されていない」を意味する正常値です。

5-4. 実TLS接続テスト

$tcpClient = New-Object System.Net.Sockets.TcpClient("gw.example.com", 443)
$sslStream = New-Object System.Net.Security.SslStream($tcpClient.GetStream(), $false, ({$true}))
$sslStream.AuthenticateAsClient("gw.example.com")
$serverCert = $sslStream.RemoteCertificate

Write-Host "Subject: $($serverCert.Subject)"
Write-Host "Issuer: $($serverCert.Issuer)"
Write-Host "Valid To: $($serverCert.GetExpirationDateString())"

$sslStream.Close()
$tcpClient.Close()

IssuerLet's Encrypt が含まれていれば完璧です。

5-5. ブラウザでの確認

別PCのブラウザから https://gw.example.com にアクセスし、証明書詳細を確認します。

ハマりポイント:ブラウザはTLSセッションをキャッシュするため、サーバ側で証明書を変更しても即座には反映されないことがあります。シークレットモードまたは別ブラウザで確認するのが確実です。

5-6. アプリケーションログでの確認

実際のクライアント接続が成立しているか、IISログで確認します。

$logPath = "C:\inetpub\logs\LogFiles\W3SVC1"
$latestLog = Get-ChildItem -Path $logPath -Filter "*.log" | 
  Sort-Object LastWriteTime -Descending | Select-Object -First 1

# HTTPステータスコード集計
Get-Content $latestLog.FullName | 
  Where-Object { $_ -notmatch "^#" -and $_ -ne "" } | 
  ForEach-Object { 
    $cols = $_ -split "\s+"
    if ($cols.Count -ge 12) { $cols[11] }
  } | 
  Group-Object | 
  Sort-Object Count -Descending | 
  Format-Table Count, Name -AutoSize

200 が圧倒的多数を占めていればOK。証明書置換後にTLSエラーが急増していないことを確認します。

STEP 6:テスト更新

実際に60日待たずに、更新フローが動くかテストします。

cd C:\Tools\win-acme
.\wacs.exe --renew --force

--force で強制更新を実行。新しい証明書が再取得され、IISバインディングが更新されることを確認します。

Let’s Encryptの同一FQDNの重複証明書発行は5枚/週までなので、この程度のテストはレート制限に引っかかりません。

トラブルシューティング:実際に遭遇した3つの問題

実際の構築で遭遇した問題と解決手順を記録します。

トラブル1:信頼ポリシーでaws:SourceArnを使うと AssumeRole が失敗する

症状

IAMロールはアタッチされているのに、AWS CLIから NoCredentials エラー。

aws: [ERROR]: An error occurred (NoCredentials): Unable to locate credentials.

IMDSから直接クレデンシャル取得を試すと、Code: AssumeRoleUnauthorizedAccess が返る。

原因

IAMロールの信頼ポリシーに、特定インスタンスへの制限として aws:SourceArn Conditionを追加していた:

"Condition": {
  "ArnLike": {
    "aws:SourceArn": "arn:aws:ec2:ap-northeast-1:123456789012:instance/i-xxx"
  }
}

EC2のIAM InstanceProfile経由のAssumeRoleでは、aws:SourceArn および aws:SourceAccount は期待通りに動作しません。これらはクロスサービス呼び出し(Confused Deputy対策)のためのCondition Keyで、EC2インスタンスがIAMロールを引き受ける動作には適用できません。

解決

信頼ポリシーを標準形(Condition無し)に戻します:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

学び

EC2用ロールのセキュリティ強化は、信頼ポリシーで縛るのではなく、権限ポリシー側で操作対象を厳格に限定するのが正しいアプローチです。

「誰がそのロールをEC2にアタッチできるか」は iam:PassRole 権限で別途制御する設計になっています。

トラブル2:間違ったHosted Zone IDをポリシーに記載

症状

win-acmeでの証明書取得時に、TXTレコード作成段階で AccessDenied エラー:

An error occured while commiting validation configuration: 
User: arn:aws:sts::xxx:assumed-role/LetsEncryptRoute53Role/i-xxx 
is not authorized to perform: route53:ChangeResourceRecordSets 
on resource: arn:aws:route53:::hostedzone/ZACTUAL_ID 
because no identity-based policy allows the route53:ChangeResourceRecordSets action

原因

IAMポリシーに記載したHosted Zone ID(ZIN_POLICY)と、Route53上で実際に該当ドメインを管理しているHosted Zone ID(ZACTUAL_ID)が異なっていた。

人間が「Route53コンソールで確認したID」と思い込んでいた値が、実は別のテスト用Hosted ZoneのIDだった、というケアレスミス。

解決

AWS CLIで実際のHosted Zone IDを確認:

aws route53 list-hosted-zones-by-name --dns-name example.com --output table

これで実在するHosted Zone IDを取得し、IAMポリシーの2箇所(AllowListRecordSetsInTargetZoneOnlyAllowAcmeChallengeTxtRecordUpsertAndDeleteOnly のResource ARN)を修正。

学び

AWSはポリシー作成時にResource ARNの実在チェックをしません。存在しないHosted Zone IDを書いてもポリシー作成は成功し、APIを実際に叩いた段階で初めて「該当ポリシー無し → AccessDenied」となります。

必ずAWS CLIで list-hosted-zones-by-name を実行して実在IDを取得するステップを入れることを推奨します。

トラブル3:Route53プラグインのDLLが認識されない

症状

win-acmeのValidation Plugin選択画面で、Route53が選択肢に出てこない。

原因

pluggable.zip をダウンロードしただけでは、Route53プラグインは含まれていません。最新版のwin-acmeでは、AWSやAzure等のクラウド系プラグインは別配布になっています。

解決

公式リリースページ から plugin.validation.dns.route53.vX.X.X.X.zip本体と同じバージョンでダウンロードし、展開して C:\Tools\win-acme\ 直下にコピー。

また、ダウンロードしたDLLは Windows の Zone.Identifier ADS(代替データストリーム) によりブロックされるため、Unblock-File で解除する必要があります:

Get-ChildItem -Path "C:\Temp\route53-extract" -Filter "*.dll" -Recurse | Unblock-File

これを忘れると、DLLが信頼されずに正しくロードされない場合があります。

運用化のポイント

構築後の運用品質を上げるためのTips。

80番ポートの閉鎖

DNS-01検証なので80番は不要です。EC2セキュリティグループから HTTP / TCP / 80 / 0.0.0.0/0 を削除して攻撃面を削減します。

監視設定

90日サイクルの自動更新を信頼しつつも、万一に備えた監視を推奨:

  • CloudWatch Alarms / Datadog / Zabbix等で証明書有効期限を日次チェック
  • 簡易版:PowerShellスクリプト + 社内Slack/Teams通知
# 証明書有効期限チェック例
$cert = Get-ChildItem -Path "Cert:\LocalMachine\WebHosting" | 
  Where-Object { $_.Subject -like "*gw.example.com*" } | 
  Sort-Object NotAfter -Descending | Select-Object -First 1

$daysRemaining = ($cert.NotAfter - (Get-Date)).Days
Write-Host "Days remaining: $daysRemaining"

if ($daysRemaining -lt 30) {
    # アラート通知処理
}

旧証明書の扱い

1〜2週間問題なく動作することを確認してから、旧証明書を削除:

$oldThumbprint = (Get-Content C:\Temp\old-cert-thumbprint.txt).Trim()
Remove-Item -Path "Cert:\LocalMachine\My\$oldThumbprint" -Force

それまでは保険として残しておきます。

切り戻し手順(緊急時)

$oldThumbprint = (Get-Content C:\Temp\old-cert-thumbprint.txt).Trim()
$binding = Get-WebBinding | Where-Object { $_.protocol -eq "https" }
$binding.RemoveSslCertificate()
$binding.AddSslCertificate($oldThumbprint, "My")
iisreset

最悪のケースではAMIスナップショットから復元します。

まとめ

本記事の構築で達成できた成果:

  • ✅ 有償SSL証明書からLet’s Encrypt(無償)への完全移行
  • ✅ 証明書更新作業の完全自動化(人手不要)
  • ✅ 最小権限IAMポリシーによる安全な設計
  • ✅ DNS-01検証で80番ポート公開を回避
  • ✅ ノーダウンタイムでの証明書置換

90日サイクルの自動更新により、証明書の有効期限を意識する必要が完全になくなりました

特にAWS Route53で権威DNSを運用している環境では、IAMロールベースの認証によりクレデンシャル管理も不要で、運用負荷が極めて低い構成を実現できます。

業務基幹システムでもLet’s Encryptは十分に実用に耐えます。ぜひ導入を検討してみてください。

参考リンク

タイトルとURLをコピーしました