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.

Linux 서버

Prev Next

The latest service changes have not yet been reflected in this content. We will update the content as soon as possible. Please refer to the Korean version for information on the latest updates.

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 설치

운영체제 환경에 맞는 명령어를 실행하여 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_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 환경에서의 참고용 예시입니다. 스크립트의 수정, 설정, 실행 환경 및 결과에 대한 책임은 사용자에게 있으며, 네이버클라우드는 해당 스크립트의 동작에 대한 기술 지원을 제공하지 않습니다. 실제 운영 환경 적용 전 충분한 테스트를 거칠 것을 권장합니다.

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 레코드 삭제 완료."