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.

Certbotベース Linuxサーバ証明書の自動化

Prev Next

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

  1. 以下のコマンドを実行して、crontabエディタを開きます。
sudo crontab -e
  1. 以下の行を追加します(毎日午前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レコードの伝播未完了 .envDNS_PROPAGATION_SECONDS値を90~120に延長するか、dig +short TXT _acme-challenge.example.com @8.8.8.8で伝播の有無を確認
TXTレコード作成に失敗 Ncloud APIキーのエラーまたはドメイン IDが一致しない .envNCP_ACCESS_KEYNCP_SECRET_KEYNCP_DOMAIN_ID値を再確認
OV証明書発行に失敗 組織事前検証の未完了 Certificate Manager > Organizationで検証ステータスを確認
更新時にフックが実行されない renewal設定ファイルにフックのパスを未設定 /etc/letsencrypt/renewal/example.com.conf[renewalparams]セクションに manual_auth_hookmanual_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レコード削除完了。"