Nginx와 Fluent Bit를 활용한 클라이언트 TLS 버전 및 암호화 알고리즘 로깅 가이드
웹 서비스의 보안 수준이 지속적으로 강화되면서, 구형 암호화 프로토콜인 TLS 1.0과 TLS 1.1의 지원을 중단하고 TLS 1.2 및 TLS 1.3으로 전환하는 기업이 늘고 있습니다. 하지만 무턱대고 하위 버전의 지원을 끊어버리면 구형 브라우저나 오래된 디바이스(예: 구형 안드로이드, IoT 기기 등)를 사용하는 고객들의 접속이 전면 차단되는 장애가 발생할 수 있습니다.
따라서 보안 정책을 업데이트하기 전, 현재 우리 서비스에 접속하는 사용자들이 어떤 TLS 버전과 암호화 알고리즘(Cipher Suite)을 사용하고 있는지 파악하는 것은 필수적인 사전 작업입니다.
이번 포스팅에서는 Nginx를 웹 서버 또는 리버스 프록시(Reverse Proxy)로 사용하고, 로그 수집기로 Fluent Bit를 활용하는 환경에서 클라이언트의 TLS 버전과 Cipher Suite를 로그로 남기고 올바르게 파싱하는 전체 과정을 상세히 알아보겠습니다. 특히 Nginx 설정 후 로그가 수집되지 않는 흔한 실수와 해결 방법까지 함께 다룹니다.
1. Nginx 설정: Access Log에 TLS 정보 추가하기
가장 먼저 해야 할 일은 웹 서버인 Nginx가 클라이언트와 핸드셰이크(Handshake)를 맺을 때 사용한 TLS 정보를 Access Log에 남기도록 지시하는 것입니다. Nginx는 이를 위해 두 가지 강력한 내장 변수를 제공합니다.
$ssl_protocol: 클라이언트와 설정된 SSL/TLS 프로토콜 버전 (예:TLSv1.2,TLSv1.3)$ssl_cipher: 연결에 사용된 암호화 알고리즘 조합 (예:ECDHE-RSA-AES256-GCM-SHA384)
이 두 변수를 기존의 log_format 지시어에 추가해야 합니다. Nginx의 설정 파일인 nginx.conf (또는 개별 사이트 설정 파일)을 열어 로그 포맷을 수정합니다.
Nginx log_format 수정 예시
기존에 IP 주소, 요청 시간, 응답 코드, User-Agent 등을 수집하고 있던 main이라는 이름의 로그 포맷 맨 끝에 TLS 관련 변수를 추가해 보겠습니다.
Nginx
http {
# 기존 log_format의 맨 끝에 "$ssl_protocol"과 "$ssl_cipher"를 추가합니다.
log_format main '$scheme $remote_addr - $remote_user [$time_local] [$request_time] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$ssl_protocol" "$ssl_cipher"';
# 수정된 main 포맷을 access_log에 적용합니다.
access_log /var/log/nginx/access.log main;
}
설정 항목 상세 분석:
$scheme: 요청이 HTTP인지 HTTPS인지 나타냅니다.$http_x_forwarded_for: 로드밸런서나 프록시를 거쳐온 경우, 클라이언트의 실제 IP를 식별하기 위해 사용하는 사용자 정의 헤더 및 표준 헤더입니다.$request_time: Nginx가 클라이언트로부터 요청을 읽기 시작한 시점부터 응답 전송을 완료할 때까지 걸린 전체 시간(초 단위, 밀리초 해상도)을 의미합니다. 성능 튜닝에 필수적인 지표입니다."$ssl_protocol" "$ssl_cipher": 이번 포스팅의 핵심입니다. 큰따옴표(" ")로 묶어준 이유는 로그를 텍스트 형태로 떨어뜨릴 때 띄어쓰기로 인한 파싱 오류를 방지하기 위함입니다.
수정을 완료했다면 Nginx 설정에 문법적 오류가 없는지 테스트한 후 프로세스를 재시작하여 설정을 반영합니다.
Bash
# Nginx 설정 문법 검사
sudo nginx -t
# Nginx 설정 리로드 (무중단 재시작)
sudo nginx -s reload
2. 문제 발생: Nginx에 추가했는데 로그 수집 시스템(Elasticsearch 등)에서 보이지 않는다면?
Nginx 설정을 마치고 로컬 서버의 /var/log/nginx/access.log 파일을 tail -f 명령어로 확인해 보면 맨 끝에 "TLSv1.3" "TLS_AES_256_GCM_SHA384" 와 같은 문자열이 정상적으로 찍히는 것을 볼 수 있습니다.
그러나 Kibana나 Grafana와 같은 로그 모니터링 대시보드를 확인해 보면 추가한 TLS 필드가 보이지 않거나, 최악의 경우 해당 서버의 Access Log 자체가 아예 수집되지 않는 현상이 발생할 수 있습니다.
이는 Fluent Bit (또는 Fluentd, Logstash)의 파서(Parser) 설정이 업데이트되지 않았기 때문입니다.
정규식 파서(Regex Parser)의 함정
Fluent Bit는 로그 파일의 각 라인을 읽어 들일 때, 설정된 정규 표현식(Regular Expression)을 사용하여 텍스트를 구조화된 JSON(Key-Value) 형태로 변환합니다. 정규식은 로그의 형태와 1:1로 완벽하게 일치해야 작동합니다.
Nginx의 log_format에 두 개의 새로운 필드를 추가하여 텍스트의 길이가 길어지고 구조가 변했지만, Fluent Bit는 여전히 과거의 로그 포맷에 맞는 정규식만 가지고 로그를 해석하려고 시도합니다. 매칭에 실패한 Fluent Bit는 해당 로그 라인을 파싱 실패(Parse Failure)로 간주하여 버리거나 원시 문자열(Raw string) 상태로만 전송하게 됩니다.
3. 해결책: Fluent Bit Parser 정규식 업데이트
이 문제를 해결하려면 Fluent Bit의 설정 파일(주로 parsers.conf 또는 fluent-bit.conf)을 열어 Nginx 로그를 처리하는 파서의 정규식을 수정해야 합니다.
Nginx 로그 포맷 맨 끝에 "$ssl_protocol"과 "$ssl_cipher"가 추가되었으므로, Fluent Bit 파서의 Regex 항목 맨 끝에도 이를 잡아낼 수 있는 Named Capture Group을 추가해 줍니다.
기존 Fluent Bit Parser 설정 (수정 전)
Ini, TOML
[PARSER]
Name nginx
Format regex
Regex ^(?<scheme>[^ ]*) (?<remote_addr>[^ ]*) (?<identd>[^ ]*) (?<remote_user>[^ ]*) \[(?<time>[^\]]*)\] \[(?<request_time>[^\]]*)\] "(?<method>\S+) (?<uri>\S+) (?<protocol>\S+)" (?<status>\d{3}) (?<body_bytes_sent>\d+) "(?<referer>[^"]*)" "(?<user_agent>[^"]*)" "(?<http_x_forwarded_for>[^"]*)"
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
수정된 Fluent Bit Parser 설정 (TLS 변수 추가)
기존 Regex의 맨 끝에 한 칸을 띄우고 "(?<ssl_protocol>[^"]*)" "(?<ssl_cipher>[^"]*)"를 추가합니다.
Ini, TOML
[PARSER]
Name nginx
Format regex
# 정규식 맨 끝에 ssl_protocol과 ssl_cipher 캡처 그룹이 추가되었습니다.
Regex ^(?<scheme>[^ ]*) (?<remote_addr>[^ ]*) (?<identd>[^ ]*) (?<remote_user>[^ ]*) \[(?<time>[^\]]*)\] \[(?<request_time>[^\]]*)\] "(?<method>\S+) (?<uri>\S+) (?<protocol>\S+)" (?<status>\d{3}) (?<body_bytes_sent>\d+) "(?<referer>[^"]*)" "(?<user_agent>[^"]*)" "(?<http_x_forwarded_for>[^"]*)" "(?<ssl_protocol>[^"]*)" "(?<ssl_cipher>[^"]*)"
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
정규식 구문 해설:
(?<이름>패턴): Fluent Bit의 Regex 파서에서 사용하는 문법으로, 패턴에 일치하는 값을 찾아내어 ‘이름’이라는 Key 값을 가진 데이터로 추출합니다."(?<ssl_protocol>[^"]*)": 큰따옴표(")로 시작하고, 그 다음 큰따옴표가 나올 때까지의 모든 문자([^"]*)를 캡처하여ssl_protocol이라는 키에 저장하라는 의미입니다. 이는 Nginx 설정에서 변수를"$ssl_protocol"형태로 큰따옴표로 묶었던 것과 정확히 대응합니다.
설정을 저장한 후 Fluent Bit 서비스를 재시작하여 새로운 파서를 적용합니다.
Bash
sudo systemctl restart fluent-bit
# 또는 컨테이너 환경의 경우
docker restart fluent-bit
이제 통합 로그 시스템에서 데이터를 확인해보면, 각 로그 엔트리에 ssl_protocol: "TLSv1.2" 및 ssl_cipher: "ECDHE-RSA-AES256-GCM-SHA384"와 같은 필드가 예쁘게 구조화되어 수집되는 것을 확인할 수 있습니다.
4. 클라우드 및 로드밸런서 환경에서의 추가 고려사항 (매우 중요)
위의 설정만으로 모든 것이 완벽하게 동작한다면 가장 이상적이겠지만, 현대의 웹 인프라 환경은 대부분 Nginx 앞단에 로드밸런서(AWS ALB, GCP HTTP(S) Load Balancer), CDN(Cloudflare, CloudFront) 또는 방화벽(WAF)이 존재합니다.
이러한 L7 프록시 계층이 존재하는 아키텍처에서는 위 설정이 예상대로 동작하지 않고 빈 값(Dash, -)이 기록될 수 있습니다.
원인: TLS 오프로딩(TLS Offloading / Termination)
클라이언트의 HTTPS 트래픽은 Nginx 서버에 직접 도달하기 전에 앞단의 로드밸런서(ALB)에서 종료(Termination)됩니다. 로드밸런서는 클라이언트와 안전한 TLS 연결을 맺은 뒤 복호화를 수행하고, 뒷단의 Nginx 서버(Target Group)로는 일반적인 HTTP(80 포트) 통신을 통해 데이터를 전달합니다.
결과적으로 Nginx 입장에서는 암호화되지 않은 HTTP 요청을 받은 것이므로, Nginx의 $ssl_protocol 변수는 항상 비어있게 됩니다.
해결책: X-Forwarded 헤더 사용
이 문제를 해결하려면 클라이언트와 실제로 핸드셰이크를 수행한 앞단의 로드밸런서/CDN이, 자신이 확인한 클라이언트의 TLS 정보를 HTTP 커스텀 헤더에 담아 Nginx로 전달해주어야 합니다.
- 앞단 장비 설정: 예를 들어 프록시 서버나 CDN의 설정에서 요청 헤더에
X-SSL-Protocol: TLSv1.2를 추가하도록 구성합니다. (AWS ALB의 경우 Access Log 자체에는 남지만 헤더로 넘겨주는 기본 기능이 부족할 수 있어 CloudFront의 헤더 포워딩 기능이나 커스텀 프록시를 활용해야 할 수 있습니다). - Nginx 수정: 앞단에서 헤더를 넘겨준다면 Nginx는 내장 변수
$ssl_protocol대신, 헤더 값을 읽는$http_x_ssl_protocol변수를 사용해야 합니다.Nginx# Nginx 로드밸런서 후단 환경 로그 포맷 예시 log_format main '... "$http_x_ssl_protocol" "$http_x_ssl_cipher"';
---
### 5. `curl`을 이용한 정상 동작 테스트
설정이 모두 완료되었다면, 외부 클라이언트 입장에서 의도적으로 특정 TLS 버전을 지정하여 서버에 요청을 보내 로그가 정확히 남는지 검증해야 합니다. 리눅스나 Mac의 터미널에서 `curl` 명령어를 사용하여 쉽게 테스트할 수 있습니다.
**TLS 1.2로 강제 접속 테스트:**
```bash
curl -v --tlsv1.2 --tls-max 1.2 https://your-domain.com
TLS 1.3으로 강제 접속 테스트:
Bash
curl -v --tlsv1.3 --tls-max 1.3 https://your-domain.com
서버에 요청을 보낸 직후 Nginx의 access.log 파일을 확인하여, 자신이 보낸 요청에 대해 지정한 TLS 버전이 올바르게 기록되었는지 대조해봅니다. 이후 Kibana/Grafana 대시보드에서 ssl_protocol: TLSv1.2 조건으로 필터링 쿼리를 실행하여 데이터가 정상 수집되었는지 최종 확인하면 모든 작업이 성공적으로 마무리됩니다.
마치며
시스템의 로그는 단순히 에러를 추적하는 도구를 넘어, 서비스의 보안 정책을 수립하고 비즈니스 방향을 결정하는 핵심 데이터가 됩니다. Nginx의 log_format과 Fluent Bit의 Regex Parser를 정확하게 일치시켜 연동하는 것은 데이터 파이프라인 구축의 기본기입니다.
이번에 추가한 ssl_protocol 및 ssl_cipher 데이터를 활용하여 사내 대시보드에 ‘TLS 버전별 트래픽 비율 파이 차트’를 구성해 보세요. 이를 통해 추후 TLS 1.0/1.1에 대한 지원 종료(Drop)를 언제 진행할지, 그리고 그로 인한 임팩트는 어느 정도일지 객관적인 데이터를 바탕으로 자신 있게 결정하실 수 있을 것입니다.