티스토리 뷰
✏️ 배경
EAT-SSU는 숭실대학교 학식 정보를 제공하는 앱으로, App Store와 Play Store에 배포되어 운영 중이다.
서버는 dev와 prod 두 개가 운영 중이며 도메인 구조는 다음과 같다.
- prod 서버: eat-ssu.tech → IP 43.200.49.228
- dev 서버: dev.eat-ssu.tech → IP 43.202.231.68 (이전 전)
dev 서버를 새로운 EC2 인스턴스(43.202.231.68 → 3.37.229.16 신규)로 이전하고 나서 장애가 발생했다.
✏️ 문제 상황
https://get.tech/ 도메인 관리 콘솔에서 DNS A 레코드를 설정하던 중 실수로 @(루트 도메인)와 dev 두 레코드 모두 새로운 dev 서버 IP로 설정해버렸다.
# 잘못된 설정
@ → 3.37.229.16 (새 dev 서버 IP) ← 실수
dev → 3.37.229.16 (새 dev 서버 IP)
# 정상 설정
@ → 43.200.49.228 (prod 서버 IP)
dev → 3.37.229.16 (새 dev 서버 IP)
약 2시간 후 실수를 발견하고 @ 레코드를 다시 prod 서버 IP로 복구했지만, 이후 다음과 같은 증상들이 나타났다.
- App Store/Play Store 배포 앱에서 카카오/애플 소셜 로그인 후 화면이 넘어가지 않음
- prod 서버 Swagger UI에서 dev 서버 주소로 요청을 보내고 있음
- prod 서버 로그에 로그인 관련 요청이 전혀 찍히지 않음
- nginx access log에 prod 앱 요청이 404로 찍힘

182.209.28.158 - "POST /oauths/v2/kakao HTTP/1.1" 404 - "EATSSU_PROD/3.2.6 iOS"
✏️ 원인 분석
1차 원인: SKT DNS 캐시 미전파
에서 확인한 결과 전 세계 대부분 노드는 정상 전파됐지만 Seoul 노드만 유독 dev 서버 IP를 캐싱하고 있었다. TTL이 7200초(2시간)임에도 SKT DNS 서버가 TTL을 무시하고 더 길게 캐싱하는 현상이 발생했다. 한국 유저 대부분이 SKT 망을 사용하기 때문에 사실상 전체 서비스 장애나 다름없는 상황이었다.

Seoul, South Korea (SK Telecom): 3.37.229.16 ← 잘못된 캐시
기타 전 세계 노드: 43.200.49.228
dig @8.8.8.8 eat-ssu.tech +short
# 43.200.49.228
dig @8.8.8.8 eat-ssu.tech
# eat-ssu.tech. 7200 IN A 43.200.49.228
# TTL: 7200초 (2시간)
2차 원인: SSL 인증서 불일치
curl -v https://eat-ssu.tech 2>&1 | grep -E "subject|SSL"
# subject: CN=dev.eat-ssu.tech ← 인증서 불일치!
# SSL: no alternative certificate subject name matches target host name 'eat-ssu.tech'
3차 원인: 새 dev 서버 nginx 설정 누락
새로 이전한 dev 서버의 nginx 설정에서 location / 블록이 proxy_pass 없이 try_files로만 되어 있었다. 구 dev 서버의 설정을 그대로 복붙했는데 구 서버도 동일하게 try_files로 설정되어 있었던 것이다. 이로 인해 dev.eat-ssu.tech로 접속 시 Spring 서버(9000포트)로 요청이 전달되지 않고 nginx가 직접 404를 반환했다.
# 잘못된 설정 - Spring 서버로 요청을 전달하지 않음
server {
server_name dev.eat-ssu.tech;
listen 443 ssl;
...
location / {
try_files $uri $uri/ =404; ← 문제
}
}
✏️ 대처 과정
대처 1: DNS TTL 단축
get.tech에서 TTL을 Auto → 2시간으로 변경했다. 하지만 이미 캐싱된 값에는 영향이 없고, SKT DNS 캐시가 만료되어야만 해결되는 상황이라 근본적인 해결책이 되지 못했다.
대처 2: 새 dev 서버 nginx 설정 수정
문제가 된 구 dev 서버 nginx 설정이고 Nginx+SSL에서 Nginx에 대해 정리해놓았다.
# Default server configuration
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ =404; ← Spring으로 요청 안 넘김
}
}
server {
root /var/www/html;
server_name dev.eat-ssu.tech;
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/dev.eat-ssu.tech/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev.eat-ssu.tech/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
try_files $uri $uri/ =404; ← 여기도 문제
}
}
server {
if ($host = dev.eat-ssu.tech) {
return 301 https://$host$request_uri;
}
listen 80;
server_name dev.eat-ssu.tech;
return 404;
}
수정된 설정 - proxy_pass 추가
# Default server configuration
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
server_name _;
location / {
proxy_pass http://localhost:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
server_name dev.eat-ssu.tech;
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/dev.eat-ssu.tech/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev.eat-ssu.tech/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:9000; ← Spring 서버로 포워딩
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
if ($host = dev.eat-ssu.tech) {
return 301 https://$host$request_uri;
}
listen 80;
server_name dev.eat-ssu.tech;
return 404;
}
대처 3: certbot으로 eat-ssu.tech 인증서 발급 시도 → 실패
dev 서버 자체는 정상화됐지만 prod 앱 유저들의 로그인 문제는 여전히 해결되지 않았다.
dev 서버에서 eat-ssu.tech 인증서를 새로 발급받으려 했다. Let's Encrypt가 인증 시 eat-ssu.tech로 접속하는데, 구글 DNS 기준으로는 이미 prod 서버 IP를 가리키고 있어서 dev 서버에서 인증서를 발급받을 수 없었다.
sudo certbot certonly --nginx -d eat-ssu.tech
# Certbot failed to authenticate some domains
# Domain: eat-ssu.tech
# Type: unauthorized
# Detail: 43.200.49.228: Invalid response from
# https://eat-ssu.tech/.well-known/acme-challenge/....: 401
대처 4: prod 인증서 복사 + dev 서버에서 prod로 포워딩 (최종 해결)
SKT DNS 캐시가 풀리기를 기다리는 것은 현실적이지 않다고 판단해 다음 방식으로 해결했다.
1단계: prod 서버에서 인증서 확인
# prod 서버에서 인증서 확인
sudo openssl x509 -noout -subject -in /etc/letsencrypt/live/eat-ssu.tech/fullchain.pem
# subject=CN = eat-ssu.tech
# SAN 확인
echo | openssl s_client -connect eat-ssu.tech:443 -servername eat-ssu.tech 2>/dev/null \
| openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# DNS:eat-ssu.tech
2단계: prod 인증서를 dev 서버로 복사
prod 서버에서 인증서 내용을 확인하고 dev 서버에 동일한 경로로 파일을 생성했다.
# prod 서버에서 내용 확인
sudo cat /etc/letsencrypt/live/eat-ssu.tech/fullchain.pem
sudo cat /etc/letsencrypt/live/eat-ssu.tech/privkey.pem
# dev 서버에서 파일 생성
sudo mkdir -p /etc/letsencrypt/live/eat-ssu.tech
sudo nano /etc/letsencrypt/live/eat-ssu.tech/fullchain.pem # 내용 붙여넣기
sudo nano /etc/letsencrypt/live/eat-ssu.tech/privkey.pem # 내용 붙여넣기
# 인증서 정상 확인
sudo openssl x509 -noout -subject -in /etc/letsencrypt/live/eat-ssu.tech/fullchain.pem
# subject=CN = eat-ssu.tech
3단계: dev 서버 nginx에 eat-ssu.tech 포워딩 블록 추가
# eat-ssu.tech → prod 서버로 포워딩 (임시)
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name eat-ssu.tech;
# prod 인증서 사용
ssl_certificate /etc/letsencrypt/live/eat-ssu.tech/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/eat-ssu.tech/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
# prod 서버 IP로 직접 포워딩
proxy_pass https://43.200.49.228:443;
proxy_ssl_verify off; # IP 직접 접근이므로 SSL 검증 비활성화
proxy_set_header Host eat-ssu.tech;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
server {
listen 80;
listen [::]:80;
server_name eat-ssu.tech;
return 301 https://eat-ssu.tech$request_uri;
}
최종 nginx 전체 설정
# Default server configuration (HTTP)
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
server_name _;
location / {
proxy_pass http://localhost:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTPS server for dev.eat-ssu.tech
server {
server_name dev.eat-ssu.tech;
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/dev.eat-ssu.tech/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev.eat-ssu.tech/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP → HTTPS redirect for dev.eat-ssu.tech
server {
if ($host = dev.eat-ssu.tech) {
return 301 https://$host$request_uri;
}
listen 80;
listen [::]:80;
server_name dev.eat-ssu.tech;
return 404;
}
# HTTPS server for eat-ssu.tech → prod 서버로 임시 포워딩
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name eat-ssu.tech;
ssl_certificate /etc/letsencrypt/live/eat-ssu.tech/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/eat-ssu.tech/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass https://43.200.49.228:443;
proxy_ssl_verify off;
proxy_set_header Host eat-ssu.tech;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
# HTTP → HTTPS redirect for eat-ssu.tech
server {
listen 80;
listen [::]:80;
server_name eat-ssu.tech;
return 301 https://eat-ssu.tech$request_uri;
}
sudo nginx -t && sudo nginx -s reload
트래픽 흐름
SKT 유저
↓
eat-ssu.tech (DNS 캐시: dev 서버 IP)
↓
dev 서버 nginx (eat-ssu.tech 인증서로 SSL 정상 처리)
↓
prod 서버 43.200.49.228:443으로 포워딩
↓
정상 응답
✏️ 결과
- 카카오/애플 소셜 로그인 정상화
- prod 서버 API 정상 동작
- Lambda 파이프라인 수동 실행으로 이번 주 학식 데이터 정상 인입
- SKT DNS 캐시 완전히 풀리면 dev 서버 nginx의 포워딩 블록 제거 예정
✏️ 회고 및 교훈
1. 서버 이전 전 TTL을 반드시 낮추기
작업 최소 24시간 전에 TTL을 짧게 낮춰두어야 한다. DNS 변경 사항이 빠르게 전파되므로 실수했을 때도 빠르게 복구할 수 있다. 작업 완료 후 다시 원래 값으로 올리면 된다.
작업 24시간 전: TTL 300으로 변경
작업 진행
작업 완료 후: TTL 원래 값으로 복구
2. DNS 변경은 항상 두 번 확인하기
@와 dev 레코드를 동시에 변경할 때는 반드시 저장 전에 각 레코드를 다시 확인해야 한다.
3. 도메인 관리 서비스 이전을 고려해라
get.tech는 SKT DNS와 궁합이 좋지 않아 TTL이 만료됐음에도 캐시가 수 시간 이상 유지됐다. AWS Route53이나 가비아처럼 안정적인 서비스로 이전하면 이런 문제를 줄일 수 있을 것이라 생각한다.
4. 서버 이전 체크리스트를 만들기
처음으로 서버 이전을 하다 보니 DNS 관련 설정에서 문제가 생겼던 것 같다.
□ 새 서버 nginx proxy_pass 설정 확인
□ SSL 인증서 발급 및 경로 확인
□ Spring 서버 정상 기동 확인
□ 도메인 연결 후 swagger 접속 테스트
□ 소셜 로그인 테스트
□ DNS 전파 확인 (dnschecker.org)
5. 신규 서버 설정은 기존 서버와 완전히 동일한지 검증해라
구 서버 설정을 그대로 복붙했지만 정작 핵심인 proxy_pass가 빠져있었다. 새 서버 세팅 후 반드시 실제 요청이 Spring 서버까지 도달하는지 로그로 확인해야 한다.
'Back-End > 잇슈' 카테고리의 다른 글
| [EAT-SSU / 잇슈] Docker 배포 스크립트 개선 - 전체 컨테이너 삭제 방지 (0) | 2026.06.23 |
|---|---|
| [EAT-SSU / 잇슈] GitHub Actions 빌드 시간 줄이기 (0) | 2026.06.22 |
| [EAT-SSU / 잇슈] RDS 데이터 다른 AWS 계정으로 이전하기 (0) | 2026.05.03 |
| [EAT-SSU / 잇슈] Flyway에서 특정 체크섬을 제거하는 방법 (0) | 2026.01.18 |
| [EAT-SSU / 잇슈] - Flyway를 도입해보자 (3) | 2026.01.18 |
- Total
- Today
- Yesterday
- 유클리드 호제법
- 자료구조
- HTML5
- BFS
- 스택
- 자바
- Do it!
- 이분 매칭
- 투 포인터
- js
- CSS
- 유니온 파인드
- html
- 반복문
- C++
- 백준 풀이
- DP
- 자바스크립트
- 세그먼트 트리
- 알고리즘
- 스프링 부트 crud 게시판 구현
- 카운팅 정렬
- DFS
- 에라토스테네스의 체
- java
- 우선순위 큐
- C++ Stack
- 알고리즘 공부
- c++ string
- 백준
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |