--- title: "win-acmeベース Windows Server証明書の自動化" slug: "certificate-winacme" updated: 2026-09-17T09:21:20Z published: 2026-09-17T09:21:20Z canonical: "guide.ncloud-docs.com/certificate-winacme" stale: true --- > ## Documentation Index > Fetch the complete documentation index at: https://guide.ncloud-docs.com/llms.txt > Use this file to discover all available pages before exploring further. # win-acmeベース Windows Server証明書の自動化 Classic/VPC環境で利用できます。 win-acme(ACMEv2クライアント)を使用して Certificate Manager ACME機能で TLS証明書を自動的に発行・更新する方法を説明します。DNS-01チャレンジの検証に Ncloud Global DNS APIを使用し、Windows Task Schedulerで自動更新を構成します。 注意 Certificate Manager ACMEは RSA 2048キーのみ許可します。win-acmeのプライマリキーサイズは RSA 3072のため、設定を変更しないと DNS-01検証に成功しても証明書の発行に失敗します。必ず `settings.json`でキーサイズを2048に変更してください。 ## サービス仕様 | 項目 | 内容 | | --- | --- | | サポート OS | Windows Server 2019, Windows Server 2022 | | 推奨クライアント | win-acme v2.2.9以上(Standalone、64-bit) | | 検証方法 | DNS-01 (Ncloud Global DNS API) | | キーアルゴリズム | RSA 2048 / SHA256withRSA(必須) | | 証明書の保存場所 | Windows Certificate Store (WebHosting) | | ACMEのエンドポイント | `https://acme.navercloudtrust.com/acme/directory` | ## 始める前に このガイドを進める前に、以下の項目を準備しておく必要があります。 | 項目 | 確認内容 | | --- | --- | | Ncloud IAM APIキー | Access Key/Secret Key発行完了、Global DNS権限付与確認 | | EABキー | Certificate Managerコンソールで EAB Key ID/HMAC Key発行完了 | | Ncloud Global DNS | 証明書を発行するドメインが Global DNSにゾーンとして登録されている | | 管理者権限 | wacs.exeの実行および Windows証明書の保存場所へアクセスするためのローカル管理者権限 | | IIS | HTTPS証明書を適用するサイト構成完了 | | PowerShell実行ポリシー | `RemoteSigned`以上の設定を確認 | 参考 External Account Binding(EAB)キーは、ACMEアカウント登録時に1度のみ使用されます。1度登録されたアカウントは、その後の更新に自動的に再利用されるため、キーを再発行する必要がありません。 ## ステップ1: win-acmeインストールと設定 ### win-acmeインストール 1. [win-acme公式リリースページ](https://github.com/win-acme/win-acme/releases)で v2.2.9以上の **release、trimmed、standalone、64-bit**バージョンをダウンロードします。 2. 目的のパスに圧縮ファイルを解凍します。 ### settings.json変更 win-acmeインストールディレクトリの `settings.json`ファイルを開き、以下の2つの項目を必ず変更します。 #### 1.ACMEのエンドポイントを変更 `Acme`セクションの `DefaultBaseUri`値を以下のように変更します。win-acmeのデフォルト値は Let's Encryptのため、必ず変更します。 ``` "Acme": { "DefaultBaseUri": "https://acme.navercloudtrust.com/acme/directory" } ``` #### 2.RSAキーサイズを変更(必須) `Csr`セクションを探し、以下のように変更します。 ``` "Csr": { "Rsa": { "KeyBits": 2048, "SignatureAlgorithm": "SHA256withRSA" } } ``` 注意 `KeyBits`デフォルト値は `3072`です。この値を変更しないと、DNS-01検証に成功しても CAが orderを `invalid`処理し、証明書の発行を拒否します。 設定完了後の重要な項目は、次の通りです。 | キーパス | 設定値 | | --- | --- | | `Acme.DefaultBaseUri` | `https://acme.navercloudtrust.com/acme/directory` | | `Csr.Rsa.KeyBits` | `2048` | | `Csr.Rsa.SignatureAlgorithm` | `SHA256withRSA` | | `ScheduledTask.RenewalDays` | `45`(証明書の有効期間が198日の場合の例。有効期限の1/3前から更新試行を推奨) | ## ステップ2: DNSフックスクリプトを準備 win-acmeは DNS-01チャレンジ実行時にユーザーが準備した外部のスクリプトを呼び出し、DNS TXTレコードを作成・削除します。DNSフックスクリプトは別途提供しないため、直接作成します。Ncloud Global DNS APIを使用する例は、[Ncloud Global DNSフックスクリプトの例](/docs/ja/certificate-winacme#NcloudGlobalDNS%ED%9B%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%98%88%EC%8B%9C)をご参照ください。 ### フックスクリプトのシステム要件 win-acmeは DNS-01チャレンジ時に、以下の2つのスクリプトを順に呼び出します。各スクリプトは以下のロールを実行する必要があります。 **作成スクリプト(dns-create)** win-acmeがチャレンジを開始する際に呼び出されます。以下を処理する必要があります。 1. 受け取ったドメインに対応する DNS zoneを検索します。 2. `_acme-challenge.<도메인>` TXTレコードを作成します。 3. DNSの変更内容を反映します。 4. DNS伝播が完了するまで、十分に待機します。(推奨: 60秒以上) **削除スクリプト(dns-delete)** チャレンジ検証が完了した後、呼び出されます。以下を処理する必要があります。 1. 作成スクリプトで作成した TXTレコードを削除します。 2. DNSの変更内容を反映します。 ### スクリプト呼び出しインターフェース win-acmeはスクリプトを呼び出す際に、以下の引数を順に渡します。スクリプト作成時は、この形式に従います。 | 引数の順序 | 値 | 説明 | | --- | --- | --- | | 1 | `create` / `delete` | 実行する動作 | | 2 | ドメイン名 | 証明書を発行するドメイン(例: `example.com`) | | 3 | レコード名 | TXTレコードの全体の名前(例: `_acme-challenge.example.com`) | | 4 | トークン値 | CAが発行したチャレンジトークン(TXTレコード値に設定) | ### Ncloud Global DNS API連携時の注意点 Ncloud Global DNS APIを使用する場合は、以下の事項を考慮してスクリプトを作成します。 - 全ての APIリクエストには、HMAC-SHA256署名ヘッダが必要です。以下の3つのヘッダを含める必要があります。 | ヘッダ | 値 | | --- | --- | | `x-ncp-apigw-timestamp` | リクエスト時間(Unixミリ秒) | | `x-ncp-iam-access-key` | Ncloud IAM Access Key | | `x-ncp-apigw-signature-v2` | HMAC-SHA256署名値(Base64エンコード) | - レコード作成・削除後は、必ず Apply APIを呼び出すことで DNSに反映されます。 - ワイルドカード証明書(`*.example.com`)発行時は、ドメインの先頭の `*.`を削除し、zoneを検索します。 - サブドメイン証明書(`sub.example.com`)発行時は、`_acme-challenge.sub`形式でホスト名を計算します。 注意 スクリプトファイルは、必ず UTF-8 BOMエンコードで保存します。ANSI(CP949)エンコードで保存すると、PowerShell実行時に文字化けが発生し、一部の環境では誤動作します。 ## ステップ3: 証明書発行 スクリプトの準備が完了すると、管理者権限の PowerShellで以下のコマンドを実行します。括弧(`[ ]`)内の項目は、実際の値に置き換えます。 ``` wacs.exe ` --source manual ` --host "[証明書を発行するドメイン]" ` --validation dnsscript ` --dnscreatescript "[作成スクリプトのパス]" ` --dnsdeletescript "[削除スクリプトのパス]" ` --store certificatestore ` --certificatestore WebHosting ` --installation iis ` --siteid [IISサイト ID] ` --eab-key-identifier "[EAB_KEY_ID]" ` --eab-key "[EAB_HMAC_KEY]" ` --emailaddress "[担当者のメールアドレス]" ` --accepttos ` --baseuri "https://acme.navercloudtrust.com/acme/directory" ``` **主要パラメータの説明** | パラメータ | 説明 | | --- | --- | | `--source manual` | ドメインを直接指定 | | `--host` | 証明書を発行するドメイン | | `--validation dnsscript` | スクリプトベースの DNS-01検証を使用 | | `--dnscreatescript` | TXTレコード作成スクリプトのパス | | `--dnsdeletescript` | TXTレコード削除スクリプトのパス | | `--certificatestore WebHosting` | 証明書を WebHostingストアに保存 | | `--installation iis` / `--siteid` | IISサイトに証明書を自動バインド | | `--eab-key-identifier` | EAB Key ID | | `--eab-key` | EAB HMAC Key | | `--accepttos` | ACME約款への自動同意 | | `--baseuri` | ACMEのエンドポイント | **発行の流れ** | ステップ | 内容 | | --- | --- | | 1 | ACME CA接続確認 | | 2 | EABキーで ACMEアカウントを登録 | | 3 | 証明書の order作成、DNS-01チャレンジトークンの受信 | | 4 | 作成スクリプトを呼び出し → TXTレコード作成 → DNS伝播待機 | | 5 | CAが TXTレコードを確認 → 検証完了 | | 6 | 削除スクリプトを呼び出す → TXTレコード削除 | | 7 | RSA 2048 CSRを提出 → CA署名 | | 8 | 証明書ダウンロード → WebHostingストアに保存 | | 9 | IISサイトの 443バインドを自動的に置き換え | | 10 | renewal.json保存(以降の自動更新に使用) | 参考 問題が発生する場合、コマンドに `--verbose`オプションを追加すると、CAとの HTTPリクエスト・レスポンスを含む詳細ログが出力されます。初回登録または EAB置き換え後の再登録時に使用することを推奨します。 ## ステップ4: 自動更新設定 証明書登録後、管理者権限の PowerShellで以下のコマンドを実行し、Task Schedulerの自動更新タスクを登録します。 ``` $action = New-ScheduledTaskAction ` -Execute "wacs.exeのパス全体" ` -Argument '--renew --baseuri "https://acme.navercloudtrust.com/acme/directory"' $trigger = New-ScheduledTaskTrigger -Daily -At "09:00" $principal = New-ScheduledTaskPrincipal ` -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet ` -ExecutionTimeLimit (New-TimeSpan -Hours 2) ` -StartWhenAvailable Register-ScheduledTask ` -TaskName "win-acme daily renew" ` -Action $action ` -Trigger $trigger ` -Principal $principal ` -Settings $settings ` -Force ``` 登録されたタスクは毎日09:00に実行され、更新するかどうかを自動的に判断します。更新が必要な場合、DNS-01チャレンジを実行して、IISバインドを無停止に変更します。 更新ステータスを確認するには、以下のコマンドを実行します。 ``` Get-ScheduledTaskInfo -TaskName "win-acme daily renew" | Select-Object LastRunTime, LastTaskResult, NextRunTime ``` | LastTaskResult | 意味 | | --- | --- | | `0` | 更新成功 | | `267011` | 更新不要(正常) | | その他 | 失敗(ログ確認が必要) | 更新ログは、win-acmeデータディレクトリ内の `Log\log-YYYYMMDD.txt`ファイルで確認できます。 ## 証明書確認 発行された証明書は、Windows Certificate Storeの `WebHosting` ストレージに保存されます。以下の方法で確認します。 ``` # IISバインドの証明書の thumbprintを確認 Get-WebBinding | Where-Object { $_.protocol -eq "https" } | Select-Object bindingInformation, certificateHash # WebHostingストアの証明書リストを照会 Get-ChildItem "Cert:\LocalMachine\WebHosting" | Select-Object Subject, Thumbprint, NotAfter, Issuer ``` または Windows証明書の管理者(`certlm.msc`) → 証明書(ローカルコンピュータ) → ウェブホスト → 証明書で確認できます。 ## EABキーの置き換え EABキーの置き換えが必要な場合、以下の手順で行います。既存の ACMEアカウントファイルが残っている場合、新しい EABキーは無視され、既存のアカウントが再利用されるため、必ずアカウントファイルを先に削除します。 1. スクリプトまたは設定ファイルの EAB値を、新しく発行した値に置き換えます。 2. win-acmeデータディレクトリ内の `Registration_v2`、`Signer_v2`、`*.renewal.json`ファイルを削除します。 3. ステップ3の発行コマンドを再実行します。 成功すると、新しいアカウントで証明書が発行され、IISバインドが自動的に置き換えられます。Task Schedulerタスクは別途変更する必要はありません。 ## 手動更新 テストまたは緊急対応が必要な場合は、以下のコマンドを使用します。 ``` # 一般更新(期限切れ条件を満たした場合のみ実行) wacs.exe --renew --baseuri "https://acme.navercloudtrust.com/acme/directory" # 強制更新(条件に関係なく直ちに実行) wacs.exe --renew --baseuri "https://acme.navercloudtrust.com/acme/directory" --force # 詳細ログを出力して強制更新 wacs.exe --renew --baseuri "https://acme.navercloudtrust.com/acme/directory" --force --verbose # Task Schedulerタスクを直ちに実行 Start-ScheduledTask -TaskName "win-acme daily renew" # 登録された renewalリストを確認 wacs.exe --list --baseuri "https://acme.navercloudtrust.com/acme/directory" ``` ## トラブルシューティング | 症状 | 原因 | 対応 | | --- | --- | --- | | order `invalid`(DNS検証は valid) | RSAキーサイズが一致しない | `settings.json` → `Csr.Rsa.KeyBits: 2048`を確認 | | Authorization `pending`後に失敗 | DNS伝播の遅延 | 作成スクリプトの待ち時間を90~120秒に延長 | | TXTレコード検証に失敗 | Zone検索に失敗 | コンソールで当該ドメインが Global DNSに Zoneとして登録されているか確認 | | API 401/403エラー | APIキーエラーまたは権限不足 | Ncloud IAMで Access Keyの有効性および Global DNS権限を再確認 | | 韓国語ログの文字化け | フックファイルのエンコードエラー | フックファイルを UTF-8 BOMで再保存 | | エラーなしで静かに失敗 | `--verbose`未使用 | `--verbose`オプション追加後に再実行 | | EAB置き換え後、既存のアカウントを再利用 | `Registration_v2`未削除 | [EABキーの置き換え](/docs/ja/certificate-winacme#EAB%ED%82%A4%EA%B5%90%EC%B2%B4)の手順を参照 | | Task Schedulerに失敗 | SYSTEMアカウント権限不足 | タスクプロパティ → **最高権限で実行**がチェックされているか確認 | | IISバインドの未更新 | 証明書の保存場所が一致しない | `--certificatestore WebHosting`パラメータを確認 | ログを確認するには、以下のコマンドを実行します。 ``` Get-Content "win-acmeデータのパス\Log\log-$(Get-Date -Format yyyyMMdd).txt" -Tail 100 ``` ## セキュリティに関する勧告 - APIキーと EABキーが含まれた設定ファイルは、Gitリポジトリや共有フォルダに配置しません。 - Ncloud APIキーには、Global DNSサービスのみに対して最小権限を付与することを推奨します。 - win-acmeデータディレクトリ(ACMEアカウントの署名キーを含む)へのアクセス権限を、管理者アカウントに限定します。 - 証明書のプライベートキーが外部に漏れないようご注意ください。 - win-acmeおよび PowerShellは定期的に最新バージョンに保ちます。 ## Ncloud Global DNSフックスクリプトの例 以下は、Ncloud Global DNS APIを使用する DNSフックスクリプトの例です。 注意 以下のスクリプトは参考例です。運用環境に合わせて十分に検討し、テストした上で使用します。スクリプトの変更・設定・実行結果に関する責任はユーザーにあります。 ### APIキーの設定ファイル: config.ps1 ``` # ACMEサーバ(変更不要) $ACME_SERVER = "https://acme.navercloudtrust.com/acme/directory" # EABキー — Certificate Managerコンソールで発行された値に置き換え $EAB_KID = "[EAB_KEY_ID]" $EAB_HMAC_KEY = "[EAB_HMAC_KEY]" # NCP Global DNS APIキー — NCP IAMコンソールで発行(Global DNSの権限が必要) $NCP_ACCESS_KEY = "[NCP_ACCESS_KEY]" $NCP_SECRET_KEY = "[NCP_SECRET_KEY]" $NCP_DNS_API = "https://globaldns.apigw.ntruss.com" # DNS伝播待ち時間(秒)。検証に失敗した場合は、90~120に延長します。 $DNS_PROPAGATION_WAIT = 60 # ACMEアカウントのメールアドレス $ACME_EMAIL = "[ADMIN_EMAIL]" ``` ### TXTレコード作成フック: dns-create.ps1 ``` # dns-create.ps1 — win-acme DNS-01作成フック(NCP Global DNS) # UTF-8 BOMエンコードで保存すること param( [string]$Action, [string]$Domain, [string]$RecordName, [string]$Token ) $ConfigPath = Join-Path $PSScriptRoot "config.ps1" . $ConfigPath function Get-NcpHeaders { param([string]$Method, [string]$Uri) $timestamp = [long]([datetimeoffset]::UtcNow.ToUnixTimeMilliseconds()) $message = "$Method $Uri`n$timestamp`n$NCP_ACCESS_KEY" $hmac = New-Object System.Security.Cryptography.HMACSHA256 $hmac.Key = [System.Text.Encoding]::UTF8.GetBytes($NCP_SECRET_KEY) $sig = [Convert]::ToBase64String( $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($message)) ) return @{ "x-ncp-apigw-timestamp" = "$timestamp" "x-ncp-iam-access-key" = $NCP_ACCESS_KEY "x-ncp-apigw-signature-v2" = $sig "Content-Type" = "application/json" } } # ワイルドカード処理 $Domain = $Domain -replace "^\*\.", "" # Zone検索 $zoneUri = "/v1/ncpdns/domain?page=1&size=100" $zones = (Invoke-RestMethod -Uri "$NCP_DNS_API$zoneUri" ` -Headers (Get-NcpHeaders "GET" $zoneUri)).domainList $matchedZone = $zones | Where-Object { $Domain -eq $_.name -or $Domain.EndsWith(".$($_.name)") } | Select-Object -First 1 if (-not $matchedZone) { Write-Error "[エラー] NCP Global DNSで「$Domain」に対応する zoneが見つかりません。" exit 1 } $zoneId = $matchedZone.domainId $zoneName = $matchedZone.name # TXTレコードのホスト名を計算 if ($Domain -eq $zoneName) { $hostName = "_acme-challenge" } else { $sub = $Domain -replace "\.$([regex]::Escape($zoneName))$", "" $hostName = "_acme-challenge.$sub" } # TXTレコード作成 $createUri = "/v1/ncpdns/record/$zoneId" $body = @{ type = "TXT"; host = $hostName; content = $Token; ttl = 60 } | ConvertTo-Json $response = Invoke-RestMethod -Uri "$NCP_DNS_API$createUri" -Method POST ` -Headers (Get-NcpHeaders "POST" $createUri) -Body $body if (-not $response.recordId) { Write-Error "[エラー] TXTレコードの作成に失敗しました。" exit 1 } # レコード IDの一時保存(削除フックで使用) $tmpFile = "$env:TEMP\ncp_sid_$($Domain -replace '\*','_').json" @{ recordId = $response.recordId; domainId = $zoneId } | ConvertTo-Json | Set-Content $tmpFile -Encoding UTF8 # DNS反映 $applyUri = "/v1/ncpdns/domain/$zoneId/apply" Invoke-RestMethod -Uri "$NCP_DNS_API$applyUri" -Method PUT ` -Headers (Get-NcpHeaders "PUT" $applyUri) | Out-Null Write-Host "DNS TXTレコード作成完了。$DNS_PROPAGATION_WAIT秒待機中…" Start-Sleep -Seconds $DNS_PROPAGATION_WAIT ``` ### TXTレコード削除フック: dns-delete.ps1 ``` # dns-delete.ps1 — win-acme DNS-01削除フック(NCP Global DNS) # UTF-8 BOMエンコードで保存すること param( [string]$Action, [string]$Domain, [string]$RecordName, [string]$Token ) $ConfigPath = Join-Path $PSScriptRoot "config.ps1" . $ConfigPath function Get-NcpHeaders { param([string]$Method, [string]$Uri) $timestamp = [long]([datetimeoffset]::UtcNow.ToUnixTimeMilliseconds()) $message = "$Method $Uri`n$timestamp`n$NCP_ACCESS_KEY" $hmac = New-Object System.Security.Cryptography.HMACSHA256 $hmac.Key = [System.Text.Encoding]::UTF8.GetBytes($NCP_SECRET_KEY) $sig = [Convert]::ToBase64String( $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($message)) ) return @{ "x-ncp-apigw-timestamp" = "$timestamp" "x-ncp-iam-access-key" = $NCP_ACCESS_KEY "x-ncp-apigw-signature-v2" = $sig } } $Domain = $Domain -replace "^\*\.", "" $tmpFile = "$env:TEMP\ncp_sid_$($Domain -replace '\*','_').json" $sid = Get-Content $tmpFile -ErrorAction SilentlyContinue | ConvertFrom-Json if (-not $sid) { Write-Warning "[警告] 削除するレコード情報が見つかりません。" exit 0 } # TXTレコード削除 $deleteUri = "/v1/ncpdns/record/$($sid.domainId)/$($sid.recordId)" Invoke-RestMethod -Uri "$NCP_DNS_API$deleteUri" -Method DELETE ` -Headers (Get-NcpHeaders "DELETE" $deleteUri) | Out-Null # DNS反映 $applyUri = "/v1/ncpdns/domain/$($sid.domainId)/apply" Invoke-RestMethod -Uri "$NCP_DNS_API$applyUri" -Method PUT ` -Headers (Get-NcpHeaders "PUT" $applyUri) | Out-Null Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue Write-Host "DNS TXTレコード削除完了。" ```