개발 트러블슈팅 완전 정복 | 웹개발·백엔드·데브옵스 실무 에러 해결법. Docker, GitHub Actions, TypeScript, MySQL, Redis 등 실전 경험 기반의 개발 블로그
목요일
수요일
Nginx SSL 인증서 만료 해결법
🔍 검색 키워드: Nginx SSL 인증서 만료, Let's Encrypt 갱신, TLS 에러, HTTPS 연결 실패, 인증서 업데이트
Nginx SSL 인증서 만료 해결법
증상
브라우저에서 HTTPS 사이트 접속 시:
NET::ERR_CERT_AUTHORITY_INVALID
ERR_SSL_OBSOLETE_VERSION
The certificate has expired
SSL_ERROR_HANDSHAKE_FAILURE_ALERT
또는 서버 로그에:
SSL_ERROR_RX_RECORD_TOO_LONG
no shared ciphers
Peer rejected a valid certificate
certificate verify failed (self signed certificate)
cURL로 확인 시:
$ curl -v https://example.com
* SSL certificate problem: certificate has expired
원인
- 인증서 만료: Let's Encrypt 90일 정책, 또는 상용 인증서 만료
- 자동 갱신 미작동: certbot/Nginx 플러그인 설정 오류
- Nginx 설정 오류: 잘못된 인증서 경로
- 시스템 시간 오류: 서버 시계가 실시간과 다름
- 인증서 체인 미완성: 중간 인증서(Intermediate) 누락
- 포트 443 차단: Let's Encrypt 갱신 포트 막힘
- 권한 오류: 인증서 파일 읽기 권한 부족
해결 방법
방법 1: 현재 인증서 상태 확인
# 인증서 만료 기간 확인
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
-noout -dates
# 출력 예:
# notBefore=Jun 4 12:00:00 2024 GMT
# notAfter=Sep 2 12:00:00 2024 GMT
# SSL 프로토콜 버전 확인
openssl s_client -connect example.com:443 -tls1_2
# 인증서 체인 확인
openssl s_client -connect example.com:443 -showcerts
# 남은 기간 확인 (일 수)
ssl-cert-check -c /etc/letsencrypt/live/example.com/cert.pem
# Nginx 설정에서 인증서 경로 확인
grep "ssl_certificate" /etc/nginx/sites-enabled/default
방법 2: Let's Encrypt 인증서 자동 갱신
# 1. certbot 설치 (미설치 시)
sudo apt-get install certbot python3-certbot-nginx
# 2. 인증서 수동 갱신
sudo certbot renew
# 3. 특정 도메인만 갱신
sudo certbot renew --cert-name example.com
# 4. 강제 갱신 (만료 60일 전이 아니라도)
sudo certbot renew --force-renewal
# 5. 갱신 후 Nginx 재로드
sudo systemctl reload nginx
# 6. Nginx 설정 테스트
sudo nginx -t
자동 갱신 설정 (cron 또는 systemd):
# crontab 설정 (매일 오전 3시 확인)
sudo crontab -e
# 다음 라인 추가:
0 3 * * * /usr/bin/certbot renew --quiet && systemctl reload nginx
# 또는 systemd timer (권장)
sudo systemctl list-timers certbot
sudo systemctl status certbot.timer
방법 3: Nginx 설정 확인 및 수정
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL 인증서 경로 확인
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS 버전 명시 (TLS 1.2 이상)
ssl_protocols TLSv1.2 TLSv1.3;
# 최신 암호화 알고리즘
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;
# HSTS 설정 (선택사항)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 설정 테스트
# $ sudo nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
}
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name example.com www.example.com;
location / {
return 301 https://$host$request_uri;
}
}
설정 적용:
sudo nginx -t
sudo systemctl reload nginx
방법 4: 새 인증서 발급 (기존 만료된 경우)
# 1. certbot 대화형 모드로 새 인증서 발급
sudo certbot certonly --nginx -d example.com -d www.example.com
# 2. 수동 DNS 검증 방식
sudo certbot certonly --manual --preferred-challenges dns \
-d example.com -d www.example.com
# DNS TXT 레코드 추가 후 엔터
# 3. 발급된 인증서 확인
sudo ls -la /etc/letsencrypt/live/example.com/
방법 5: 시스템 시간 확인 및 수정
# 시스템 시간 확인
date
# 시간이 틀렸다면 수정
sudo timedatectl set-ntp true # NTP 자동 동기화
sudo timedatectl set-timezone Asia/Seoul
# 수정 확인
date
timedatectl
# 시간 동기화 강제 실행
sudo ntpdate -s ntp.ubuntu.com
방법 6: SSL 점검 및 모니터링
# SSL Labs 온라인 테스트 (브라우저에서)
# https://www.ssllabs.com/ssltest/analyze.html?d=example.com
# 로컬에서 SSL 테스트
sudo apt-get install sslscan
sslscan --no-failed example.com:443
# 인증서 투명성 로그 확인
curl https://ct.googleapis.com/log/all_logs_list.json
# Nginx 에러 로그 확인
sudo tail -f /var/log/nginx/error.log
# Certbot 갱신 로그 확인
sudo tail -f /var/log/letsencrypt/letsencrypt.log
정리표
| 에러 메시지 | 원인 | 해결법 |
|---|---|---|
| certificate has expired | 인증서 만료 | certbot renew 실행 |
| SSL_ERROR_HANDSHAKE_FAILURE_ALERT | TLS 설정 오류 | Nginx SSL 설정 확인 |
| no shared ciphers | 암호화 불일치 | ssl_ciphers 최신 버전 설정 |
| certificate verify failed | 인증서 체인 누락 | fullchain.pem 사용 확인 |
| ERR_SSL_OBSOLETE_VERSION | 구버전 TLS | TLS 1.2 이상으로 설정 |
팁: Let's Encrypt는 90일마다 갱신이 필요합니다. certbot renew --dry-run으로 자동 갱신을 사전 테스트하고, 갱신 후 systemctl reload nginx로 무중단 재로드하세요. Nginx 재시작 시 기존 연결은 유지되므로 서비스 중단이 없습니다.
Kubernetes ImagePullBackOff 에러 해결법
🔍 검색 키워드: k8s ImagePullBackOff, 쿠버네티스 이미지 풀 실패, Docker 레지스트리 인증, 이미지 태그 오류, Pod 시작 실패
Kubernetes ImagePullBackOff 에러 해결법
증상
Kubernetes Pod를 배포했을 때 다음과 같은 상태에서 멈춘다:
$ kubectl get pods -n production
NAME READY STATUS RESTARTS AGE
app-deployment-abc123 0/1 ImagePullBackOff 2 5m
상세 확인 시:
$ kubectl describe pod app-deployment-abc123 -n production
Events:
Type Reason Age Message
---- ------ ---- -------
Normal Scheduled 5m Successfully assigned production/app-deployment-abc123 to worker-node-1
Normal BackOff 4m Back-off pulling image "myrepo/app:v1.0"
Warning Failed 4m Failed to pull image "myrepo/app:v1.0": rpc error: code = Unknown desc = Error response from daemon: unauthorized
Warning Failed 3m Back-off pulling image
또는 다음과 같은 메시지:
image not found
image pull rate limit exceeded
no such image
invalid reference format
원인
- 이미지명 오류: 잘못된 저장소명, 태그 또는 레지스트리 주소
- 인증 실패: Private Docker 레지스트리 접근 권한 없음
- 네트워크 단절: 워커 노드에서 레지스트리 접근 불가
- 레지스트리 다운: Docker Hub, ECR 등 서비스 장애
- 이미지 미존재: 푸시되지 않은 이미지 태그
- 레이트 제한: Docker Hub 무료 계정 풀 한도 초과
- CPU/메모리 부족: 노드 리소스 부족으로 스케줄링 실패
해결 방법
방법 1: 이미지명 및 태그 확인
# 현재 Pod의 이미지 정보 확인
kubectl get pod app-deployment-abc123 -n production -o yaml | grep image
# 정확한 이미지명 확인 (레지스트리 포함)
# 형식: [registry]/[repository]/[image]:[tag]
# 정상 예: docker.io/myrepo/app:v1.0
# 정상 예: gcr.io/my-project/app:latest
# 정상 예: ecr.amazonaws.com/123456789.dkr.ecr.us-east-1.amazonaws.com/app:v1.0
Deployment 수정:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-deployment
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: docker.io/myrepo/app:v1.0 # 정확한 이미지명
imagePullPolicy: IfNotPresent # 또는 Always
방법 2: Private 레지스트리 인증 설정
# 1. Docker 자격증명으로 Secret 생성
kubectl create secret docker-registry regcred \
--docker-server=gcr.io \
--docker-username=_json_key \
--docker-password="$(cat ~/gcr-key.json)" \
--docker-email=user@example.com \
-n production
# 2. 또는 기존 docker config 파일 사용
kubectl create secret generic regcred \
--from-file=.dockerconfigjson=$HOME/.docker/config.json \
--type=kubernetes.io/dockercfg \
-n production
Deployment에서 사용:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-deployment
spec:
template:
spec:
imagePullSecrets:
- name: regcred # 위에서 생성한 Secret 이름
containers:
- name: app
image: gcr.io/my-project/app:v1.0
방법 3: 워커 노드 네트워크 확인
# 워커 노드에서 직접 레지스트리 연결 확인
kubectl debug node/worker-node-1 -it --image=ubuntu
# Pod 내에서:
apt-get update && apt-get install -y curl
curl -I https://gcr.io
curl -I https://docker.io
# 또는 임시 Pod에서 테스트
kubectl run test-curl --image=curlimages/curl -it --rm -- \
curl -v https://gcr.io
방법 4: 로컬에서 이미지 빌드 및 푸시 확인
# 1. 로컬에서 이미지 빌드
docker build -t myrepo/app:v1.0 .
# 2. 레지스트리에 푸시
docker push myrepo/app:v1.0
# 3. 푸시된 이미지 확인
# Docker Hub: https://hub.docker.com/r/myrepo/app
# GCR: gcloud container images list
# ECR: aws ecr describe-images --repository-name app
# 4. 로컬에서 이미지 실행 가능 확인
docker run --rm myrepo/app:v1.0 --version
방법 5: imagePullPolicy 조정
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-deployment
spec:
template:
spec:
containers:
- name: app
image: myrepo/app:v1.0
# imagePullPolicy 옵션:
# Always: 매번 레지스트리에서 풀 (기본, 태그 latest 사용 시)
# IfNotPresent: 로컬에 없을 때만 풀
# Never: 로컬에서만 사용 (오프라인 환경)
imagePullPolicy: Always
방법 6: 디버깅 및 재시도
# Pod 상세 로그 확인
kubectl logs app-deployment-abc123 -n production --previous
# 이벤트 확인 (시간 역순)
kubectl get events -n production --sort-by='.lastTimestamp'
# Pod 재생성 (자동 재시도)
kubectl rollout restart deployment/app-deployment -n production
# 캐시 클리어 후 재배포
kubectl set image deployment/app-deployment \
app=myrepo/app:v1.1 \
-n production
# 또는 현재 이미지로 강제 롤아웃
kubectl rollout restart deployment/app-deployment -n production
정리표
| 원인 | 증상 | 해결법 |
|---|---|---|
| 이미지명 오류 | image not found | 정확한 이미지명 확인 |
| 인증 실패 | unauthorized | imagePullSecrets 설정 |
| 네트워크 단절 | connection timeout | 워커 노드 네트워크 확인 |
| 레지스트리 장애 | service unavailable | 레지스트리 상태 확인 |
| 태그 미존재 | manifest not found | docker push 재실행 |
| 레이트 제한 | rate limit exceeded | 인증된 계정으로 전환 |
| 노드 리소스 부족 | OutOfmemory, OutOfDisk | 노드 리소스 확인 |
팁: 배포 전에 로컬 환경에서 docker push까지 성공하는지 확인하고, 클러스터에는 imagePullPolicy: Always 설정으로 항상 최신 이미지를 가져오도록 설정하면 버전 관리가 쉬워집니다.
피드 구독하기:
글 (Atom)