TroubleShooting & Study/Infra

[SSL] NginxProxyManager SSL 인증서 설정 에러 (feat. the domain's nameservers may be malfunctioning, Some challenges have failed)

DH_0518 2025. 11. 27. 02:52

 

 

DuckDNS에서 ec2 컨테이너에 무료 도메인을 할당 후, NginxProxyManager를 사용해서 SSL 인증서를 설정하려했다.

 

 

npm SSL 설정

 

 

그런데 Internal Error 발생 !!

똑같은 방식으로 다른 컨테이너 두 개에는 멀쩡하게 HTTPS 설정도 완료하고 접속도 잘 되는데, 얘는 왜이런걸까??

 

바로 portainer 켜서 로그 확인ㄱㄱ

 

 

portainer npm 로그

 

 

흠.. 그렇구나.. 너는 무언가를 하려고했지만 잘 안됐구나. 그래 고생했다..

 

"Saving debug log to /tmp/letsencrypt-log/letsencrypt.log. Some challenges have failed."

 

라고 하니까, 터미널로 해당 파일에 접속해서 에러 로그를 확인해보자.

 

 

npm 에러 로그

 

오잉? DNS 문제라고? 

dig trace를 사용해서 당장 확인해보자

 

dig trace

 

마지막 ANSWER SECTION을 보면 알겠지만, 루트 DNS 서버부터 Authoritative DNS까지 응답이 잘 나오는 것을 확인할 수 있다.

즉, duckdns의 네임서버에 내 ec2 ip주소와 domain 정보가 A레코드로 잘 등록되었다는거라 DNS 문제는 아닌걸 알 수 있다.

 

일단 npm 에러 로그를 보면 추가적인 로그가 더 있으니까 확인해보자.

 


오케이.. 예림이 그 로그 더 봐봐!

npm 에러 로그 (2)

 

 

추가 로그를 다시 확인해보면,

 

1. cerbot에서 에러가 발생했고, 뭔지는 모르겠지만 "Some challenges have failed"라네? challenges에 문제가 있군

2. "Removing /data/letsencrypt-acme-challenge/.well-known/acme-challenge/dieBjdZNrZPZkKsmHIsd8x2i6mVaSX-SoKvFuDajwa4" 라는 로그를 보니,  acme-challenge 라는 경로에 또 challenge라는 용어가 나오네. 해당 경로에서 뭔 일이 벌어졌구나!

 

라는걸 알 수 있다.

 

이제 여기서 우리가 먼저 알아야할 선행 지식들이 있다.

 

바로 LetsEncrypt, ACME, Cerbot, Webroot 에 관한 개념들이다!

이미 아는 사람들이라면 위의 로그가 무슨 뜻인지 알테니 다음으로 넘어가자.

 

해당 개념들을 모르는 사람이면 필히 읽어야 중간에 논리적인 흐름이 끊기질 않으므로, 꼭 읽고 넘어가자.

 

 

 

LetsEncrypt, ACME, Cerbot, Webroot

더보기

1) Let’s Encrypt

  • 무료 SSL 인증서를 발급해주는 인증기관(CA, Certificate Authority)
  • Nginx Proxy Manager(NPM)의 기본 SSL 발급 대상이기 때문에, SSL 신청 시 자동으로 Let’s Encrypt가 사용됨

 

2) ACME (Automated Certificate Management Environment)

  • Let’s Encrypt가 만든 인증서 자동 발급 프로토콜(표준)
  • 도메인 주인이 맞는지 인증하기 위해, [Let’s Encrypt] → [서버]로 특정 파일을 요청하여 확인하는 방식

 

3) Certbot

  • ACME 프로토콜을 실행하는 클라이언트 프로그램
  • NPM은 내부적으로 certbot을 호출하여 인증서를 발급함
  • Certbot이 challenge 파일을 만들고, Let’s Encrypt가 그 URL로 파일을 확인함
  • 이때 URL은 http://도메인/.well-known/acme-challenge/

 

4) Webroot

  • Certbot이 challenge 파일을 실제로 저장하는 물리적 디렉토리 경로.
  • 예: /data/letsencrypt-acme-challenge/.well-known/acme-challenge/토큰
  • nginx는 이 물리경로를 [URL /.well-known/acme-challenge/<토큰>] 과 정확히 매핑해야 인증이 성공함

 

 

 

자, 그럼 우리는 위의 개념들을 사용해서 다시 문제상황을 유추해보자

 

 

1. "certbot.errors.AuthorizationError: Some challenges have failed." 로그

- Let's Encrypt가 challenge 파일을 읽을때 무슨 문제가 발생했구나?

- 두 가지 경우를 생각해볼 수 있겠네
  a) cerbot의 요청이 아예 서버까지 도달 못한 경우

  b) 요청은 서버로 도착했지만, 파일을 읽을때 문제가 발생한 경우

 

 

2. "DEBUG:certbot._internal.plugins.webroot:Removing /data/letsencrypt-acme-challenge/.well-known/acme-challenge/" 로그

"DEBUG:certbot._internal.plugins.webroot:All challenges cleaned up" 로그

- 일단 이건 에러도 아니고 그냥 디버깅이니까 단순한 현상 설명이네. 오케이 계속 읽어보자

