Classic/VPC環境で利用できます。
Certbotを使用して Linuxサーバ環境で証明書の発行と更新を自動化する方法をご案内します。
Ubuntu 22.04/24.04 LTSおよび Rocky Linux 8/RHEL 8環境を基準にして作成されました。
Ncloud Trust CAは RSA 2048のみ許可します。ECDSAまたは RSA 4096でリクエストすると、証明書の発行に失敗します。以下のガイドのコマンドには、--key-type rsa --rsa-key-size 2048オプションが含まれています。任意に変更しないでください。
このガイドは Certbotを基準に提供されます。RFC 8555に準拠した他の ACMEクライアントも使用できますが、公式のテクニカルサポートは Certbot基準でのみ提供されます。
始める前に
このガイドに進む前に、ACME の仕様の全ての手順を完了してください。以下の項目を予め準備しておく必要があります。
- 発行された EAB Key IDおよび EAB HMAC Key
- 使用するドメイン検証方法を決定(DNS-01動的方法または事前検証方法)
- OV証明書を使用する場合、Certificate Manager > Organizationで組織検証を完了
ステップ1: Certbotインストール
OS環境に適したコマンドを実行し、Certbotをインストールします。インストール方法の詳細は、Certbot公式インストールガイドをご参照ください。
Ubuntu 22.04 / 24.04 LTS
sudo apt update
sudo apt install -y curl openssl jq python3
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Rocky Linux 8 / RHEL 8
sudo dnf install -y epel-release
sudo dnf install -y curl openssl jq python3 snapd
sudo systemctl enable --now snapd.socket
sudo ln -s /var/lib/snapd/snap /snap
# 再ログイン後に実行
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
インストールが完了すると、以下のコマンドでバージョンを確認します。Certbotは2.x以上を推奨します。
certbot --version
jq --version
openssl version
ステップ2: DNSフックスクリプトの設定
DNS-01チャレンジ自動化のために、Certbotの --manual-auth-hookおよび --manual-cleanup-hookオプションに DNS TXTレコードを作成・削除するスクリプトを連携します。
タスクディレクトリは、以下のように構成されます。
/opt/acme-ncp-dns/
├── .env # 設定ファイル(直接作成、外部への開示禁止)
├── ncp-auth.sh # Certbot認証フック(TXTレコード作成)
└── ncp-cleanup.sh # Certbotクリーンアップフック(TXTレコード削除)
ディレクトリを作成します。
sudo mkdir -p /opt/acme-ncp-dns
Ncloud Global DNSを使用する場合、フックスクリプトの例は Ncloud Global DNSフックスクリプトの例をご参照ください。他の DNSプロバイダを使用する場合、そのプロバイダの APIを呼び出して同じロールを実行するスクリプトを作成します。
スクリプトファイルを配置した後、以下のように設定ファイルを作成し、実行権限を付与します。
sudo vi /opt/acme-ncp-dns/.env
sudo chmod 600 /opt/acme-ncp-dns/.env
sudo chmod +x /opt/acme-ncp-dns/ncp-auth.sh
sudo chmod +x /opt/acme-ncp-dns/ncp-cleanup.sh
.envファイルの内容:
# Ncloud API認証キー(コンソールの My Account > アカウントとセキュリティ管理 > セキュリティ管理 > アクセス管理 > API認証キー)
NCP_ACCESS_KEY="YOUR_NCP_ACCESS_KEY"
NCP_SECRET_KEY="YOUR_NCP_SECRET_KEY"
# NCP Global DNS APIエンドポイント(変更不要)
NCP_DNS_API="https://globaldns.apigw.ntruss.com"
# Global DNSのドメイン ID
# NCPコンソール > Global DNS > F12開発者ツール > ドメインクリック > URLの数字(例: dns/domain/36019)
NCP_DOMAIN_ID="YOUR_DOMAIN_ID"
# Global DNSに登録されたルートドメイン(例: example.com)
NCP_ZONE_DOMAIN="example.com"
# DNS TXTレコードの伝播待ち時間(秒)。検証に失敗した場合は、90~120に延長します。
DNS_PROPAGATION_SECONDS=60
| 項目 | 説明 | 確認方法 |
|---|---|---|
NCP_ACCESS_KEY |
Ncloud API Access Key | コンソールの My Account > アカウントとセキュリティ管理 > セキュリティ管理 > アクセス管理 > API認証キー |
NCP_SECRET_KEY |
Ncloud API Secret Key | コンソールの My Account > アカウントとセキュリティ管理 > セキュリティ管理 > アクセス管理 > API認証キー |
NCP_DOMAIN_ID |
Global DNSドメイン数字 ID | コンソールの Global DNS > F12 > URL数字を確認 |
NCP_ZONE_DOMAIN |
DNS領域のルートドメイン | Global DNSに登録されたドメイン(例: example.com) |
DNS_PROPAGATION_SECONDS |
DNSの伝播待ち時間 | デフォルト60秒。検証に失敗した場合は、90~120に調整 |
ステップ3: 証明書発行
以下のコマンドの括弧([ ])内の項目を、実際の値に置き換えて実行します。
| パラメータ | 説明 | 例 |
|---|---|---|
[ACME_DIRECTORY_URL] |
ACMEを始める前の準備段階で確認した ACMEディレクトリ URL | https://acme.navercloudtrust.com/acme/directory |
[EAB_KEY_ID] |
EAB認証情報発行時に提供された Key ID | abc123... |
[EAB_HMAC_KEY] |
EAB認証情報発行時に提供された HMAC Key | xyz789... |
[ADMIN_EMAIL] |
証明書の期限切れ通知を送信するメールアドレス | admin@example.com |
シングルドメイン
sudo certbot certonly \
--manual \
--preferred-challenges dns \
--key-type rsa \
--rsa-key-size 2048 \
--manual-auth-hook /opt/acme-ncp-dns/ncp-auth.sh \
--manual-cleanup-hook /opt/acme-ncp-dns/ncp-cleanup.sh \
--server [ACME_DIRECTORY_URL] \
--eab-kid "[EAB_KEY_ID]" \
--eab-hmac-key "[EAB_HMAC_KEY]" \
--agree-tos \
--email [ADMIN_EMAIL] \
--non-interactive \
-d example.com
サブドメインを含む(SAN)
sudo certbot certonly \
--manual \
--preferred-challenges dns \
--key-type rsa \
--rsa-key-size 2048 \
--manual-auth-hook /opt/acme-ncp-dns/ncp-auth.sh \
--manual-cleanup-hook /opt/acme-ncp-dns/ncp-cleanup.sh \
--server [ACME_DIRECTORY_URL] \
--eab-kid "[EAB_KEY_ID]" \
--eab-hmac-key "[EAB_HMAC_KEY]" \
--agree-tos \
--email [ADMIN_EMAIL] \
--non-interactive \
-d example.com \
-d www.example.com \
-d api.example.com
ワイルドカードドメイン
sudo certbot certonly \
--manual \
--preferred-challenges dns \
--key-type rsa \
--rsa-key-size 2048 \
--manual-auth-hook /opt/acme-ncp-dns/ncp-auth.sh \
--manual-cleanup-hook /opt/acme-ncp-dns/ncp-cleanup.sh \
--server [ACME_DIRECTORY_URL] \
--eab-kid "[EAB_KEY_ID]" \
--eab-hmac-key "[EAB_HMAC_KEY]" \
--agree-tos \
--email [ADMIN_EMAIL] \
--non-interactive \
-d "*.example.com"
ワイルドカード(*)を含む場合、シェルの glob拡張を防ぐためにドメインを引用符で囲みます。
発行に成功した後、以下のコマンドで証明書を確認します。
sudo certbot certificates
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
-noout -subject -issuer -dates
ステップ4: 自動更新設定
Certbotは、証明書の有効期限30日前から更新を試みます。フックスクリプトのパスは、初回発行時に自動的に保存されるため、「certbot
renew」のみ実行すれば、DNS検証が自動的に実行されます。
まずは以下のコマンドで更新動作をシミュレーションして確認します。
sudo certbot renew --dry-run
方法1: systemdタイマー(推奨)
snapで Certbotをインストールした場合、更新タイマーが自動的に登録されます。以下のコマンドでステータスを確認します。
sudo systemctl status snap.certbot.renew.timer
# タイマーがない場合は、手動で登録
sudo systemctl enable --now snap.certbot.renew.timer
方法2: Crontab
- 以下のコマンドを実行して、crontabエディタを開きます。
sudo crontab -e
- 以下の行を追加します(毎日午前3時に更新を試行)。
0 3 * * * /usr/bin/certbot renew --quiet 2>&1 | logger -t certbot-renew
--quietオプションでは、更新なしでスキップする場合に出力を抑制します。logger -t certbot-renewは、更新結果をシステムログに記録します(journalctl -t certbot-renewで確認)。- 更新ログは
/var/log/letsencrypt/letsencrypt.logでも確認できます。
ウェブサーバ証明書を適用
発行された証明書ファイルの保存場所は、次の通りです。
| ファイル | 説明 |
|---|---|
/etc/letsencrypt/live/<도메인>/cert.pem |
証明書 |
/etc/letsencrypt/live/<도메인>/chain.pem |
中間 CAチェーン |
/etc/letsencrypt/live/<도메인>/fullchain.pem |
証明書 + チェーン(ウェブサーバ設定を推奨) |
/etc/letsencrypt/live/<도메인>/privkey.pem |
プライベートキー(外部への開示禁止) |
Nginx
/etc/nginx/sites-available/example.comファイルに以下の内容を入力します。
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
location / {
root /var/www/html;
index index.html;
}
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
sudo nginx -t
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo systemctl reload nginx
更新後に自動再ロードするため、deploy-hookを設定します。
sudo vi /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/bash
systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Apache
/etc/apache2/sites-available/example.com-ssl.confファイルに以下の内容を入力します。(Ubuntu基準)
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLHonorCipherOrder off
DocumentRoot /var/www/html
</VirtualHost>
sudo a2enmod ssl
sudo a2ensite example.com-ssl
sudo apache2ctl configtest
sudo systemctl reload apache2
sudo vi /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh
#!/bin/bash
systemctl reload apache2
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh
トラブルシューティング
| 症状 | 原因 | 対応 |
|---|---|---|
The key ID was not found |
EAB Key IDエラーまたは使用済みのキー | コンソールで EABキーを再発行し、コマンドを再実行 |
unauthorized |
EAB HMAC Keyエラー | HMAC Key値を再確認。空白・改行の有無を確認 |
Error finalizing order :: invalid CSR |
RSA 2048以外のキータイプを使用 | コマンドに --key-type rsa --rsa-key-size 2048オプションを追加しているかを確認 |
DNS problem: NXDOMAIN |
TXTレコードの伝播未完了 | .envの DNS_PROPAGATION_SECONDS値を90~120に延長するか、dig +short TXT _acme-challenge.example.com @8.8.8.8で伝播の有無を確認 |
| TXTレコード作成に失敗 | Ncloud APIキーのエラーまたはドメイン IDが一致しない | .envの NCP_ACCESS_KEY、NCP_SECRET_KEY、NCP_DOMAIN_ID値を再確認 |
| OV証明書発行に失敗 | 組織事前検証の未完了 | Certificate Manager > Organizationで検証ステータスを確認 |
| 更新時にフックが実行されない | renewal設定ファイルにフックのパスを未設定 | /etc/letsencrypt/renewal/example.com.confの [renewalparams]セクションに manual_auth_hook、manual_cleanup_hookパスを直接追加 |
sudo tail -100 /var/log/letsencrypt/letsencrypt.log
セキュリティに関する勧告
.envファイルは必ず権限を600に設定します。(sudo chmod 600 /opt/acme-ncp-dns/.env).envファイルを Gitリポジトリや共有フォルダに配置しません。- Ncloud APIキーには、Global DNSサービスのみに対して最小権限を付与することを推奨します。
- 証明書のプライベートキー(
privkey.pem)が外部に漏れないようご注意ください。
Ncloud Global DNSフックスクリプトの例
以下のスクリプトは、Ncloud Global DNS環境における参考例です。スクリプトの変更・設定・実行環境および結果に関する責任はユーザーにあり、NAVERクラウドは当該スクリプトの動作に関するテクニカルサポートを提供しません。実際に運用環境へ適用する前に、十分なテストを行うことを推奨します。
ncp-auth.sh
#!/bin/bash
# ncp-auth.sh — Certbot DNS-01認証フック(NCP Global DNS)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/.env"
make_signature() {
local method=$1 uri=$2 timestamp=$3
# nl変数($)への割り当てを削除し、printfで改行を行うことでエディタのパースエラーを防止
printf "%s %s\n%s\n%s" "${method}" "${uri}" "${timestamp}" "${NCP_ACCESS_KEY}" \
| openssl dgst -sha256 -hmac "${NCP_SECRET_KEY}" -binary | base64
}
# ワイルドカードを削除した後、TXTホストを計算
CLEAN_DOMAIN=$(echo "${CERTBOT_DOMAIN}" | sed 's/^\*\.//')
if [ "${CLEAN_DOMAIN}" = "${NCP_ZONE_DOMAIN}" ]; then
HOST="_acme-challenge"
else
SUB="${CLEAN_DOMAIN%.${NCP_ZONE_DOMAIN}}"
HOST="_acme-challenge.${SUB}"
fi
# TXTレコード作成
TIMESTAMP=$(python3 -c 'import time; print(int(time.time() * 1000))' 2>/dev/null || date +%s000)
URI="/dns/v1/ncpdns/record/${NCP_DOMAIN_ID}"
SIGNATURE=$(make_signature "POST" "${URI}" "${TIMESTAMP}")
RESPONSE=$(curl -s --connect-timeout 10 --max-time 30 -X POST \
"${NCP_DNS_API}${URI}" \
-H "Content-Type: application/json" \
-H "x-ncp-apigw-timestamp: ${TIMESTAMP}" \
-H "x-ncp-iam-access-key: ${NCP_ACCESS_KEY}" \
-H "x-ncp-apigw-signature-v2: ${SIGNATURE}" \
-d "[{\"host\":\"${HOST}\",\"type\":\"TXT\",\"content\":\"${CERTBOT_VALIDATION}\",\"ttl\":300,\"lbRegionCode\":\"KR\"}]")
SID=$(echo "${RESPONSE}" | jq -r '.[0].sid // empty')
if [ -z "${SID}" ]; then
echo "[エラー] TXTレコード作成に失敗しました: ${RESPONSE}" >&2
exit 1
fi
echo "${SID}" > "/tmp/ncp_sid_${CERTBOT_DOMAIN}.txt"
# 変更内容を反映
TIMESTAMP=$(python3 -c 'import time; print(int(time.time() * 1000))' 2>/dev/null || date +%s000)
URI_APPLY="/dns/v1/ncpdns/record/apply/${NCP_DOMAIN_ID}"
SIGNATURE=$(make_signature "PUT" "${URI_APPLY}" "${TIMESTAMP}")
curl -s --connect-timeout 10 --max-time 30 -X PUT \
"${NCP_DNS_API}${URI_APPLY}" \
-H "Content-Type: application/json" \
-H "x-ncp-apigw-timestamp: ${TIMESTAMP}" \
-H "x-ncp-iam-access-key: ${NCP_ACCESS_KEY}" \
-H "x-ncp-apigw-signature-v2: ${SIGNATURE}" \
-d '{}' > /dev/null
echo "DNS TXTレコード作成完了。${DNS_PROPAGATION_SECONDS}秒待機中…"
sleep "${DNS_PROPAGATION_SECONDS}"
ncp-cleanup.sh
#!/bin/bash
# ncp-cleanup.sh — Certbot DNS-01クリーンアップフック(NCP Global DNS)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/.env"
make_signature() {
local method=$1 uri=$2 timestamp=$3
printf "%s %s\n%s\n%s" "${method}" "${uri}" "${timestamp}" "${NCP_ACCESS_KEY}" \
| openssl dgst -sha256 -hmac "${NCP_SECRET_KEY}" -binary | base64
}
TMP_FILE="/tmp/ncp_sid_${CERTBOT_DOMAIN}.txt"
SID=$(cat "${TMP_FILE}" 2>/dev/null || true)
if [ -z "${SID}" ]; then
echo "[警告] 削除するレコード IDが見つかりません。" >&2
exit 0
fi
# TXTレコード削除(SIDをリクエストボディに配列で渡す)
TIMESTAMP=$(python3 -c 'import time; print(int(time.time() * 1000))' 2>/dev/null || date +%s000)
URI="/dns/v1/ncpdns/record/${NCP_DOMAIN_ID}"
SIGNATURE=$(make_signature "DELETE" "${URI}" "${TIMESTAMP}")
curl -s --connect-timeout 10 --max-time 30 -X DELETE \
"${NCP_DNS_API}${URI}" \
-H "Content-Type: application/json" \
-H "x-ncp-apigw-timestamp: ${TIMESTAMP}" \
-H "x-ncp-iam-access-key: ${NCP_ACCESS_KEY}" \
-H "x-ncp-apigw-signature-v2: ${SIGNATURE}" \
-d "[${SID}]" > /dev/null
# 変更内容を反映
TIMESTAMP=$(python3 -c 'import time; print(int(time.time() * 1000))' 2>/dev/null || date +%s000)
URI_APPLY="/dns/v1/ncpdns/record/apply/${NCP_DOMAIN_ID}"
SIGNATURE=$(make_signature "PUT" "${URI_APPLY}" "${TIMESTAMP}")
curl -s --connect-timeout 10 --max-time 30 -X PUT \
"${NCP_DNS_API}${URI_APPLY}" \
-H "Content-Type: application/json" \
-H "x-ncp-apigw-timestamp: ${TIMESTAMP}" \
-H "x-ncp-iam-access-key: ${NCP_ACCESS_KEY}" \
-H "x-ncp-apigw-signature-v2: ${SIGNATURE}" \
-d '{}' > /dev/null
rm -f "${TMP_FILE}"
echo "DNS TXTレコード削除完了。"