왜 TLS를 이해해야 하는가
DB 서버에 ssl: required를 설정했는데 애플리케이션 연결이 갑자기 끊겼다. 인증서 만료가 원인이었지만, 에러 메시지는 SSL connection error뿐이고 어떤 인증서가 문제인지, 왜 갱신이 안 됐는지 모르는 채로 롤백하기 쉽다.
TLS는 네트워크 계층에서 암호화(Confidentiality), 인증(Authentication), 무결성(Integrity) 세 가지를 동시에 제공한다. DB 운영자라면 TLS 핸드셰이크가 어떻게 동작하고, 인증서 체인이 어떻게 검증되며, 인증서 만료가 어떤 식으로 장애를 일으키는지 이해해야 한다. 그래야 openssl s_client 한 줄로 문제를 진단할 수 있다.
TLS의 세 가지 역할
| 역할 | 설명 | 없으면 생기는 문제 |
|---|---|---|
| 기밀성 | 데이터를 암호화해 도청 방지 | 네트워크 스니핑으로 SQL·결과 노출 |
| 인증 | 서버(또는 클라이언트)가 주장하는 신원을 검증 | MITM 공격 — 가짜 DB 서버로 연결될 수 있음 |
| 무결성 | MAC(Message Authentication Code)으로 데이터 변조 감지 | 패킷 위·변조 후 삽입 공격 |
TLS 버전 현황
TLS 1.3이 2018년 8월 RFC 8446으로 표준화됐고, 2026년 기준으로 HTTPS 트래픽의 약 95%가 TLS 1.3으로 처리된다. TLS 1.0/1.1은 RFC 8996(2021)으로 폐기됐다.
| 버전 | RTT | 상태 |
|---|---|---|
| SSL 2.0 / SSL 3.0 | — | 폐기 (POODLE, DROWN 취약점) |
| TLS 1.0 / 1.1 | 2-RTT | RFC 8996으로 폐기 |
| TLS 1.2 | 2-RTT | 여전히 사용 중이나 권장하지 않음 |
| TLS 1.3 | 1-RTT (0-RTT 지원) | 현재 권장 표준 |
TLS 1.3 핸드셰이크
TLS 1.3의 가장 중요한 개선은 핸드셰이크를 1-RTT로 단축한 것이다. TLS 1.2에서는 암호 협상(Cipher Suite)을 먼저 하고 키 교환을 나중에 했지만, TLS 1.3에서는 클라이언트가 첫 메시지에 키 공유(Key Share)까지 같이 보낸다.
TLS 1.2와의 비교
TLS 1.2는 핸드셰이크에 2-RTT가 필요했다. ClientHello → ServerHello → (1 RTT) → ClientKeyExchange → ChangeCipherSpec → (1 RTT) → 데이터. 지연이 두 배다. WAN 환경에서 RTT가 100ms이면 연결 수립에만 200ms가 걸렸다. TLS 1.3에서 이 비용이 절반으로 줄었다.
인증서 체인
TLS 인증서는 계층 구조로 서로를 서명해(chain of trust) 신뢰를 구축한다.
DigiCert / Let's Encrypt ISRG Root X1
브라우저·OS Trust Store에 내장
Let's Encrypt E5 / R10
서버가 핸드셰이크 중 전송
db.prod.example.com
유효기간 90일 (Let's Encrypt) / 1년
핸드셰이크 중 서버는 Leaf 인증서와 Intermediate CA 인증서를 함께 전송한다. Root CA는 전송하지 않는다. 클라이언트의 Trust Store에 이미 있어야 하기 때문이다. 브라우저와 OS는 약 150개의 Root CA를 내장하고 있다.
인증서 필드 핵심
Subject: CN=db.prod.example.com
Subject Alternative Names (SAN): db.prod.example.com, db-ro.prod.example.com
Issuer: Let's Encrypt E5
Validity: 2026-05-01 ~ 2026-07-30 (90일)
Public Key: EC 256-bit (P-256)
Key Usage: Digital Signature, Key Encipherment
Extended Key Usage: TLS Web Server AuthenticationSAN(Subject Alternative Names)이 중요하다. 다중 호스트명을 하나의 인증서로 커버할 수 있다. 와일드카드(*.prod.example.com)보다 SAN 목록을 명시하는 것이 보안상 낫다.
인증서 폐기 확인
인증서가 만료 전에 폐기되는 경우(개인키 유출, 설정 오류 등)를 감지해야 한다.
CRL (Certificate Revocation List)
CA가 폐기된 인증서 목록을 주기적으로 게시한다. 클라이언트가 CRL 파일을 다운로드해서 확인한다. 파일 크기가 크고(수 MB), 업데이트 주기가 길다(수 시간~수 일). 실시간 폐기 감지에 취약하다.
OCSP (Online Certificate Status Protocol)
CA의 OCSP 서버에 인증서 일련번호를 조회해 현재 상태를 실시간으로 확인한다. 빠르지만 CA OCSP 서버가 다운되면 연결이 지연되거나 실패한다. 또한 클라이언트 IP가 CA에 노출된다는 프라이버시 문제가 있다.
Let's Encrypt는 2025년 OCSP 지원을 종료했다. 단기 인증서(90일)로 폐기 필요성 자체를 줄이는 방향으로 전환했다.
OCSP Stapling
서버가 미리 OCSP 응답을 가져와 캐시해두고, 핸드셰이크 중 클라이언트에게 직접 전달한다. CA OCSP 서버에 클라이언트가 직접 연결할 필요가 없어 지연과 프라이버시 문제가 해소된다.
# nginx에서 OCSP Stapling 활성화
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/chain.pem;
resolver 8.8.8.8 1.1.1.1 valid=300s;mTLS (Mutual TLS)
일반 TLS는 클라이언트가 서버 인증서를 검증한다. mTLS는 양방향이다 — 서버도 클라이언트 인증서를 검증한다.
인증서 제공
클라이언트 인증서 제공
서버 인증서 제공
mTLS는 쿠버네티스 서비스 메시(Istio, Linkerd)에서 기본 제공된다. 모든 pod 간 트래픽이 자동으로 mTLS로 암호화·인증된다. DB 접근에서도 mTLS를 적용하면 암호 없이 인증서 기반으로 접근 제어가 가능하다.
DB 운영에서의 TLS 설정
MySQL TLS
# my.cnf (서버)
[mysqld]
require_secure_transport = ON
ssl_ca = /etc/mysql/ca.pem
ssl_cert = /etc/mysql/server-cert.pem
ssl_key = /etc/mysql/server-key.pem
tls_version = TLSv1.2,TLSv1.3
# 클라이언트 연결 확인
mysql -h db.prod -u user --ssl-ca=ca.pem --ssl-mode=VERIFY_IDENTITY -p# 현재 세션 TLS 정보 확인
SHOW STATUS LIKE 'Ssl_%';
-- Ssl_version: TLSv1.3
-- Ssl_cipher: TLS_AES_256_GCM_SHA384
# 인증서 유효기간 확인
SHOW STATUS LIKE 'Ssl_server_not_after';PostgreSQL TLS
# postgresql.conf
ssl = on
ssl_ca_file = 'root.crt'
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_min_protocol_version = 'TLSv1.2'
# pg_hba.conf — clientcert=verify-full: mTLS
hostssl all all 10.0.0.0/8 scram-sha-256 clientcert=verify-full인증서 관리 자동화
수동 갱신은 실수를 부른다. Let's Encrypt 90일 인증서가 표준이 되면서 자동화는 선택이 아닌 필수다.
(ACME 프로토콜)
(Kubernetes) 또는 certbot (VM)
또는 로컬 파일
자동 재로드
실용 갱신 체크리스트
# 인증서 만료일 확인 (openssl)
openssl x509 -in /etc/ssl/server.crt -noout -dates
# 원격 서버 인증서 확인
openssl s_client -connect db.prod.example.com:3306 -starttls mysql 2>/dev/null \
| openssl x509 -noout -dates
# 인증서 체인 전체 확인
openssl s_client -connect db.prod.example.com:5432 -showcerts 2>/dev/null \
| grep -E "subject|issuer|notAfter"TLS 세션 재개
커넥션 풀을 사용하더라도 풀이 재시작되거나 새 연결을 만들 때마다 TLS 핸드셰이크 비용이 든다. 세션 재개로 이 비용을 줄일 수 있다.
Session Resumption (TLS 1.2): 서버가 Session ID를 발급. 클라이언트가 같은 ID로 재연결하면 전체 핸드셰이크를 생략한다. 서버 메모리에 세션 상태를 저장해야 해서 로드밸런서 환경에서 복잡하다.
Session Tickets (TLS 1.2/1.3): 서버가 세션 상태를 암호화해 클라이언트에게 전달. 클라이언트가 재연결 시 티켓을 보내면 서버가 복호화해 상태를 복원. 서버 측 상태 저장이 불필요해 로드밸런서 환경에 적합.
PSK (Pre-Shared Key, TLS 1.3): TLS 1.3의 세션 재개 방식. 0-RTT도 가능하지만 DB 연결에서는 일반적으로 1-RTT PSK만 사용.
MySQL 8.0+와 PostgreSQL 15+는 TLS 세션 재개를 지원한다. 커넥션 풀 재시작 시 첫 연결에만 전체 핸드셰이크가 발생한다.
실무 트러블슈팅
흔한 TLS 오류
| 오류 | 원인 | 확인 방법 |
|---|---|---|
certificate has expired | 인증서 만료 | openssl x509 -dates |
certificate verify failed | 체인 불완전 / CA 미신뢰 | openssl verify -CAfile ca.pem server.crt |
hostname mismatch | CN/SAN에 연결 도메인 없음 | openssl x509 -text → SAN 확인 |
TLS version mismatch | 클라이언트/서버 최소 TLS 버전 불일치 | openssl s_client -tls1_2 ... |
handshake timeout | 네트워크 방화벽 or OCSP 서버 응답 대기 | tcpdump로 패킷 흐름 확인 |
진단 명령
# TLS 핸드셰이크 전체 과정 출력
openssl s_client -connect db.prod.example.com:5432 -starttls postgres -state -debug 2>&1 | head -60
# 특정 TLS 버전만 테스트
openssl s_client -connect host:3306 -starttls mysql -tls1_3
# 인증서 체인 유효성 검증
openssl verify -CAfile /etc/ssl/ca-bundle.crt /etc/ssl/server.crt
# curl로 TLS 정보 포함 응답
curl -v --cacert ca.pem https://db-proxy.prod.example.com/health 2>&1 | grep -E "SSL|TLS|cert"핵심 요약
- TLS 1.3은 1-RTT로 핸드셰이크를 완료한다. 인증서가 핸드셰이크 안에서 암호화된 채로 전송된다.
- 인증서 체인(Leaf → Intermediate → Root CA)을 서버가 제공하고 클라이언트가 Trust Store와 대조해 검증한다.
- mTLS는 서버와 클라이언트 양방향 인증이다. 서비스 메시와 DB 접근 제어에 적합하다.
- Let's Encrypt 90일 인증서가 표준화되면서 자동 갱신 파이프라인이 필수다. 만료 30일 전 갱신, 만료 모니터링 알림을 설정한다.
openssl s_client로 원격 인증서를 언제든 직접 확인할 수 있다.
References
- RFC 8446 (TLS 1.3), https://datatracker.ietf.org/doc/html/rfc8446
- RFC 8996 (TLS 1.0/1.1 폐기), https://datatracker.ietf.org/doc/html/rfc8996
- Cloudflare, "What happens in a TLS handshake?", https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/
- Let's Encrypt, "Ending OCSP Support in 2025", https://letsencrypt.org/2024/12/05/ending-ocsp
- MySQL 8.0 TLS/SSL Reference, https://dev.mysql.com/doc/refman/8.0/en/using-encrypted-connections.html
- PostgreSQL SSL Support, https://www.postgresql.org/docs/current/ssl-tcp.html
- cert-manager documentation, https://cert-manager.io/docs/
- Apache APISIX, "What is Mutual TLS (mTLS)?", https://apisix.apache.org/learning-center/what-is-mutual-tls/
- systeminternals.dev, "TLS 1.3 Handshake", https://systeminternals.dev/security/tls-handshake/