13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] Nginx SSL

  • [보안] 안전한 웹 서버를 위한 TLS/SSL 설정 베스트 프랙티스 체크리스트 10가지

    [보안] 안전한 웹 서버를 위한 TLS/SSL 설정 베스트 프랙티스 체크리스트 10가지

    [보안] TLS/SSL 설정 베스트 프랙티스 체크리스트 10가지

    TLS SSL 보안 설정은 웹 서버를 운영할 때 가장 먼저 점검해야 하는 기본기입니다. 서비스는 잘 열렸는데 브라우저에 경고가 뜨거나, HTTPS는 붙었는데 설정이 허술해서 취약한 암호 스위트(cipher suite, 암호군)가 열려 있는 경우가 생각보다 많거든요. 저도 처음 홈랩에서 Nginx SSL을 붙일 때는 '자물쇠만 뜨면 끝 아닌가?' 싶었는데, 실제로 써보니까 그 뒤에 챙겨야 할 게 꽤 많았습니다. 특히 웹 서버 보안은 한 번 설정하고 끝나는 일이 아니라서, 점검 가능한 체크리스트 형태로 잡아두는 게 진짜 편하더라고요.

    이번 글에서는 안전한 웹 서버를 위해 제가 현업과 홈랩에서 반복해서 확인하는 TLS/SSL 설정 베스트 프랙티스 10가지를 정리해보겠습니다. Nginx SSL, Apache SSL 둘 다 적용할 수 있게 예시를 넣었고, 중간중간 제가 삽질했던 포인트도 같이 적어둘게요. 혹시 '인증서만 설치했는데 왜 점수가 안 나오지?' 같은 경험 있으신가요? 딱 그런 분들께 맞는 checklist 글입니다.

    TLS SSL 보안 설정이 적용된 웹 서버 아키텍처 개요 이미지

    인터넷 사용자, 로드밸런서, 웹 서버, 인증서, HTTPS 흐름이 한눈에 보이는 개요 이미지입니다.

    TLS/SSL이 뭔지부터 짧게 정리해보겠습니다

    쉽게 말해 TLS(Transport Layer Security, 전송 계층 보안)는 클라이언트와 서버 사이 통신을 암호화해서 중간에서 내용을 훔쳐보거나 변조하기 어렵게 만드는 기술입니다. SSL(Secure Sockets Layer)은 예전 이름에 가깝고, 요즘 실무에서는 대부분 TLS를 쓰지만 여전히 'SSL 인증서', 'Nginx SSL' 같은 표현이 널리 쓰이죠.

    여기서 중요한 포인트는 HTTPS를 켰다고 해서 곧바로 안전한 건 아니라는 점입니다. 어떤 프로토콜 버전을 허용하는지, 어떤 암호 알고리즘을 쓰는지, 인증서 체인이 올바른지, HTTP에서 HTTPS로 강제 전환이 되는지 같은 운영 설정이 같이 맞아야 합니다. 그래서 저는 아래 10가지를 묶어서 봅니다.

    TLS/SSL 보안 설정 체크리스트 10가지

    1. TLS 1.2, TLS 1.3만 허용하기
    2. 구형 SSL/TLS 프로토콜 비활성화하기
    3. 안전한 Cipher Suite(암호군) 사용하기
    4. 신뢰 가능한 인증서 체인 전체 구성하기
    5. HTTP를 HTTPS로 강제 리다이렉트하기
    6. HSTS(HTTP Strict Transport Security, 강제 HTTPS 정책) 적용하기
    7. OCSP Stapling(인증서 상태 확인 최적화) 검토하기
    8. 개인키 권한 최소화 및 자동 갱신 점검하기
    9. 보안 헤더와 쿠키 속성 함께 점검하기
    10. 외부 도구와 명령어로 반드시 검증하기

    Nginx SSL과 Apache SSL에 공통으로 적용되는 핵심 원칙

    항목 왜 중요한가 권장 방향
    프로토콜 구형 버전은 알려진 약점이 많습니다 TLS 1.2 이상만 허용
    암호군 취약한 알고리즘 허용 시 전체 보안이 약해집니다 현대적 기본값 사용
    리다이렉트 사용자가 평문 HTTP로 들어오면 보호가 깨집니다 HTTPS 강제 전환
    인증서 체인 중간 인증서 누락 시 접속 오류가 발생할 수 있습니다 full chain 구성
    검증 설정 파일만 보고는 실수 찾기 어렵습니다 명령어와 외부 검사 병행

    실전 구현: Nginx SSL 보안 설정 예시

    제가 직접 해보니 Nginx는 설정이 비교적 단순해서 체크리스트를 반영하기 좋았습니다. 다만 처음엔 인증서 파일 경로와 체인 파일 개념이 헷갈려서 삽질 좀 했습니다 ㅎㅎ 아래 예시는 일반적인 리버스 프록시(reverse proxy, 역방향 프록시) 웹 서버 기준입니다.

    1. TLS 버전 제한

    server {
        listen 443 ssl http2;
        server_name example.com;
    
        ssl_protocols TLSv1.2 TLSv1.3;
    }

    핵심은 SSLv3, TLSv1.0, TLSv1.1 같은 구형 프로토콜을 열어두지 않는 것입니다. 오래된 클라이언트 호환성이 아쉽긴 한데, 요즘은 보안 측면에서 닫는 쪽이 맞습니다.

    2. 인증서와 개인키 연결

    server {
        listen 443 ssl http2;
        server_name example.com;
    
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    }

    여기서 fullchain.pem을 쓰는 이유가 중요합니다. 서버 인증서만 달랑 넣으면 브라우저나 일부 클라이언트에서 체인 검증 실패가 날 수 있거든요.

    3. 권장 보안 옵션 추가

    server {
        listen 443 ssl http2;
        server_name example.com;
    
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;
    
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    }

    HTTP/2는 성능상 이점이 있어 많이 쓰지만, 중요한 건 성능보다도 보안 헤더를 빠뜨리지 않는 것입니다. TLS SSL 보안 설정을 한다고 하면서 헤더는 비워두는 경우가 의외로 많습니다.

    4. HTTP에서 HTTPS로 강제 전환

    server {
        listen 80;
        server_name example.com;
        return 301 https://$host$request_uri;
    }

    이건 정말 기본인데, 운영 들어가면 종종 빠집니다. 특히 오래된 북마크나 외부 링크가 HTTP를 타고 들어오는 경우가 있어서 웹 서버 보안 관점에서는 꼭 넣는 편이 좋습니다.

    Nginx SSL과 Apache SSL 설정 비교를 보여주는 이미지

    Nginx SSL과 Apache SSL에서 프로토콜, 인증서, 리다이렉트 설정이 어떻게 대응되는지 비교하는 이미지입니다.

    실전 구현: Apache SSL 보안 설정 예시

    Apache SSL도 원리는 같습니다. 문법만 다를 뿐이에요. 현업에서 레거시 시스템은 아직 Apache 비중이 꽤 있어서, 이 부분도 같이 알아두면 좋습니다.

    <VirtualHost *:443>
        ServerName example.com
    
        SSLEngine on
        SSLProtocol -all +TLSv1.2 +TLSv1.3
    
        SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    
        Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
        Header always set X-Content-Type-Options "nosniff"
        Header always set X-Frame-Options "SAMEORIGIN"
    </VirtualHost>
    
    <VirtualHost *:80>
        ServerName example.com
        Redirect permanent / https://example.com/
    </VirtualHost>

    Apache SSL에서 자주 놓치는 건 모듈 활성화 여부입니다. 배포판에 따라 mod_ssl, headers 모듈이 필요하니 로드 상태를 확인하세요. 저도 처음엔 설정 다 넣고 왜 헤더가 안 보이나 했었는데, 알고 보니 모듈이 빠져 있었더라고요.

    체크리스트별로 조금 더 깊게 보겠습니다

    1. 구형 프로토콜 제거

    SSLv2, SSLv3, TLS 1.0, TLS 1.1은 지금 기준으로는 운영망에서 유지할 이유가 거의 없습니다. 아주 특수한 구형 장비 호환이 아니라면 닫는 쪽이 맞고, 그 예외는 문서화해두는 게 좋습니다.

    2. Cipher Suite 정리

    암호군은 운영체제와 웹 서버 버전에 따라 권장값이 조금 달라질 수 있어서, 제가 실무에서는 무리하게 직접 나열하기보다 지원 중인 최신 배포판과 웹 서버의 기본 권장값을 우선 확인합니다. 확실하지 않은 값을 인터넷 글에서 복붙하면 오히려 더 위험할 수 있거든요.

    3. HSTS는 신중하게 적용

    HSTS는 강력합니다. 한 번 브라우저가 기억하면 계속 HTTPS만 쓰게 만들거든요. 근데 서브도메인까지 강제로 묶는 includeSubDomains를 넣을 땐 정말 확인하고 넣으셔야 합니다. 테스트 서브도메인이 아직 HTTP만 쓰는 환경이라면 바로 장애로 이어질 수 있습니다. 여기서 중요한 포인트! 운영 전에 도메인 구조를 먼저 점검하세요.

    4. 인증서 자동 갱신

    Let's Encrypt 같은 자동화 도구를 쓰는 환경이라면 갱신 자체보다도 갱신 후 reload/restart가 정상 동작하는지가 더 중요합니다. 인증서는 갱신됐는데 프로세스가 예전 인증서를 물고 있는 경우, 생각보다 자주 봤습니다.

    5. 개인키 권한

    개인키(private key, 비밀키)는 정말 최소 권한으로 다뤄야 합니다. 홈랩에서는 편하다고 권한을 넉넉하게 주고 넘어가기 쉬운데, 나중에 습관이 그대로 남거든요. 운영 서버라면 읽기 권한 대상과 백업 위치까지 같이 점검하시는 걸 권합니다.

    검증 명령어: 설정 후 꼭 확인해야 하는 것들

    설정을 넣었다면 이제 검증입니다. 드디어 됐다! 하고 넘기면 안 됩니다. 실제로 써보니까 TLS SSL 보안 설정은 검증 단계에서 문제를 제일 많이 잡습니다.

    1. 웹 서버 설정 문법 검사
    2. HTTPS 접속 및 인증서 체인 확인
    3. HTTP 리다이렉트 동작 확인
    4. 보안 헤더 응답 확인
    5. 외부 스캐너로 최종 진단
    sudo nginx -t
    sudo apachectl configtest
    curl -I http://example.com
    curl -I https://example.com
    openssl s_client -connect example.com:443 -servername example.com

    curl -I는 헤더만 빠르게 보기 좋아서 저는 거의 습관처럼 씁니다. openssl s_client는 처음엔 출력이 길어서 당황스러운데, 인증서 체인과 핸드셰이크(handshake, 연결 협상) 상태를 볼 수 있어서 익숙해지면 아주 든든합니다.

    TLS SSL 보안 설정 검증을 위한 HTTPS 터미널 점검 이미지

    openssl s_client와 curl -I 결과를 통해 인증서 체인, 리다이렉트, 보안 헤더를 검증하는 예시 이미지입니다.

    ⚠️ 제가 실제로 겪었던 트러블슈팅 포인트

    인증서가 맞는데도 브라우저 경고가 뜨는 경우

    이건 중간 인증서 누락이 원인인 경우가 많았습니다. 서버 인증서만 맞다고 끝이 아니더라고요. 체인 파일이 올바른지 먼저 보세요.

    리다이렉트 루프가 생기는 경우

    로드밸런서(load balancer, 부하 분산 장치) 뒤에 웹 서버가 있을 때 자주 봤습니다. 앞단에서 이미 HTTPS 종료를 했는데 뒤 서버도 강제로 HTTPS를 재해석하면 무한 루프가 납니다. 이럴 땐 X-Forwarded-Proto 같은 프록시 헤더 처리 로직을 같이 확인해야 합니다.

    HSTS 적용 후 테스트 도메인이 접속 안 되는 경우

    이건 진짜 당황스럽습니다. 저도 예전에 서브도메인까지 묶어버렸다가 테스트 환경 접근이 꼬였던 적이 있습니다. 그래서 요즘은 처음부터 긴 max-age를 넣기보다 짧게 검증하고 늘리는 편입니다.

    보안 점수만 보고 안심하는 경우

    외부 진단 점수는 참고용으로 좋지만, 그 점수만 높다고 실제 운영이 안전한 건 아닙니다. 인증서 갱신 실패 알림, 키 보관 정책, 리버스 프록시 구조, 애플리케이션 쿠키 속성까지 같이 봐야 하거든요.

    결과 확인: 무엇이 달라져야 하나요?

    설정이 잘 끝나면 최소한 아래 결과는 확인되어야 합니다.

    • HTTP 요청이 HTTPS로 일관되게 전환됩니다.
    • 브라우저 자물쇠 경고 없이 인증서 체인이 정상 표시됩니다.
    • 구형 프로토콜 협상이 차단됩니다.
    • 보안 헤더가 응답에 포함됩니다.
    • 자동 갱신 후에도 서비스 재기동 없이 안정적으로 운영됩니다.

    이 상태가 되면 단순히 HTTPS만 켠 수준이 아니라, 실제 서비스 운영에 맞는 보안 강화 체크리스트를 한 바퀴 돈 셈입니다. 특히 웹 서버 보안은 누락 하나가 전체 신뢰도를 떨어뜨리니, 변경 후에는 반드시 다시 스캔해보세요.

    HTTPS와 TLS SSL 보안 설정 검증 결과 대시보드 이미지

    HTTPS 적용 완료, 리다이렉트 성공, 보안 헤더 확인, 프로토콜 제한 상태를 대시보드 형태로 보여주는 이미지입니다.

    정리: 안전한 HTTPS 운영을 위한 최종 체크

    마지막으로 제가 현장에서 보는 관점으로 한 번 더 압축해볼게요.

    1. TLS 1.2 이상만 허용했는지 확인합니다.
    2. 인증서 체인(full chain)이 올바른지 확인합니다.
    3. HTTP -> HTTPS 강제 전환이 되는지 확인합니다.
    4. HSTS와 기본 보안 헤더가 적용됐는지 확인합니다.
    5. 개인키 권한과 자동 갱신을 점검합니다.
    6. curl, openssl, 외부 점검 도구로 실제 동작을 검증합니다.

    처음엔 이게 뭔가 싶어도, 한 번 체크리스트로 정리해두면 다음부터는 훨씬 수월합니다. 저도 처음엔 설정 파일 한 줄 바꿀 때마다 겁났는데, 지금은 검증 루틴까지 묶어서 운영하니 훨씬 편해졌습니다. 이거 진짜 편하더라고요.

    다음 글에서는 Nginx SSL 환경에서 리버스 프록시와 HSTS를 함께 운영할 때 주의할 점, 그리고 프록시 뒤 애플리케이션 쿠키 보안 설정까지 이어서 다뤄볼 예정입니다. 이전 글에서 다뤘던 리버스 프록시 기본 구성과 함께 보시면 흐름이 더 잘 잡히실 거예요.

    안전한 HTTPS 운영을 위한 10가지 체크리스트를 한 장으로 요약한 인포그래픽 이미지입니다.

    자주 묻는 질문

    Q. SSL과 TLS를 같은 뜻으로 써도 되나요?

    실무 대화에서는 섞여 쓰는 경우가 많습니다. 다만 정확히는 현대 웹 보안 통신은 TLS를 의미한다고 보시면 됩니다.

    Q. Nginx SSL과 Apache SSL 중 뭐가 더 안전한가요?

    둘 중 무엇이 절대적으로 더 안전하다기보다, 어떻게 설정하고 검증하느냐가 더 중요합니다. 기본 원칙은 같습니다.

    Q. HTTPS만 켜면 웹 서버 보안은 끝인가요?

    아닙니다. TLS SSL 보안 설정은 시작점이고, 접근 제어, 패치 관리, 로깅, WAF(Web Application Firewall, 웹 애플리케이션 방화벽), 애플리케이션 보안까지 같이 봐야 전체가 단단해집니다.

  • [보안] Let’s Encrypt 갱신 오류, 흔한 문제와 해결 전략

    [보안] Let’s Encrypt 갱신 오류, 흔한 문제와 해결 전략

    [보안] Let’s Encrypt 갱신 오류, 흔한 문제와 해결 전략

    운영 중인 서비스에서 Let’s Encrypt 갱신 오류가 한 번이라도 터지면 진짜 식은땀이 납니다. 평소엔 조용히 돌아가다가 어느 날 갑자기 HTTPS 오류가 보이고, 브라우저에서 보안 경고가 뜨면 그때부터는 마음이 급해지거든요. 저도 홈랩이랑 소규모 서비스 환경에서 SSL 인증서 갱신이 자동으로 될 줄만 알았다가, 새벽에 인증서 만료 알림 보고 삽질 좀 했습니다 ㅎㅎ 처음엔 이게 뭔가 싶었는데, 결국 원인은 늘 비슷한 데 있더라고요.

    이번 글에서는 제가 직접 점검할 때 쓰는 흐름대로 Certbot(서트봇, Let’s Encrypt 인증서 발급 도구) 문제 해결 방법을 정리해보겠습니다. 단순히 명령어만 던지는 게 아니라, 왜 실패하는지, 어디부터 봐야 하는지, 그리고 다시는 같은 문제를 반복하지 않으려면 어떻게 운영해야 하는지까지 같이 보시죠.

    Let's Encrypt 갱신 오류 흐름을 설명하는 웹 서버와 DNS 개요 다이어그램

    인증서 갱신이 실패하는 지점을 한눈에 보여주는 전체 흐름도죠.

    1. 왜 Let’s Encrypt 갱신 오류가 자주 생길까요?

    쉽게 말해 Let’s Encrypt는 “이 도메인을 정말 당신이 제어하고 있나요?”를 주기적으로 다시 확인합니다. 이때 Challenge(챌린지, 도메인 소유 확인 절차)가 정상 처리되지 않으면 갱신이 실패합니다. 겉으로는 인증서 문제처럼 보여도, 실제론 웹 서버 설정, DNS, 방화벽, 리버스 프록시, 컨테이너 라우팅 중 하나가 어긋난 경우가 많습니다.

    실제로 써보니까 실패 원인은 대체로 아래 범주에 들어갔습니다.

    • 포트 80 접근 불가: HTTP-01 검증이 막히는 경우
    • DNS 레코드 불일치: 도메인이 다른 IP를 가리키는 경우
    • 웹루트 경로 오류: challenge 파일이 예상 위치에 생성되지 않는 경우
    • 프록시/Ingress 설정 충돌: Nginx, Apache, Ingress(인그레스, 외부 트래픽 진입점)에서 경로를 가로채는 경우
    • 자동 갱신 스케줄 미동작: systemd timer(시스템디 타이머) 또는 cron(크론) 문제

    2. 먼저 알아야 할 핵심 개념: HTTP-01과 DNS-01

    여기서 중요한 포인트! 어떤 방식으로 검증하느냐에 따라 점검 포인트가 완전히 달라집니다.

    검증 방식 동작 방식 주로 막히는 지점 언제 적합한가
    HTTP-01 도메인의 80/tcp로 challenge 파일 확인 방화벽, 리버스 프록시, 웹루트 경로 일반적인 웹 서버 환경
    DNS-01 DNS TXT 레코드로 소유권 검증 DNS 전파 지연, 자동화 스크립트 와일드카드 인증서, 외부 포트 제한 환경

    저도 처음엔 무조건 Certbot만 다시 돌리면 되는 줄 알았는데, 사실 중요한 건 갱신 명령이 아니라 검증 경로였습니다. HTTP-01이라면 웹 서버 응답을 먼저 봐야 하고, DNS-01이라면 DNS 제공자 쪽 자동화가 제대로 되는지 봐야 하거든요.

    3. Let’s Encrypt 갱신 오류가 났을 때 가장 먼저 확인할 1차 점검

    문제 생기면 바로 재발급부터 하지 마시고, 아래 순서대로 보시는 게 훨씬 빠릅니다. 제가 직접 해보니 이 순서가 제일 덜 헤맸습니다.

    1. 현재 인증서 만료일 확인
    2. 갱신 테스트 실행
    3. 도메인 DNS 확인
    4. 포트 80/443 외부 접근 확인
    5. 웹 서버 로그와 Certbot 로그 확인

    3-1. 인증서 상태 확인

    sudo certbot certificates

    이 명령으로 현재 인증서 이름, 연결된 도메인, 만료 예정 시점을 먼저 확인합니다. 여러 도메인을 한 서버에서 운영하면 어떤 인증서가 실패했는지 여기서 감이 옵니다.

    3-2. 갱신 시뮬레이션 실행

    sudo certbot renew --dry-run

    dry-run(드라이런, 실제 반영 없이 테스트 실행)은 거의 필수입니다. 실제 갱신 전 검증 흐름을 테스트해주기 때문에, 운영 중인 인증서를 건드리지 않고 문제를 확인할 수 있습니다. 이 단계에서 에러 메시지가 꽤 직설적으로 나오더라고요.

    3-3. DNS 확인

    dig +short example.com
    
    dig +short www.example.com

    도메인이 현재 서비스 중인 공인 IP를 정확히 가리키는지 보셔야 합니다. 의외로 예전 서버 IP가 남아 있거나, CDN/프록시 설정이 중간에 꼬여 있는 경우가 있습니다.

    3-4. 외부 접근 확인

    curl -I http://example.com/.well-known/acme-challenge/test-file

    여기서 404는 나올 수 있어도, 최소한 example.com까지 정상적으로 도달해야 합니다. 연결 자체가 안 되거나 엉뚱한 리다이렉트가 걸리면 그때부터 원인을 좁히면 됩니다.

    Let's Encrypt 갱신 오류 점검을 위한 Certbot dry-run 터미널 이미지

    dry-run 테스트, DNS 확인, 외부 접근 확인 순서를 시각적으로 정리한 이미지입니다.

    4. 실전 구현: 웹루트 방식으로 SSL 인증서 갱신 점검하기

    가장 흔한 시나리오라서 webroot(웹루트, 웹 서버가 문서를 제공하는 디렉터리) 기준으로 설명드리겠습니다. Nginx를 예로 들지만, Apache도 핵심 원리는 비슷합니다.

    4-1. challenge 경로 테스트 파일 배치

    sudo mkdir -p /var/www/html/.well-known/acme-challenge
    
    echo test | sudo tee /var/www/html/.well-known/acme-challenge/health-check

    그 다음 외부에서 아래처럼 접근해봅니다.

    curl http://example.com/.well-known/acme-challenge/health-check

    여기서 test가 보여야 합니다. 안 보이면 Certbot 문제가 아니라 웹 서버 라우팅 문제일 가능성이 큽니다.

    4-2. Nginx 설정 예시

    server {
        listen 80;
        server_name example.com www.example.com;
    
        location /.well-known/acme-challenge/ {
            root /var/www/html;
            try_files $uri =404;
        }
    
        location / {
            return 301 https://$host$request_uri;
        }
    }

    처음엔 저도 모든 HTTP 요청을 바로 HTTPS로 넘기면 끝이라고 생각했었는데, challenge 경로는 예외 처리가 필요할 때가 있습니다. 특히 리버스 프록시를 여러 단으로 쌓아두면 여기서 꼬이기 쉽더라고요.

    4-3. Certbot 갱신 테스트

    sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
    
    sudo certbot renew --dry-run

    새 발급이 아니라 갱신 점검이 목적이라면 기존 인증서 구조를 해치지 않는 선에서 테스트하시는 게 좋습니다. 운영 서버에선 무턱대고 옵션을 바꾸기보다 현재 어떤 플러그인 방식으로 인증서가 관리되는지 먼저 확인하세요.

    5. ⚠️ 흔한 문제와 해결 전략

    이 섹션이 핵심입니다. 실제로 가장 자주 보는 Let’s Encrypt 갱신 오류 패턴을 정리해보겠습니다.

    5-1. 포트 80이 막혀 있는 경우

    HTTP-01은 기본적으로 80/tcp 응답이 필요합니다. 보안을 이유로 80을 닫아둔 환경이 종종 있는데, 이러면 갱신이 안 돼요.

    • 클라우드 보안 그룹(Security Group, 보안 그룹) 확인
    • 서버 로컬 방화벽(UFW, firewalld) 확인
    • 공유기 포트 포워딩 확인
    sudo ss -lntp | grep ':80'
    
    sudo ufw status

    홈랩에서는 특히 공유기 NAT(나트, 사설망 주소 변환) 때문에 삽질하는 경우가 많습니다. 저도 한동안 서버 설정만 봤다가, 알고 보니 공유기 포트 포워딩이 빠져 있던 적이 있었네요.

    5-2. DNS는 맞는 줄 알았는데 다른 IP를 보는 경우

    DNS TTL(Time To Live, 캐시 유지 시간) 때문에 예전 값이 남아 있을 수 있습니다. A 레코드, AAAA 레코드가 같이 있을 때 IPv6 경로만 잘못된 경우도 꽤 흔합니다.

    dig example.com A +short
    
    dig example.com AAAA +short

    특히 IPv6를 의도적으로 운영하지 않는데 AAAA 레코드가 남아 있으면, 일부 환경에서 엉뚱한 경로로 검증이 시도될 수 있습니다.

    5-3. 컨테이너와 프록시 체인에서 challenge 파일이 안 보이는 경우

    Docker, Kubernetes, NAS 내장 프록시 환경에서 많이 만납니다. 애플리케이션 컨테이너는 뜨는데 .well-known 경로만 별도 처리되지 않아서 실패하는 패턴이죠.

    • 호스트와 컨테이너의 웹루트 경로가 같은지 확인
    • Ingress 또는 reverse proxy(리버스 프록시) 규칙 충돌 확인
    • 리다이렉트 규칙이 challenge 경로를 덮어쓰지 않는지 확인
    Let's Encrypt 갱신 오류의 원인이 되는 리버스 프록시와 컨테이너 구조 다이어그램

    프록시 체인과 컨테이너 환경에서 challenge 요청이 어디서 끊기는지 설명하는 다이어그램입니다.

    5-4. 자동 갱신은 설정했는데 실제로는 안 도는 경우

    이건 꽤 허탈합니다. 저는 예전에 certbot 명령은 잘 되는데, 정작 자동 갱신 스케줄이 비활성화되어 있던 적이 있었습니다.

    systemctl list-timers | grep certbot
    
    systemctl status certbot.timer

    환경에 따라 cron을 쓰는 경우도 있으니 둘 다 확인하셔야 합니다. SSL 인증서 갱신은 “설정했다”가 아니라 “주기적으로 실제 실행되는지 검증했다”가 되어야 합니다.

    5-5. 갱신 후 웹 서버 재적용이 안 되는 경우

    인증서는 갱신됐는데 서비스에는 옛날 인증서가 계속 보이는 경우가 있습니다. 이럴 땐 reload(리로드, 설정 재적용)가 누락된 경우를 의심해보세요.

    sudo nginx -t
    
    sudo systemctl reload nginx

    Apache라면 서비스 이름만 다를 뿐 흐름은 비슷합니다. 설정 검증 없이 바로 재시작하면 더 크게 터질 수 있으니, 저는 항상 문법 체크부터 합니다.

    6. 운영 팁: 갱신 실패를 미리 막는 습관

    문제 생기고 나서 대응하는 것보다, 애초에 안 터지게 만드는 게 훨씬 낫습니다. 실제로 써보니까 아래 습관이 꽤 효과적이었습니다.

    1. 월 1회 dry-run 실행으로 검증 경로 이상 여부 확인
    2. 인증서 만료 알림 메일을 놓치지 않도록 별도 라벨링
    3. 웹 서버 설정 변경 후 challenge 경로 재테스트
    4. DNS 변경 직후 외부에서 A/AAAA 레코드 재확인
    5. 프록시 구조 변경 시 HTTP-01 대신 DNS-01 검토

    💡 팁 하나 드리면, 구조가 자주 바뀌는 환경이라면 HTTP-01만 고집하지 않는 게 좋습니다. 와일드카드가 필요하거나 외부 포트 정책이 빡빡하면 DNS-01이 더 안정적일 때도 많거든요.

    7. 검증과 결과 확인

    이제 마지막 확인입니다. 갱신이 성공했다고 바로 끝내지 마시고, 실제 서비스에 반영됐는지 확인해야 합니다.

    sudo certbot renew --dry-run
    
    openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -subject

    저는 보통 dry-run 성공 후 실제 서비스 인증서의 시작일과 종료일을 확인합니다. 브라우저 자물쇠만 보는 건 조금 불안하더라고요. 명령행에서 직접 확인하면 훨씬 확실합니다.

    ✅ 정상이라면 다음과 같은 상태가 나와야 합니다.

    • dry-run이 오류 없이 종료됨
    • 도메인 443 응답이 정상
    • 웹 서버 reload 후 새 인증서 날짜 반영
    • 브라우저 보안 경고 없음
    Let's Encrypt 갱신 오류 해결 후 SSL 인증서 갱신 검증 화면

    갱신 성공 후 무엇을 확인해야 하는지 체크리스트 형태로 정리한 이미지입니다.

    8. 자주 묻는 질문과 정리

    Q1. HTTPS로만 서비스 중인데 80 포트가 꼭 필요한가요?

    HTTP-01을 쓴다면 보통 필요합니다. 다만 DNS-01을 쓰는 방식으로 전환하면 80 포트 의존도를 줄일 수 있습니다.

    Q2. Certbot 문제 해결은 로그를 어디부터 보면 좋을까요?

    저는 먼저 certbot renew --dry-run 결과를 보고, 그 다음 웹 서버 접근 로그와 에러 로그를 같이 봅니다. Certbot 문제 해결은 단일 명령보다 흐름 전체를 보는 게 중요합니다.

    Q3. 갱신은 성공했는데 브라우저에서 예전 인증서가 보입니다.

    웹 서버 reload 누락, 로드밸런서 캐시, CDN 인증서 분리 운영 등을 차례로 의심해보시면 됩니다.

    정리

    Let’s Encrypt 갱신 오류는 겉보기엔 인증서 이슈지만, 실제론 네트워크와 웹 서버 설정 문제인 경우가 많습니다. 제가 직접 해보니 가장 중요한 건 “재발급”보다 “검증 경로 추적”이었습니다. 포트 80, DNS, 웹루트, 프록시, 자동 갱신 타이머. 이 다섯 가지만 차분히 보면 대부분 풀립니다.

    🎉 드디어 됐다! 싶은 순간이 오면, 거기서 끝내지 말고 재발 방지까지 해두시는 걸 추천드립니다. 다음 글에서는 Nginx 리버스 프록시 환경에서 HTTPS 오류를 줄이기 위한 설정 분리 방법도 다뤄볼 예정입니다. 이전 글에서 다뤘던 리버스 프록시 구조 점검 글이 있다면 같이 참고하시면 더 이해가 빠르실 거예요.

    검증 방식 선택 기준과 운영 체크포인트를 한 장으로 요약한 인포그래픽입니다.