콘텐츠로 이동

Nginx Hardening 및 SSL 자동화 가이드 (운영 서버 기준)

운영 서버(Production Server) 에서 Nginx 웹 서버를 보안 강화(Hardening)하고
Let’s Encrypt + Certbot을 이용해 SSL 인증서를 자동으로 관리하기 위한 운영 기준을 정리.

기능 설명이 아닌 운영 보안 기준 + 실제 운용 절차(runbook) 를 다룸.


1. Nginx Hardening 적용 목적

  • 불필요한 정보 노출 차단
  • HTTPS 강제 적용
  • 최신 보안 표준 기반 통신 유지
  • 인증서 자동 갱신을 통한 운영 리스크 최소화

운영 서버에서는 모든 웹 트래픽을 HTTPS로 제한하는 것을 기본 원칙으로 함.


2. Nginx 기본 보안 설정

2.1 서버 정보 노출 차단

/etc/nginx/nginx.conf 또는 공통 설정 파일에 추가.

server_tokens off;
  • Nginx 버전 정보 노출 차단.
  • 취약점 스캐닝 시 노출 정보 최소화.

2.2 불필요한 HTTP 메서드 차단

if ($request_method !~ ^(GET|HEAD|POST)$) {
    return 444;
}
  • PUT, DELETE 등 불필요한 메서드 차단.
  • 공격 표면 축소 목적.

3. HTTPS 강제 리디렉션

모든 HTTP 요청은 HTTPS로 강제 전환함.

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com \
            example.kr www.example.kr \
            example.co.kr www.example.co.kr;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type "text/plain";
        try_files $uri =404;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

운영 서버에서는 HTTP 서비스 단독 제공 금지.


4. SSL 인증서 자동화 (Let’s Encrypt)

4.1 SSL 발급 방식 개요 (운영 관점)

Certbot은 동일한 Let’s Encrypt 인증서를 발급하되 인증(Challenge)을 처리하는 방식에 따라 여러 실행 방식을 제공.

운영 서버에서는 다음 두 방식을 병렬적으로 지원함.

  • Webroot 방식 (운영 표준)
  • Nginx 플러그인 방식 (--nginx)

이 외 방식은 특수 목적용으로 간략히 소개.

4.2 Certbot 설치

sudo dnf install -y epel-release
sudo dnf install -y certbot python3-certbot-nginx

4.3 SSL 인증서 발급

운영 기준은 Development(Staging) → Live(Production) 순서로 진행.

4.3.1 Webroot 기반 SSL 발급 (운영 표준)

운영 서버에서는 Nginx 설정 자동 변경을 방지하기 위해 certonly --webroot 방식을 표준으로 사용함.

  • Nginx 설정 파일을 Certbot이 수정하지 않음
  • 설정 Drift 방지
  • 배포 스크립트 / IaC 환경과 충돌 없음
  • HTTPS 구성에 대한 통제권을 운영자가 유지
4.3.1.1 ACME Challenge 디렉토리 생성
sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
# RHEL 계열은 nginx
sudo chown -R nginx:nginx /var/www/letsencrypt
# Ubuntu는 보통 www-data
# sudo chown -R www-data:www-data /var/www/letsencrypt
sudo chmod -R 755 /var/www/letsencrypt
# SELinux 사용 환경(RHEL 등)에서만 필요
sudo restorecon -Rv /var/www/letsencrypt
echo ok | sudo tee /var/www/letsencrypt/.well-known/acme-challenge/ping >/dev/null

4.3.1.2 Nginx 설정 추가 (HTTP 80 서버 블록)

location ^~ /.well-known/acme-challenge/ {
    root /var/www/letsencrypt;
    default_type "text/plain";
    try_files $uri =404;
}
  • 해당 location은 반드시 HTTP(80) 서버 블록에 존재해야 함.
  • HTTPS(443) 서버 블록에만 존재할 경우 HTTP-01 인증은 실패함.

4.3.1.3 Development (Staging) 발급

