--- title: "Certbot 기반 Linux 서버 인증서 자동화" slug: "certificate-linuxacme" updated: 2026-09-17T09:08:15Z published: 2026-09-17T09:08:15Z canonical: "guide.ncloud-docs.com/certificate-linuxacme" 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. # Certbot 기반 Linux 서버 인증서 자동화 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 사용 준비](/docs/certificatemanager-acme-prepare)의 모든 단계를 완료해 주십시오. 다음 항목이 미리 준비되어야 합니다. - 발급된 EAB Key ID 및 EAB HMAC Key - 사용할 도메인 검증 방식 결정 (DNS-01 동적 방식 또는 사전 검증 방식) - OV 인증서를 사용하는 경우, **Certificate Manager** > **Organization**에서 조직 검증 완료 ## 도메인 검증 방식 선택 Certbot으로 인증서를 발급하기 전에, 도메인 소유 검증(DCV)을 어떤 방식으로 수행할지 결정해야 합니다. Ncloud Trust CA는 두 가지 방식을 지원하며, 어떤 방식을 선택하느냐에 따라 준비 작업과 발급 명령어가 달라집니다. | 구분 | DNS-01 동적 방식 | 사전 검증 방식 | | --- | --- | --- | | 발급 시 DCV | 발급·갱신할 때마다 `_acme-challenge` TXT 레코드로 검증 | 도메인 사전 검증 등록 시 1회 완료, 발급 시점에는 생략 | | 훅 스크립트 | **필요** (`--manual-auth-hook` / `--manual-cleanup-hook`) | **불필요** | | 사전 준비 | DNS API 연동 훅 스크립트 구성 (2단계) | 콘솔 / Open API로 도메인 사전 검증 등록 | | 권장 authenticator | `--manual` + 훅 | `--standalone` | | 적합한 경우 | DNS를 API로 자동 제어할 수 있는 환경, 와일드카드·다수 도메인 | DNS 훅 구성이 어렵거나, 이미 사전 검증을 사용하는 환경 | 참고 이후 단계는 검증 방식에 따라 다음과 같이 진행됩니다. - **DNS-01 동적 방식**: 1단계(설치) → 2단계(훅 스크립트 설정) → 3단계 A(동적 방식 발급) → 4단계(자동 갱신) - **사전 검증 방식**: 1단계(설치) → **2단계 건너뜀** → 3단계 B(사전 검증 방식 발급) → 4단계(자동 갱신) ## 1단계: Certbot 설치 운영체제 환경에 맞는 명령어를 실행하여 Certbot을 설치해 주십시오. 자세한 설치 방법은 [Certbot 공식 설치 가이드](https://certbot.eff.org/instructions)를 참조해 주십시오. **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 동적 방식** 전용입니다. **사전 검증 방식**을 사용하는 경우 이 단계는 필요 없으므로 건너뛰고 [3단계: 인증서 발급](/docs/certificate-linuxacme#3%EB%8B%A8%EA%B3%84%EC%9D%B8%EC%A6%9D%EC%84%9C%EB%B0%9C%EA%B8%89)의 **방식 B**로 진행해 주십시오. 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 훅 스크립트 예시](/docs/certificate-linuxacme#NcloudGlobalDNS%ED%9B%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%98%88%EC%8B%9C)를 참조해 주십시오. 다른 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단계: 인증서 발급 선택한 도메인 검증 방식에 따라 **방식 A(DNS-01 동적 방식)** 또는 **방식 B(사전 검증 방식)** 중 하나로 진행해 주십시오. 두 방식 모두 아래 공통 파라미터를 사용하며, 대괄호(`[ ]`) 항목을 실제 값으로 교체하여 실행합니다. | 파라미터 | 설명 | 예시 | | --- | --- | --- | | `[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` | 참고 `--eab-kid` / `--eab-hmac-key`는 **해당 서버에서 ACME 계정을 최초 등록할 때 1회만** 사용됩니다. 계정이 등록된 후에는 갱신 시 다시 지정할 필요가 없습니다. ### 방식 A: DNS-01 동적 방식 [2단계](/docs/certificate-linuxacme#2%EB%8B%A8%EA%B3%84DNS%ED%9B%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%84%A4%EC%A0%95)에서 훅 스크립트를 구성한 뒤 진행합니다. `--manual` authenticator와 훅 스크립트를 사용하여 발급 시점에 DNS TXT 레코드를 자동으로 생성·검증·삭제합니다. **단일 도메인** ``` 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 확장을 방지하기 위해 도메인을 따옴표로 감싸 주십시오. ### 방식 B: 사전 검증 방식 콘솔 또는 Open API로 도메인을 **사전검증 등록**하면 그 시점에 DCV가 1회 완료됩니다. 이 경우 ACME 서버는 발급 요청 시 order/authz를 처음부터 검증 완료 상태로 응답하므로, Certbot이 DNS 챌린지·훅 단계를 통째로 건너뜁니다. 따라서 **훅 스크립트도, 웹루트 경로도 필요 없습니다.** 다만 Certbot은 구조상 authenticator(도메인 증명 방식) 플러그인을 하나는 반드시 지정해야 하므로, 실제 챌린지가 일어나지 않는 사전 검증 방식에서는 **`--standalone`을 "형식상" 지정**하는 것이 가장 간단합니다. 사전 검증 상태에서는 `--standalone`이 임시 웹 서버나 포트를 실제로 열지 않습니다. 참고 사전 검증 방식의 동작 흐름은 다음과 같습니다. ``` [사전 준비] 콘솔/Open API로 도메인을 사전 검증 등록할 때 DCV가 이미 1회 완료됨 ────────────────────────────────────────────────────────── 1. 계정 등록 (EAB 바인딩) 2. newOrder → order 즉시 "ready", authz "valid" (검증완료 상태로 응답) 3. Certbot이 authz가 이미 valid임을 확인 → 챌린지·훅 단계 전체 건너뜀 (TXT 생성 안 함, auth-hook/cleanup-hook 호출 안 함) 4. finalize (CSR 제출) 5. 서버 processing → valid → 인증서 다운로드 ``` **단일 도메인** ``` sudo certbot certonly \ --standalone \ --key-type rsa \ --rsa-key-size 2048 \ --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 \ --standalone \ --key-type rsa \ --rsa-key-size 2048 \ --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 ``` 주의 `-d`로 지정하는 **모든 도메인(및 와일드카드)이 사전 검증 등록을 마친 상태**여야 합니다. 사전 검증되지 않은 도메인이 포함되면 해당 authz가 `valid`로 응답되지 않아 발급이 실패합니다. 발급 성공 후 다음 명령어로 인증서를 확인해 주십시오. (검증 방식과 무관하게 동일합니다.) ``` sudo certbot certificates sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \ -noout -subject -issuer -dates ``` ## 4단계: 자동 갱신 설정 Certbot은 인증서 만료 30일 전부터 갱신을 시도합니다. 발급 시 사용한 옵션(검증 방식, 훅 스크립트 경로 등)은 최초 발급 시 자동으로 저장되므로, `certbot renew`만 실행하면 최초 발급과 동일한 방식으로 갱신이 수행됩니다. - **DNS-01 동적 방식**: 갱신 시 저장된 훅 경로로 DNS 검증이 자동 수행됩니다. - **사전 검증 방식**: 사전 검증 상태가 유지되는 한 갱신 시에도 훅이 호출되지 않으며, `--standalone`으로 즉시 갱신됩니다. 먼저 다음 명령어로 갱신 동작을 시뮬레이션하여 확인해 주십시오. ``` 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 기준) ``` 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 ``` ``` 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` 또는 `Unsupported key type or size. Only RSA-2048 is accepted.` | 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` 값 재확인 | | `Could not choose an appropriate plugin: authenticator` | authenticator 미지정 | 동적 방식은 `--manual`, 사전 검증 방식은 `--standalone`을 지정 | | 사전 검증 방식인데 발급 실패 (authz가 valid로 응답되지 않음) | `-d`에 사전 검증되지 않은 도메인 포함 | 콘솔/Open API에서 해당 도메인의 사전 검증 등록 상태 확인 | | 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 환경에서의 참고용 예시입니다. 스크립트의 수정, 설정, 실행 환경 및 결과에 대한 책임은 사용자에게 있으며, 네이버클라우드는 해당 스크립트의 동작에 대한 기술 지원을 제공하지 않습니다. 실제 운영 환경 적용 전 충분한 테스트를 거칠 것을 권장합니다. ### 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 레코드 삭제 완료." ```