티스토리 뷰

반응형

✏️ 배경

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
 
구글 DNS로 확인한 결과
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 인증서 불일치

한국 유저가 eat-ssu.tech로 접속하면 SKT DNS 캐시로 인해 dev 서버로 라우팅되는데, dev 서버 nginxdev.eat-ssu.tech 인증서만 가지고 있었다. 앱에서 HTTPS 통신 시 SSL 인증서 검증 실패로 서버에 요청 자체가 도달하지 못했다. OAuth 콜백도 마찬가지로 실패했고, iOS와 Android에서 앱 코드에서 실패 시 "카카오톡으로 생성된 계정입니다" 토스트를 띄우는 로직이 있어 해당 메시지가 출력됐다.
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 서버까지 도달하는지 로그로 확인해야 한다.

 
 
 
 
 
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/07   »
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
글 보관함