sudo certbot certonly --webroot -w /var/www/letsencrypt -d <DomainName> --staging --no-eff-email --agree-tos -m <E-Mail> -v

4.3.1.4 Production 발급

sudo certbot certonly --webroot -w /var/www/letsencrypt -d <DomainName> --force-renewal --no-eff-email --agree-tos -m <E-Mail> -v

기존에 staging 인증서가 존재하는 경우에만 --force-renewal 옵션을 사용함.

  • 인증서는 /etc/letsencrypt/live/<DomainName>/ 경로에 생성됨
  • Nginx 설정은 자동 수정되지 않음

4.3.1.5 443 Block 수동 적용 (운영 표준)

운영 표준은 Nginx 설정 파일에 인증서 경로를 직접 반영하는 방식임.

ssl_certificate     /etc/letsencrypt/live/<DomainName>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<DomainName>/privkey.pem;
  • webroot 방식의 장점은 Nginx 설정 Drift 를 막는 데 있으므로, 443 설정도 직접 관리하는 것이 원칙임
  • 다수 server block 환경에서는 include 구조 또는 공통 snippet 으로 관리하는 편이 안전함

4.3.1.6 certbot install 사용 (편의용, 운영 표준 아님)

sudo certbot install --cert-name <DomainName>
  • 설정 파일을 수동으로 수정하기 번거로운 환경에서 사용할 수 있는 편의용 명령임
  • 실행 시 Nginx 설정 파일이 자동 수정될 수 있음
  • staging 인증서가 남아 있으면 staging 경로가 반영될 수 있으므로 production 발급 완료 후 사용함
  • 운영 표준은 아니며, 사용 시에는 변경된 설정을 반드시 diff 확인함

4.3.2 Nginx 플러그인 방식 (--nginx)

Nginx 플러그인 방식은 Certbot이 Nginx 설정을 직접 분석·수정하여 인증서 발급과 HTTPS 설정을 자동 적용하는 방식.

  • 인증서 발급 + SSL 설정 자동 반영
  • HTTP → HTTPS 리디렉션 자동 구성 가능
  • 초기 구축 속도가 빠름
  • Nginx 설정 파일이 자동 수정됨
  • include 기반 복잡한 설정 구조에서는 예기치 않은 변경 가능
  • 운영 표준 구성에서는 변경 이력을 반드시 검토할 것

4.3.2.1 Development (Staging) 발급

sudo certbot --nginx -d <DomainName> --staging --no-eff-email --agree-tos -m <E-Mail> -v
  • Let’s Encrypt 레이트 리밋 회피 목적
  • 운영 적용 전 검증 단계

4.3.2.2 Production 발급

sudo certbot --nginx -d <DomainName> --force-renewal --no-eff-email --agree-tos -m <E-Mail> -v

기존에 staging 인증서가 존재하는 경우에만 --force-renewal 옵션을 사용함.

  • Nginx 설정 자동 수정.
  • 인증서 발급 및 HTTPS 설정 자동 적용.

4.3.3 인증서 확인

4.3.3.1 Development (Staging) 발급 확인

sudo nginx -t && sudo systemctl reload nginx
curl -fsS --max-time 10 http://<DomainName>/.well-known/acme-challenge/ping
sudo openssl x509 -in /etc/letsencrypt/live/<DomainName>/fullchain.pem -noout -issuer

4.3.3.2 Production 발급 확인

sudo nginx -t && sudo systemctl reload nginx
HTTP_CODE="$(curl -sSI --connect-timeout 5 --max-time 10 https://<DomainName> -o /dev/null -w "%{http_code}")" && \
[ "$HTTP_CODE" != "000" ] && echo "HTTPS reachable (HTTP $HTTP_CODE)"
echo | openssl s_client -connect <DomainName>:443 -servername <DomainName> 2>/dev/null | openssl x509 -noout -issuer -subject
  • issuer 항목에 (STAGING) 문구가 존재하면 staging 인증서임.

4.3.3.3 자동 갱신 검증.

sudo certbot renew --dry-run
  • 실패 시 webroot 경로 또는 80 server block 재확인
  • 성공 시 systemd timer에 의해 자동 갱신됨 (systemctl list-timers | grep certbot)

4.4 기타 SSL 발급 방식 (요약)

  • Standalone 방식

    • 임시 웹 서버를 실행하여 인증
    • 80 포트 점유 필요
    • 운영 서버에서는 비권장
  • DNS-01 방식

    • DNS TXT 레코드로 인증
    • 와일드카드 인증서 발급 가능
    • DNS API 연동 필요

4.5 자동 갱신 방식 개요

Certbot은 SSL 인증서 자동 갱신을 위해 두 가지 방식을 제공.

  • systemd timer 기반 자동 갱신 (Certbot 기본 방식)
  • cron 기반 자동 갱신 (운영자가 직접 관리)

특정 방식을 강제하지 않으며, 운영 환경과 정책에 따라 적절한 방식을 선택할 수 있도록 두 방식을 모두 설명. 실제 운영 적용 시에는 한 가지 방식만 선택하여 사용

4.5.1 Hook 개념 정리

Certbot 갱신 시 다음 Hook을 제공.

  • --pre-hook
    갱신 시도 전에 실행

  • --post-hook
    갱신 성공 여부와 관계없이 실행

  • --deploy-hook
    실제 인증서가 갱신되었을 때만 실행 (운영 권장)

4.5.2 Cron 기반 운영

Cron 기반 자동 갱신은 운영자가 갱신 주기, 실행 시점, 로그 관리 방식을 명시적으로 제어할 수 있는 방식.

  • 실행 주기와 시점을 명확히 지정 가능
  • 날짜별 로그 파일 생성 및 보관 정책 명시 가능
  • 장애 발생 시 로그 추적이 용이하며, 운영 가시성이 높음

cronie 설치

sudo dnf -y install cronie

crontab 설정

crontab -e
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 5 * * 1 mkdir -p /var/log/certbot-renew && touch /var/log/certbot-renew/certbot-renew-$(date +\%Y-\%m-\%d).log && certbot renew --deploy-hook "systemctl reload nginx" >> /var/log/certbot-renew/certbot-renew-$(date +\%Y-\%m-\%d).log 2>&1
0 5 * * 1 find /var/log/certbot-renew/ -type f -name "certbot-renew-*" -mtime +7 -exec rm {} \;
  • 매주 월요일 새벽 5시에 /var/log/certbot-renew 디렉토리가 없으면 생성, 해당 날짜의 로그 파일이 없으면 생성 후 로그파일에 기록
  • 매주 월요일 새벽 5시에 /var/log/certbot-renew 에 있는 7일이 지난 로그파일 삭제

crond 서비스 활성화

systemctl start crond &&
systemctl enable crond &&
systemctl restart crond &&
systemctl daemon-reload &&
systemctl status crond

4.5.3 systemd timer 기반 운영

Certbot은 기본적으로 systemd timer를 통해 자동 갱신을 수행하도록 구성되어 있음.
별도의 cron 설정 없이도 OS 기본 메커니즘을 활용해 인증서 갱신이 이루어짐.

  • 설정이 단순하며 추가 관리 포인트가 적음
  • OS 표준 방식에 의존하므로 일관성 있음
  • 로그 위치와 실행 시점이 systemd 정책에 종속됨

운영 서버에서는
systemd timer 기반 자동 갱신 + deploy-hook을 통한 nginx reload 구성을
Certbot SSL 자동화의 기본 완료 상태로 정의함.

⚠️ systemd timer 기반 운영 시 필수 설정

Certbot은 인증서 갱신 후 웹 서버를 자동으로 reload 하지 않음.

nginx는 reload / restart 전까지 기존 인증서를 계속 사용하므로, deploy-hook 설정 없이 systemd timer만 사용하는 경우
인증서가 갱신되었음에도 만료 인증서를 계속 사용하는 위험이 존재함.

운영 서버에서는 반드시 deploy-hook 을 설정할 것.

⚠️ 운영 주의 사항 (중요)

systemd timer 기반 Certbot 자동 갱신은
systemctl enable 상태라도 런타임에서 active 상태가 아닐 수 있음.

특히 RHEL / Rocky / AlmaLinux 계열에서는
타이머가 최초 1회 start 되지 않아
inactive (dead) 상태로 남아 있는 사례가 빈번함.

운영 서버에서는 enable + start + 상태 확인까지 완료되어야 함.

4.5.3.1. systemd timer 초기 설정 절차 (운영 서버 기준)
# systemd unit 반영
sudo systemctl daemon-reload

# 부팅 시 자동 시작 등록
sudo systemctl enable certbot-renew.timer

# 즉시 타이머 시작 (필수)
sudo systemctl start certbot-renew.timer
4.5.3.2. deploy-hook 설정 (nginx 자동 reload)

RHEL / Rocky / AlmaLinux 계열에서는
/etc/sysconfig/certbot 파일을 통해 hook을 설정함.

sudo vi /etc/sysconfig/certbot

다음과 같이 설정:

PRE_HOOK=""
POST_HOOK=""
DEPLOY_HOOK="--deploy-hook 'systemctl reload nginx'"
CERTBOT_ARGS=""

파일 저장 후 별도 서비스 재시작은 필요 없음.
다음 갱신 시 자동 적용됨.

설정 반영 확인:

sudo systemctl cat certbot-renew.service

출력에 $DEPLOY_HOOK 포함 여부 확인.

4.5.3.3. systemd timer 상태 확인
sudo systemctl status certbot-renew.timer

정상 상태:

  • Active: active (waiting)
  • Trigger: 항목에 다음 실행 시각 표시

다음 실행 시각 확인:

sudo systemctl list-timers | grep certbot
4.5.3.4. 자동 갱신 동작 검증 (운영 필수)
# timer 등록 여부
sudo systemctl list-unit-files | grep certbot

# timer 활성 상태 확인
sudo systemctl status certbot-renew.timer

# 다음 실행 시각 확인
sudo systemctl list-timers | grep certbot

# 실제 갱신 시뮬레이션
sudo certbot renew --dry-run

정상 기준:

  • certbot-renew.timer 가 active (waiting)
  • Trigger 시각 존재
  • dry-run 성공
  • 로그에 deploy-hook 실행 메시지 확인 가능
4.5.3.5. nginx 설정 사전 검증 (권장)

deploy-hook 동작 전 nginx 설정이 정상이어야 함.

sudo nginx -t

문제 발생 시 reload 실패 가능.

4.5.3.6. 수동 즉시 갱신 (운영 테스트용)
sudo certbot renew --deploy-hook "systemctl reload nginx"
  • 실제 갱신 발생 시 nginx 자동 reload 수행
  • 인증서가 갱신되지 않으면 reload 실행되지 않음

4.6 자동 갱신 방식 비교 및 선택 기준

항목 Cron 기반 systemd timer 기반
설정 난이도 중간 낮음
실행 시점 제어 명확 제한적
로그 관리 명시적 (파일 단위) systemd journal 의존
운영 가시성 높음 보통
OS 종속성 낮음 systemd 의존
추천 환경 운영 서버 / 장기 서비스 단순 구성 / 기본 환경

선택 가이드

  • 운영 서버 / 로그 감사 / 장애 추적이 중요한 환경 → Cron 기반 자동 갱신 권장

  • 구성이 단순하고 기본 동작이면 충분한 환경 → systemd timer 기반 사용 가능

4.7 Server 에 저장된 모든 인증서 목록 확인

certbot certificates

4.8 인증서를 삭제

certbot delete --cert-name <DomainName>

4.9 터미널에서 실행

  • 테스트용으로 시뮬레이션 실행. --dry-run 옵션은 실제 인증서 갱신 없이 함.
certbot renew --dry-run
  • 바로 갱신
certbot renew --deploy-hook "systemctl reload nginx"

4.10 여러개 할때 (멀티 도메인 / SAN 인증서) 예시

sudo certbot --nginx \
-d example.com -d www.example.com \
-d example.kr -d www.example.kr \
-d example.co.kr -d www.example.co.kr \
--force-renewal --no-eff-email --agree-tos -m test@test.com -v

기존에 staging 인증서가 존재하는 경우에만 --force-renewal 옵션을 사용함.


5. SSL 보안 설정 강화

5.1 강력한 TLS 설정 적용

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
  • TLS 1.0 / 1.1 비활성화.
  • 최신 브라우저 기준 보안 유지.

5.2 보안 헤더 설정

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  • 클릭재킹 방지.
  • MIME 타입 스니핑 방지.
  • 기본 XSS 보호 활성화.

본 설정은 기본 운영 권장 수준이며
CSP, OCSP Stapling 등 고급 보안 정책은 서비스 특성에 따라 별도 설계 필요.


6. HSTS 적용 (운영 서버 전용)

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
  • HTTPS 강제 정책 브라우저에 캐시.
  • 적용 후 HTTP 접속 불가.
  • 운영 서버에서만 적용 권장.

7. Nginx 설정 검증 및 재시작

sudo nginx -t
sudo systemctl reload nginx

8. 운영 서버 적용 체크

  • HTTPS 접속 정상 확인.
  • HTTP 접속 시 HTTPS 리디렉션 확인.
  • 인증서 만료일 확인.
  • 브라우저 보안 경고 없음 확인.

9. 주의 사항

  • 인증서 발급 전 DNS 설정 완료 필요.
  • 내부망 전용 서버에는 Let’s Encrypt 적용 불가.
  • 테스트 서버에서는 HSTS 적용 주의.

10. Nginx 설정 참고 (예시 / 운영 환경에 맞게 수정)

10.1 HTTP → HTTPS 리디렉션 (직접 수정 권장)

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com \
            example.kr www.example.kr \
            example.co.kr www.example.co.kr;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type "text/plain";
        try_files $uri =404;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

10.2 HTTPS 서버 블록 (프록시 예시)

  • webroot 인증은 10.1(80 블록)에서 처리되며, 443에도 동일 location을 둘 수 있음(선택).
server {
    server_name example.com www.example.com \
            example.kr www.example.kr \
            example.co.kr www.example.co.kr;

    client_max_body_size    100M;

    access_log /var/log/nginx/example.access.log;
    error_log /var/log/nginx/example.error.log;

    charset utf-8;

    root /var/www/example;

    error_page 404 /404.html;
    location = /404.html {
        root /usr/share/nginx/html;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }

    # webroot 인증은 10.1(80 블록)에서 처리되며, 443에도 동일 location을 둘 수 있음(선택).
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type "text/plain";
        try_files $uri =404;
    }

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-NginX-Proxy true;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_pass http://127.0.0.1:3000;
        proxy_redirect off;
    }

    listen 443 ssl;
    listen [::]:443 ssl;
    ssl_certificate /etc/letsencrypt/live/example/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example/privkey.pem;

    # options-ssl-nginx.conf / ssl-dhparams.pem 이 없으면 (--nginx 미사용 환경 등)
    # include / ssl_dhparam 라인을 제거하거나 동일 수준의 TLS 설정을 수동으로 구성

    # --nginx 방식 사용 시 Certbot이 자동 생성될 수 있음
    # webroot 방식 사용 시 파일이 없다면 include 라인을 제거하거나 수동 준비 필요
    include /etc/letsencrypt/options-ssl-nginx.conf;

    # --nginx 방식 사용 시 Certbot이 자동 생성될 수 있음
    # webroot 방식 사용 시 파일이 없다면 dhparam 파일을 생성하거나 라인을 제거 필요
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}