- cerbot이 challenge 파일을 "/data/letsencrypt-acme-challenge/.well-known/acme-challenge/" 경로에 만들고 삭제했나보군

- 어 근데 저 경로는 Webroot네? 이러면 Webroot와 "http://도메인/.well-known/acme-challenge/" 매핑 상태를 확인해봐야겠네

 

 

3. 1-(a) 경우를 먼저 확인 

- "http://도메인/.well-known/acme-challenge/" 에 요청을 보내고

- npm 내부에 bash로 접속해서 "/data/logs"의 "proxy-host-확인할 호스트 번호_access.log" 를 열어서, url로 요청이 잘 왔는지 확인하자!

- 오케이 GET 요청 로그 잘 찍혔고! 그럼 요청이 서버로 잘 온다는 증거니까 (b) 상황이겠군!

 

 

4. 1-(b) 경우 확인

- 일단 Webroot에 test용 파일을 만들어서, 정말로 매핑이 안되고 있는지 다시 확인해보자
(echo "HELLO" > /data/letsencrypt-acme-challenge/.well-known/acme-challenge/test-npm)

- 좋아. Webroot에 "test-npm"을 만들었고, 이제 검증 url로 접속하면 HELLO-ACME가 출력되어야겠군

 

 

5. 검증 URL (http://도메인/.well-known/acme-challenge/test-npm) 접속

- "http://도메인/.well-known/acme-challenge/test"로 curl 요청을 보내면?
(curl http://도메인/.well-known/acme-challenge/test-npm)

curl 결과

 

- 404 발생!!!!!!!!!!!!!!!!!!

- 이를 통해 challenge 파일은 잘 만들어지지만, nginx가 내부적으로 webroot가 아니라 다른 경로를 탐색하고 있다는 것을 검증했다

 

 

6. 명시적으로 매핑 해주기

- 이제 우리가 해줄건, nginx가 'Webroot'와 '검증 url'을 잘 매핑하도록, 명시적으로 설정해주는 것이다

- nginxproxymanager의 host, advanced 설정에 들어가서 다음 매핑 설정을 추가해주자

 

location ^~ /.well-known/acme-challenge/ {
    root /data/letsencrypt-acme-challenge;
}

 

 

 

 

7. 다시 검증 테스트

- 매핑을 완료해줬으니, 다시 Webroot에 test파일을 만들고, curl로 요청을 보내면, 응답값의 body에 "HELLO-ACME"가 포함되어야 한다

- 끼얏호우!

 

 

8. SSL 인증 재요청

- 모든 준비가 끝났다. 매핑 설정을 다시 완료하고 테스트도 마쳤으니 다시 SSL 인증을 요청하자

 

 

????????????????????????????????????????????????????????????????

인증서 발급은 됐는데 Offline이요?????????????????????????????????????

 

 

해당 호스트 conf가 없다구요?????????????????????????????????????????

 

 

 

9. 일단 Advanced 설정 삭제

 

- 일단 진정하고.. 다시 인증서를 None으로 만드니까 호스트.conf이 생성되고 status가 정상으로 된걸 볼 수 있다

- 이를 통해 알 수 있는건, 인증서는 발급되었고! 바뀐건 advanced 설정 뿐..

- 깨끗한 상태를 만들기 위해 host 자체를 삭제 후 재설정을 진행하자.

- 그리고 advanced 설정이 없이, 방금 발급했던 인증서를 적용해보자

 

- 성공 ????? 왜 되는건데???

 

 

 

 

 

 

 

 

 

결론

 

 

 

초기에 실패했던 이유를 모르기 때문에 추론만 진행해보겠다.

 

 

1. 모종의 이유로 처음 proxy 호스트를 생성할때 문제 발생

2. 이 과정에서 Webroot와 acme-challenge 라우팅이 누락되었고, 이로 인해 SSL 인증서 발급이 실패

3. 어쩔 수 없이 수동으로 '라우팅 설정'을 추가

4. 추가한 설정 덕분에 SSL 인증서 발급 성공

5. 하지만 NPM 자체적으로도 내가 설정한 '라우팅 설정'과 똑같은 설정을 생성하려고 하기에, 동일한 location 블록이 생성되어 config validation이 실패

6. 이로 인해 인증서는 발급됐지만, host.conf가 삭제되고 status가 offline

7. 따라서 다시 proxy host를 삭제하고 깨끗한 상태로 인증서 적용 -> 성공

 

정도로 볼 수 있을 것 같다.. (정말로 아직도 처음에 왜 안됐는지 모르겠다)

 

 

 

 

현재 시간 새벽 2시47분..

처음부터 host 삭제하고 재발급 요청을 보냈으면 해결됐을수도 있었겠다 라는 생각에 살짝 현타가 올 것 같지만, 그래도 덕분에 SSL 인증에 관해서 한단계 더 깊은 이해를 했다는 뿌듯함이 더 크다.

 

 

이 문제는 나중에 두고두고 다시 곱씹어봐야지.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

** 본 글은 개인 학습 및 경험을 기반으로 작성되었으며, 글의 정보중 일부는 AI 도구, 공식 문서, 블로그 등 다양한 자료를 참고하였습니다.
** 부정확한 부분이 있다면 언제든지 피드백 부탁드립니다.