티스토리 뷰
✏️ 1. DNS 기본 개념
DNS란?
DNS(Domain Name System)는 도메인 이름을 IP 주소로 변환해주는 인터넷의 전화번호부로 사람이 기억하기 쉬운 도메인을 컴퓨터가 이해하는 IP로 바꿔주는 역할을 한다.
eat-ssu.tech → 43.200.49.228
DNS 조회 흐름(eat-ssu를 예시로)
브라우저에서 eat-ssu.tech 입력
↓
1. 브라우저 캐시 확인
↓
2. OS 캐시 확인 (/etc/hosts)
↓
3. ISP DNS 서버 (SKT, KT 등) 조회
↓
4. 루트 DNS 서버 (.)
↓
5. TLD DNS 서버 (.tech, .com 등)
↓
6. 권한 있는 DNS 서버 (get.tech, Route53 등)
↓
IP 주소 반환 → TTL 동안 캐싱
DNS 레코드 종류
| 레코드 | 용도 | 예시 |
| A | 도메인 → IPv4 | eat-ssu.tech → 43.200.49.228 |
| AAAA | 도메인 → IPv6 | eat-ssu.tech → 2001:db8::1 |
| CNAME | 도메인 → 도메인 | www → eat-ssu.tech |
| MX | 메일 서버 | eat-ssu.tech → mail.eat-ssu.tech |
| TXT | 텍스트 정보 | 도메인 소유권 인증 등 |
A 레코드에서 @는 루트 도메인을 의미한다.
@ → 43.200.49.228 (eat-ssu.tech)
dev → 3.37.229.16 (dev.eat-ssu.tech)
www → 43.200.49.228 (www.eat-ssu.tech)
TTL(Time To Live)은 DNS 응답을 캐시에 얼마나 보관할지 결정하는 값으로 초 단위를 사용한다.
DNS 레코드를 변경해도 TTL이 만료되기 전까지는 기존 캐시를 사용한다. 즉 TTL이 86400이면 변경 후 최대 24시간까지 기존 IP로 접속될 수 있다
TTL 300 = 5분
TTL 3600 = 1시간
TTL 7200 = 2시간
TTL 86400 = 24시간
작업 24시간 전: TTL을 300(5분)으로 낮추기
↓
기존 TTL이 만료될 때까지 대기 (최대 기존 TTL 시간)
↓
서버 이전 및 DNS 변경
↓
변경 후 5분이면 전 세계 전파 완료
↓
작업 완료 후 TTL 원래 값으로 복구
DNS 확인 명령어
# 기본 조회
dig eat-ssu.tech
# IP만 출력
dig eat-ssu.tech +short
# 43.200.49.228
# 특정 DNS 서버로 조회 (구글 DNS)
dig @8.8.8.8 eat-ssu.tech +short
# TTL 확인
dig @8.8.8.8 eat-ssu.tech
# eat-ssu.tech. 7200 IN A 43.200.49.228
# ^^^^
# 남은 TTL(초)
# nslookup으로 조회 (Windows/Mac)
nslookup eat-ssu.tech
✏️ 2. SSL과 HTTPS
HTTP vs HTTPS
HTTP는 데이터를 평문으로 전송하기 때문에 중간에서 누군가 패킷을 가로채면 내용을 그대로 볼 수 있다. HTTPS는 SSL/TLS로 암호화하기 때문에 가로채도 내용을 알 수 없다.
| HTTP | HTTPS | |
| 포트 | 80 | 443 |
| 암호화 | X | 0 |
| 인증서 | 불필요 | 필요 |
| 보안 | 취약 | 강함 |
SSL 인증서란?
SSL 인증서는 두 가지 역할을 한다.
- 신원 확인: 이 서버가 진짜 eat-ssu.tech 서버임을 증명
- 암호화 키 교환: 클라이언트와 서버 간 암호화 통신을 위한 공개키 포함
인증서는 CA(Certificate Authority, 인증 기관)가 발급하며 Let's Encrypt는 무료 CA로 certbot을 통해 쉽게 발급받을 수 있고,
SSL 핸드셰이크 흐름은 아래의 표와 같다.클라이언트 서버
| |
|---- ClientHello -------------->| (지원하는 암호화 방식 목록)
| |
|<--- ServerHello + 인증서 ------| (선택한 암호화 방식 + 공개키)
| |
| 인증서 검증 |
| (CA 서명 확인, 도메인 일치 확인)|
| |
|---- 대칭키 교환 --------------->|
| |
|<===== 암호화 통신 시작 ========>|
인증서 구성 파일
certbot으로 발급받으면 다음 파일들이 생성되며 nginx에서는 fullchain.pem과 privkey.pem 두 개만 사용한다.
/etc/letsencrypt/live/eat-ssu.tech/
├── fullchain.pem # 서버 인증서 + 중간 인증서 (nginx에서 사용)
├── privkey.pem # 개인키 (절대 외부에 노출 금지)
├── cert.pem # 서버 인증서만
└── chain.pem # 중간 인증서만
CN과 SAN
- CN(Common Name): 인증서의 주 도메인
- SAN(Subject Alternative Name): 인증서가 커버하는 모든 도메인 목록
현대 브라우저는 CN을 무시하고 SAN만 검사한다. 인증서의 SAN에 접속하려는 도메인이 없으면 인증서 오류가 발생한다.
# 인증서 CN 확인
openssl x509 -noout -subject -in /etc/letsencrypt/live/eat-ssu.tech/fullchain.pem
# subject=CN = eat-ssu.tech
# 인증서 만료일 확인
openssl x509 -noout -dates -in /etc/letsencrypt/live/eat-ssu.tech/fullchain.pem
# notBefore=Apr 24 09:18:28 2026 GMT
# notAfter=Jul 23 09:18:27 2026 GMT
# 실제 서버가 내려주는 인증서 확인
echo | openssl s_client -connect eat-ssu.tech:443 -servername eat-ssu.tech 2>/dev/null \
| openssl x509 -noout -subject
# 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
# nginx 플러그인으로 인증서 발급 (nginx 설정 자동 수정)
sudo certbot --nginx -d eat-ssu.tech
# 인증서만 발급 (nginx 설정 수동)
sudo certbot certonly --nginx -d eat-ssu.tech
# 여러 도메인 한 번에 발급
sudo certbot certonly --nginx -d eat-ssu.tech -d www.eat-ssu.tech
# 인증서 갱신 (Let's Encrypt 인증서는 90일 유효)
sudo certbot renew
# 갱신 테스트 (실제 갱신 X)
sudo certbot renew --dry-run
# 발급된 인증서 목록 확인
sudo certbot certificates
주의: certbot 발급 시 해당 도메인이 반드시 이 서버 IP를 가리키고 있어야 한다. Let's Encrypt가 인증 과정에서 도메인으로 직접 접속해 검증하기 때문이다.
✏️ 3. Nginx 기본 개념
Nginx란?
Nginx는 웹 서버이자 리버스 프록시다.
- 웹 서버: 정적 파일(HTML, CSS, 이미지 등) 직접 서빙
- 리버스 프록시: 클라이언트 요청을 받아 내부 서버(Spring Boot 등)로 전달
클라이언트 → Nginx(80/443) → Spring Boot(9000)
Spring Boot만 단독으로 운영하면 안 되는가? 이론상 가능하지만 다음 이유로 Nginx를 앞에 둔다.
- SSL 처리: HTTPS 인증서 관리를 Nginx가 담당, Spring은 HTTP만 처리
- 포트 관리: 80/443 포트 바인딩 (1024 이하 포트는 root 권한 필요)
- 정적 파일 서빙: 이미지, CSS 등 Nginx가 직접 처리해서 Spring 부하 감소
- 로드 밸런싱: 여러 Spring 인스턴스로 요청 분산
- 보안: Spring 포트(9000) 직접 노출 방지
/etc/nginx/
├── nginx.conf # 메인 설정 (worker 수, 로그 경로 등)
├── sites-available/ # 가상 호스트 설정 파일 보관
│ └── default # 실제 설정 파일
├── sites-enabled/ # 활성화된 설정 심볼릭 링크
│ └── default -> ../sites-available/default
└── conf.d/ # 추가 설정 파일
sites-available에 설정을 작성하고 sites-enabled에 심볼릭 링크를 걸어 활성화한다.
# 심볼릭 링크 생성 (활성화)
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
# 심볼릭 링크 제거 (비활성화)
sudo rm /etc/nginx/sites-enabled/myapp
✏️ 4. Nginx 설정 파일
server 블록
하나의 설정 파일에 여러 server 블록을 둘 수 있다. 각 블록은 listen 포트와 server_name의 조합으로 구분되며 default_server는 매칭되는 server 블록이 없을 때 기본으로 처리하는 블록이다.
# 443 포트 + eat-ssu.tech 도메인 처리
server {
listen 443 ssl;
server_name eat-ssu.tech;
...
}
# 443 포트 + dev.eat-ssu.tech 도메인 처리
server {
listen 443 ssl;
server_name dev.eat-ssu.tech;
...
}
# 80 포트 + 모든 도메인 처리
server {
listen 80 default_server;
server_name _; # _는 모든 도메인을 의미
...
}
listen 지시어
listen 80; # IPv4, 80포트
listen [::]:80; # IPv6, 80포트
listen 80 default_server; # 기본 서버로 지정
listen 443 ssl; # SSL 활성화
listen [::]:443 ssl ipv6only=on; # IPv6 전용
location 블록
요청 경로에 따라 다르게 처리할 수 있다.
server {
server_name eat-ssu.tech;
# 모든 요청 → Spring Boot로 전달
location / {
proxy_pass http://localhost:9000;
}
# /static/ 경로 → 파일 직접 서빙
location /static/ {
root /var/www/html;
expires 30d; # 브라우저 캐시 30일
}
# 정확히 /health 경로만
location = /health {
return 200 "ok";
add_header Content-Type text/plain;
}
# /api/ 로 시작하는 경로
location /api/ {
proxy_pass http://localhost:9000;
}
}
location 매칭 우선순위
= (정확히 일치) 최우선
^~ (앞부분 일치) 두 번째
~ (정규식, 대소문자 구분)
~* (정규식, 대소문자 무시)
/ (일반 매칭) 최후순위
proxy_pass와 proxy_set_header
location / {
# Spring Boot(9000포트)로 요청 전달
proxy_pass http://localhost:9000;
# HTTP/1.1 사용 (Keep-Alive 지원, WebSocket 등)
proxy_http_version 1.1;
proxy_set_header Connection "";
# 원본 Host 헤더 전달
# 없으면 Spring이 localhost를 Host로 인식
proxy_set_header Host $host;
# 클라이언트 실제 IP 전달
# Spring에서 request.getRemoteAddr() 시 실제 IP 반환
proxy_set_header X-Real-IP $remote_addr;
# 프록시 체인 IP 목록
# 여러 프록시를 거친 경우 전체 IP 체인
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 원본 프로토콜 전달 (http 또는 https)
# Spring Security에서 HTTPS 여부 판단에 사용
proxy_set_header X-Forwarded-Proto $scheme;
}
spring boot에서의 헤더 활용
이 설정을 하면 Spring이 X-Forwarded-* 헤더를 읽어 실제 클라이언트 정보를 사용하여 서버 에러 알림 등에 사용 가능하다
# application.yml
server:
forward-headers-strategy: framework
SSL 설정
server {
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;
# certbot이 생성한 보안 설정
# TLS 버전(1.2, 1.3만 허용), 암호화 방식 등
include /etc/letsencrypt/options-ssl-nginx.conf;
# DH 파라미터 (키 교환 보안 강화)
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
...
}
HTTP -> HTTPS 리다이렉트
server {
listen 80;
listen [::]:80;
server_name eat-ssu.tech;
# 301: 영구 리다이렉트
# $host: 요청한 도메인 유지
# $request_uri: 경로 + 쿼리스트링 유지
return 301 https://$host$request_uri;
}
✏️ 5. 완성된 nginx default 파일 예시
dev/prod 두 개의 서버를 운영할 때 설정 예시이다.
# =============================================
# 기본 HTTP 서버 (매칭 안 되는 요청 처리)
# =============================================
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location / {
return 444; # 연결 끊기 (보안)
}
}
# =============================================
# prod 서버: eat-ssu.tech
# =============================================
# HTTP → HTTPS 리다이렉트
server {
listen 80;
listen [::]:80;
server_name eat-ssu.tech;
return 301 https://$host$request_uri;
}
# HTTPS 메인 서버
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name eat-ssu.tech;
# SSL 인증서
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;
# Spring Boot로 프록시
location / {
proxy_pass http://127.0.0.1:9000;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
}
}
# =============================================
# dev 서버: dev.eat-ssu.tech
# =============================================
# HTTP → HTTPS 리다이렉트
server {
listen 80;
listen [::]:80;
server_name dev.eat-ssu.tech;
if ($host = dev.eat-ssu.tech) {
return 301 https://$host$request_uri;
}
return 404;
}
# HTTPS dev 서버
server {
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;
server_name dev.eat-ssu.tech;
# dev 인증서
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;
}
}
✏️ 6. 클라이언트 요청 전체 흐름
앱에서 GET https://eat-ssu.tech/meals 요청을 보낼 때 전체 흐름이다.
1. DNS 조회
앱 → OS DNS 캐시 확인
→ ISP DNS 서버 (캐시 없으면 권한 있는 DNS 서버까지 조회)
→ eat-ssu.tech = 43.200.49.228 반환
→ TTL 동안 캐싱
2. TCP 연결
앱 → 43.200.49.228:443 TCP SYN
서버 → SYN-ACK
앱 → ACK (3-way handshake 완료)
3. SSL 핸드셰이크
앱 → ClientHello (지원 암호화 방식)
서버 → ServerHello + eat-ssu.tech 인증서
앱 → 인증서 검증 (CA 서명, 도메인, 만료일)
앱 → 대칭키 생성 후 공개키로 암호화해서 전송
이후 대칭키로 암호화 통신
4. HTTP 요청 (암호화됨)
앱 → GET /meals HTTP/1.1
Host: eat-ssu.tech
Authorization: Bearer {token}
...
5. Nginx 처리
443 포트 수신
→ server_name eat-ssu.tech 매칭
→ SSL 복호화
→ location / 매칭
→ proxy_pass http://127.0.0.1:9000/meals
6. Spring Boot 처리
ControllerLogAspect:
REQUEST GET /meals args=userId=4748, ...
JwtAuthenticationFilter:
→ Authorization 헤더에서 토큰 추출
→ 토큰 검증
→ SecurityContext에 인증 정보 저장
MealsController:
→ MealsService 호출
→ JPA로 DB 조회
→ 응답 반환
ControllerLogAspect:
RESPONSE GET /meals (14ms) result={...}
7. 응답 반환
Spring → Nginx → 암호화 → 앱
✏️ 7. 주의할 점들
DNS 관련
- 서버 이전 전 TTL을 반드시 낮추기 => 작업 전날 TTL을 300으로 낮추고 기존 TTL이 만료될 때까지 기다려야 한다. 이미 캐싱된 TTL이 만료되어야 새 TTL이 적용되기 때문이다.
- ISP마다 TTL 준수 여부가 다르다 => 일부 ISP는 TTL을 무시하고 자체적으로 더 길게 캐싱하는 경우가 있다. 특히 SKT가 이런 경향이 있다. DNS 변경 후 전파가 늦어지면 dnschecker.org에서 어느 ISP/지역이 문제인지 확인할 수 있다.
- DNS 변경은 되돌리기 어렵다 => 변경 즉시 전 세계로 전파가 시작되기 때문에 실수하면 되돌리더라도 TTL만큼 시간이 걸린다.
SSL 관련
- 인증서 도메인과 접속 도메인이 반드시 일치해야 한다 => dev.eat-ssu.tech 인증서를 eat-ssu.tech 도메인에 사용하면 SSL 오류가 발생한다. 앱에서는 연결 자체를 거부한다.
- 개인키(privkey.pem)는 절대 외부에 노출하면 안 된다 => 개인키가 유출되면 서버를 사칭할 수 있다. git에 올리거나 로그에 남기면 절대 안 된다.
- certbot 발급 시 해당 도메인이 이 서버를 가리켜야 한다 => DNS가 다른 서버를 가리키고 있으면 Let's Encrypt 인증에 실패한다.
- Let's Encrypt 인증서는 90일마다 갱신해야 한다.
# crontab에 자동 갱신 등록
sudo crontab -e
0 12 * * * /usr/bin/certbot renew --quiet
Nginx 관련
설정 변경 후 반드시 문법 검사 하기 => 문법 오류가 있으면 reload 시 nginx가 다운될 가능성
sudo nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
proxy_pass에서 HTTP/HTTPS 구분을 정확히 하기 => HTTP로 HTTPS 포트(443)에 요청하면 400 Bad Request가 발생
proxy_pass http://localhost:9000; # 내부 통신 (HTTP)
proxy_pass https://43.200.49.228; # 외부 서버로 포워딩 (HTTPS)
리다이렉트 루프 주의하기
# 잘못된 설정 예시 - 무한 루프 발생
server {
listen 80;
server_name eat-ssu.tech;
return 301 https://eat-ssu.tech$request_uri;
}
server {
listen 443 ssl;
server_name eat-ssu.tech;
location / {
# prod가 다시 http로 리다이렉트하면 루프
proxy_pass http://43.200.49.228:80;
}
}
try_files와 proxy_pass는 함께 쓸 수 없다
# 잘못된 설정 - proxy_pass가 무시됨
location / {
try_files $uri $uri/ =404;
proxy_pass http://localhost:9000; # 동작 안 함
}
# 올바른 설정
location / {
proxy_pass http://localhost:9000;
}
✏️ 8. 자주 쓰는 명령어 모음
# ==================
# Nginx 명령어
# ==================
# 설정 문법 검사 (변경 후 항상 먼저 실행)
sudo nginx -t
# 설정 재로드 (무중단, 권장)
sudo nginx -s reload
# nginx 재시작 (중단 후 시작)
sudo systemctl restart nginx
# nginx 상태 확인
sudo systemctl status nginx
# nginx 시작/중지
sudo systemctl start nginx
sudo systemctl stop nginx
# 접속 로그 실시간 확인
sudo tail -f /var/log/nginx/access.log
# 에러 로그 실시간 확인
sudo tail -f /var/log/nginx/error.log
# ==================
# SSL/certbot 명령어
# ==================
# 인증서 발급
sudo certbot certonly --nginx -d eat-ssu.tech
# 발급된 인증서 목록 확인
sudo certbot certificates
# 인증서 갱신
sudo certbot renew
# 갱신 테스트
sudo certbot renew --dry-run
# 인증서 정보 확인
openssl x509 -noout -subject -dates -in /etc/letsencrypt/live/eat-ssu.tech/fullchain.pem
# 서버 인증서 확인
echo | openssl s_client -connect eat-ssu.tech:443 -servername eat-ssu.tech 2>/dev/null \
| openssl x509 -noout -subject
# ==================
# DNS 명령어
# ==================
# 기본 조회
dig eat-ssu.tech +short
# 특정 DNS 서버로 조회
dig @8.8.8.8 eat-ssu.tech +short
# TTL 포함 조회
dig @8.8.8.8 eat-ssu.tech
# SSL 연결 확인
curl -v https://eat-ssu.tech 2>&1 | grep -E "subject|SSL|issuer"
'Back-End > 개인 공부' 카테고리의 다른 글
| [Spring Boot / 스프링 부트] 축제 부스앱 결제 자동화 도전기 - Portone 반려부터 RT PAY까지 (2) | 2026.05.18 |
|---|---|
| [Spring Boot / 스프링 부트] Spring Boot의 Command 객체 (0) | 2026.05.17 |
| 무중단 배포 방식 정리 - Rolling, Blue-Green, Canary (0) | 2026.03.30 |
| [Spring Boot / 스프링 부트] toString()을 왜 오버라이딩해야 하는가 - 보안 로깅 관점 (0) | 2026.03.28 |
| [Spring Boot / 스프링 부트] 카카오 & 네이버 소셜 로그인 구현하기 (OAuth 2.0) (0) | 2026.03.28 |
- Total
- Today
- Yesterday
- 자바스크립트
- 알고리즘
- C++
- C++ Stack
- DFS
- BFS
- 알고리즘 공부
- 스프링 부트 crud 게시판 구현
- 자바
- DP
- html
- 반복문
- 이분 매칭
- js
- CSS
- c++ string
- 카운팅 정렬
- 투 포인터
- 백준
- HTML5
- Do it!
- 세그먼트 트리
- 우선순위 큐
- 백준 풀이
- 유니온 파인드
- 에라토스테네스의 체
- 자료구조
- 유클리드 호제법
- java
- 스택
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |