SSL 터널링 완전 가이드: TLS 원리부터 stunnel 구축·인증서 검증·장애 해결까지
오래된 프로그램이나 사내 시스템을 운영하다 보면 데이터를 암호화하지 않은 채 TCP로 주고받는 서비스를 만나게 됩니다. 프로그램을 당장 수정하기는 어렵지만, 그렇다고 평문 통신을 인터넷이나 신뢰할 수 없는 네트워크에 그대로 노출할 수도 없습니다.
이때 사용할 수 있는 방법이 SSL 터널링입니다. 다만 이름과 달리 오늘날 새로 구성하는 터널은 구식 SSL이 아니라 TLS를 사용합니다. 이 글에서는 특정 SSL VPN 제품이 아니라, 기존 TCP 통신을 TLS로 감싸는 원리와 대표 도구인 stunnel의 서버·클라이언트 구성을 초보자 기준으로 설명합니다.
이 글의 기준일은 2026년 8월 27일입니다. 예제의 도메인·IP·포트는 문서용 값이며, 실제 비밀번호·개인키·내부 주소를 공개 글이나 저장소에 넣으면 안 됩니다.
3줄 요약
- SSL 터널링은 애플리케이션을 크게 바꾸지 않고 특정 TCP 통신을 TLS로 암호화하는 방법입니다.
- 보호 범위는 두 TLS 종단점 사이입니다. 터널 앞뒤의 로컬 구간과 서버 자체까지 자동으로 보호하지는 않습니다.
- 운영 환경에서는 암호화만 켜지 말고 CA 기반이면 체인과 이름을, 인증서 고정이면 지정한 리프 인증서와 이름을 함께 검증해야 합니다. TLS 1.2 이상, 방화벽 제한, 필요 시 mTLS도 적용합니다.
1. 먼저 용어부터: SSL이 아니라 TLS입니다
SSL은 TLS의 전신입니다. SSL 2.0과 SSL 3.0은 더 이상 안전한 프로토콜이 아니며 사용해서는 안 됩니다. TLS 1.0과 TLS 1.1도 IETF가 사용 중단했습니다. 새 구성은 TLS 1.3을 우선하고, 호환성이 필요한 경우 안전하게 구성한 TLS 1.2까지만 허용하는 것이 일반적인 기준입니다.
그래도 현장에서는 다음 표현이 익숙하게 남아 있습니다.
- SSL 인증서
- SSL 통신
- SSL 터널
- SSL 종료
따라서 이 글에서도 검색과 이해를 돕기 위해 “SSL 터널링”이라는 표현을 사용하지만, 실제 의미는 TLS 터널링입니다.
2026년 8월 27일 확인 기준 RFC 9846은 TLS 1.3의 현행 기본 명세입니다. 기존 RFC 8446을 대체하지만 TLS 버전 번호는 그대로이며, 하위 호환되는 개정입니다. TLS 1.3은 핸드셰이크에서 상대 신원과 암호화 매개변수, 공유 키를 정하고 Record 계층에서 실제 데이터를 보호합니다.
상대 인증과 TLS 구성을 올바르게 적용했을 때 TLS가 제공하려는 핵심은 세 가지입니다.
- 인증: 연결한 서버가 의도한 서버인지 확인합니다. 필요하면 클라이언트도 인증할 수 있습니다.
- 기밀성: 전송 중인 데이터 내용을 제3자가 읽기 어렵게 합니다.
- 무결성: 데이터가 전송 중 바뀌면 이를 탐지합니다.
다만 TLS가 목적지 IP, 포트, 연결 시각, 통신량 같은 모든 메타데이터까지 숨기는 것은 아닙니다.
2. SSL 터널링은 무엇을 하는가
SSL 터널링은 평문 TCP 데이터를 TLS 연결 안에 넣어 전달합니다. 기존 애플리케이션은 TLS를 직접 처리하지 않아도 됩니다. 애플리케이션 가까이에 있는 터널 프로그램이 평문 데이터를 받아 암호화하고, 반대편 터널 프로그램이 복호화하여 원래 서비스로 전달합니다.
택배에 비유하면 이해하기 쉽습니다. 원래 데이터가 일반 상자라면 TLS 터널은 그 상자를 잠긴 보안 운송함에 넣어 이동시키는 역할을 합니다. 이동 중에는 내용을 읽거나 바꾸기 어렵지만, 출발 전에 상자가 놓인 장소와 도착 후 상자를 꺼내는 장소까지 자동으로 안전해지는 것은 아닙니다.
가장 흔한 구성은 다음 순서로 동작합니다.
- 기존 클라이언트가 자신의
127.0.0.1에 열린 stunnel 포트로 평문 데이터를 보냅니다. - 클라이언트 stunnel이 서버 인증서를 검증하고 원격 stunnel과 TLS 연결을 만듭니다.
- 인터넷 또는 신뢰할 수 없는 구간은 TLS로 암호화됩니다.
- 서버 stunnel이 데이터를 복호화합니다.
- 복호화한 데이터를 서버의
127.0.0.1에 있는 원래 백엔드 서비스로 전달합니다.
핵심 보호 구간은 다음과 같습니다.
클라이언트 stunnel ↔ 서버 stunnel
반대로 아래 구간은 보통 평문입니다.
- 기존 클라이언트에서 로컬 stunnel까지
- 서버 stunnel에서 실제 백엔드까지
이 두 구간을 모두 루프백 주소에 두는 이유가 여기에 있습니다. stunnel과 백엔드가 서로 다른 서버에 있다면 그 사이도 별도의 TLS나 VPN으로 보호해야 합니다.
3. SSL 터널링이 해결하는 것과 해결하지 못하는 것
TLS 터널은 네트워크 구간의 도청과 변조 위험을 줄이는 도구입니다. 다음 문제까지 해결하는 만능 보안 기능은 아닙니다.
- 취약한 애플리케이션과 SQL 인젝션
- 약한 비밀번호나 탈취된 계정
- 잘못된 사용자 권한
- 감염된 클라이언트
- 침해된 서버 운영체제
- 로그나 백업에 남은 민감정보
- 터널 종료 이후의 평문 통신
또한 “암호화됨”과 “올바른 상대에게 연결됨”은 다른 문제입니다. 인증서 검증을 끈 TLS 연결은 공격자의 서버와도 암호화 통신을 할 수 있습니다. 그래서 운영 구성에서는 암호화와 신원 검증을 반드시 함께 적용해야 합니다.
4. HTTPS·SSH 터널·VPN·리버스 프록시와의 차이
비슷해 보이는 기술도 보호 범위와 목적이 다릅니다.
HTTPS
HTTPS는 HTTP를 TLS로 보호하는 표준 방식입니다. 브라우저와 웹 서버는 URL, 헤더, 요청과 응답이라는 HTTP의 의미를 압니다.
stunnel은 안쪽 데이터가 HTTP인지, 데이터베이스 프로토콜인지, 사내 전용 프로토콜인지 해석할 필요가 없습니다. 특정 TCP 스트림을 TLS로 감싸 전달합니다. stunnel을 443번 포트에서 실행한다고 안쪽 프로토콜이 HTTPS로 바뀌는 것도 아닙니다.
SSH 터널
SSH 터널은 SSH 연결 안에서 선택한 포트를 전달합니다. 예를 들어 관리자가 서버의 PostgreSQL에 잠시 접근할 때 다음과 같은 로컬 포워딩을 사용할 수 있습니다.
ssh -N \
-L 15432:127.0.0.1:5432 \
admin@server.example.com
SSH 계정과 호스트 키를 이미 관리하고 있고 일시적인 관리자 접근이 목적이라면 SSH 터널이 더 단순합니다. X.509 인증서 기반으로 애플리케이션 연결을 상시 운영하거나 클라이언트별 인증서를 사용하려면 stunnel이 더 자연스러울 수 있습니다.
SSH 설정을 실제 운영에 적용하기 전에는 SSH 비밀번호 차단과 root 로그인 정책 점검도 함께 확인하는 편이 좋습니다.
VPN
VPN은 대체로 장치나 네트워크 대역을 연결합니다. 여러 서비스와 IP를 함께 사용해야 한다면 VPN이 적합합니다. 반대로 특정 TCP 포트 하나만 암호화하려면 stunnel이 더 작고 명확한 구성이 될 수 있습니다.
예를 들어 A1 서버와 E2 서버 사이의 여러 관리 서비스를 연결한다면 Tailscale 실전 가이드처럼 VPN 기반 접근이 편합니다. 특정 레거시 서비스 하나만 외부 구간에서 암호화한다면 stunnel을 검토할 수 있습니다.
리버스 프록시
Nginx와 HAProxy는 HTTP뿐 아니라 TCP 프록시와 TLS 종료도 지원합니다. HTTP에서는 도메인·경로 라우팅, 헤더 처리, 캐시, 압축, 로드밸런싱 같은 애플리케이션 계층 기능도 제공합니다.
stunnel은 TLS와 TCP 전달에 집중합니다. 공개 웹 서비스라면 리버스 프록시가 보통 더 적합하고, HTTP가 아닌 평문 TCP 서비스를 감싸려면 stunnel이 유용합니다. 관리자 화면을 공개할 때는 터널만 추가할 것이 아니라 Nginx Proxy Manager와 Authelia 접근 제어처럼 인증 계층도 별도로 설계해야 합니다.
5. 언제 stunnel을 사용하면 좋은가
다음 상황에서는 SSL 터널링이 실용적입니다.
- 소스코드를 수정하기 어려운 레거시 TCP 서비스
- TLS를 지원하지 않는 오래된 클라이언트
- 전용 TLS 연결 또는 stunnel의 프로토콜 협상 기능이 지원되는 데이터베이스·메일 포트의 단계적 암호화 전환
- 서로 다른 서버 사이의 단일 TCP 서비스 연결
- 인증서 기반 상호 인증이 필요한 장치나 시스템
- 애플리케이션 변경 없이 전송 구간만 먼저 보호해야 하는 마이그레이션
반대로 다음 상황에서는 다른 방법을 먼저 검토하는 편이 좋습니다.
- 애플리케이션이나 데이터베이스가 안정적인 자체 TLS를 제공하는 경우
- 여러 서브넷과 많은 포트를 함께 연결해야 하는 경우
- HTTP 헤더·URL·쿠키를 기준으로 요청을 처리해야 하는 경우
- 사용자별 세밀한 접근 제어가 핵심인 경우
- UDP 서비스를 TCP용 TLS 예제에 그대로 넣으려는 경우
- 백엔드가 원래 클라이언트 IP를 반드시 알아야 하는 경우
stunnel을 추가하면 프로세스, 포트, 인증서, 로그, 장애 지점도 하나씩 늘어납니다. 얻는 보안 효과와 운영 복잡도를 함께 판단해야 합니다.
6. stunnel 실습 구성
이번 실습은 Ubuntu 24.04 기준입니다. 서버의 127.0.0.1:9000에서 실행되는 테스트 HTTP 서비스를 TLS로 감싸고, 클라이언트 애플리케이션은 자신의 127.0.0.1:18080으로 접속합니다.
- 서버 DNS:
tunnel.example.com - 외부 TLS 포트:
8443 - 서버 백엔드:
127.0.0.1:9000 - 서버 바인딩 예시:
192.0.2.10:8443 - 클라이언트 로컬 포트:
127.0.0.1:18080
example.com과 예제 포트는 실제 운영 정보가 아닙니다. 실행 전 자신의 환경에 맞게 바꿔야 합니다.
6-1. 양쪽에 stunnel 설치
서버와 클라이언트에서 실행합니다.
sudo apt update
sudo apt install --yes stunnel4 openssl
stunnel4 -version
openssl version
Ubuntu 24.04의 공식 패키지 이름은 stunnel4입니다. 배포판과 버전에 따라 실행 파일, systemd 단위, 서비스 계정이 다를 수 있으므로 설치된 파일을 기준으로 확인합니다.
command -v stunnel4
systemctl list-unit-files 'stunnel*'
6-2. 서버의 테스트 백엔드 실행
실제 데이터가 있는 디렉터리를 실수로 공개하지 않도록 빈 임시 디렉터리를 사용합니다.
demo_dir="$(mktemp -d)"
python3 -m http.server 9000 \
--bind 127.0.0.1 \
--directory "$demo_dir"
다른 터미널에서 백엔드부터 확인합니다.
curl --fail --show-error http://127.0.0.1:9000/
여기서 실패한다면 stunnel보다 백엔드를 먼저 정상화해야 합니다.
6-3. 실습용 인증서 생성
운영 환경에서는 공개 CA 또는 조직 내부 CA가 발급한 인증서를 사용합니다. 여기서는 흐름을 이해하기 위해 30일짜리 자체 서명 인증서를 만듭니다. 아래 openssl req는 같은 이름의 파일이 있으면 덮어쓸 수 있으므로 먼저 sudo ls -l /etc/stunnel/certs /etc/stunnel/private로 확인하고, 기존 파일이 있다면 다른 실습용 이름을 사용합니다.
sudo install -d -m 0755 /etc/stunnel/certs
sudo install -d -m 0700 /etc/stunnel/private
sudo openssl req \
-x509 \
-newkey rsa:3072 \
-sha256 \
-nodes \
-days 30 \
-keyout /etc/stunnel/private/demo-server.key \
-out /etc/stunnel/certs/demo-server.crt \
-subj "/CN=tunnel.example.com" \
-addext "subjectAltName=DNS:tunnel.example.com"
sudo chmod 0600 /etc/stunnel/private/demo-server.key
현대 인증서 이름 검증은 SAN을 기준으로 하므로 subjectAltName을 빠뜨리면 안 됩니다. 개인키인 demo-server.key는 클라이언트로 복사하지 않습니다.
인증서 내용을 확인합니다.
sudo openssl x509 \
-in /etc/stunnel/certs/demo-server.crt \
-noout \
-subject \
-issuer \
-dates \
-ext subjectAltName
-nodes는 자동 기동 실습을 위해 암호화하지 않은 개인키를 만드는 옵션입니다. 따라서 파일 권한과 서버 접근 통제가 특히 중요합니다.
6-4. 서버 stunnel 설정
/etc/stunnel/server.conf를 다음처럼 작성합니다.
foreground = yes
setgid = stunnel4
setuid = stunnel4
debug = notice
[legacy-service]
accept = 192.0.2.10:8443
connect = 127.0.0.1:9000
cert = /etc/stunnel/certs/demo-server.crt
key = /etc/stunnel/private/demo-server.key
sslVersionMin = TLSv1.2
중요한 설정은 다음과 같습니다.
accept: 클라이언트의 TLS 연결을 받는 주소입니다.connect: 복호화한 데이터를 전달할 실제 백엔드입니다.cert와key: 서버 신원을 증명할 인증서와 개인키입니다.sslVersionMin: TLS 1.0과 1.1 같은 오래된 버전을 협상하지 않게 합니다.
CA가 발급한 운영 인증서에서는 cert가 리프 인증서와 필요한 중간 인증서를 순서대로 포함한 full-chain 파일을 가리켜야 합니다. 이 실습의 자체 서명 인증서는 한 장으로 체인이 끝납니다.
stunnel@.service는 기본적으로 root로 시작하므로 예제는 인증서와 키를 읽은 뒤 stunnel4 사용자·그룹으로 권한을 낮춥니다. 인증서를 교체한 뒤에는 새 파일을 다시 읽도록 systemctl restart stunnel@server.service를 실행하고 연결 검증까지 반복합니다.
192.0.2.10은 문서용 주소이므로 실제 서버의 사설 IP나 VPN IP로 반드시 바꿉니다. 공용 연결이 필요하다면 클라우드 방화벽과 호스트 방화벽에서 허용한 출발지만 열어야 합니다. 외부 노출 포트와 방화벽 확인 순서처럼 외부 관찰과 서버 내부 리스닝 상태를 함께 확인합니다.
6-5. 서버 실행과 확인
Ubuntu 24.04의 stunnel4 패키지는 /etc/stunnel/<이름>.conf를 읽는 stunnel@.service 템플릿을 제공합니다. 설정에 foreground = yes가 필요한 이유는 systemd 단위가 포그라운드 프로세스를 관리하기 때문입니다.
sudo systemctl enable --now stunnel@server.service
sudo systemctl status --no-pager stunnel@server.service
sudo journalctl \
-u stunnel@server.service \
-n 50 \
--no-pager
수신 주소도 확인합니다.
sudo ss -lntp | grep -E ':(8443)\b'
6-6. 공개 인증서만 클라이언트에 전달
실습용 자체 서명 인증서를 고정하려면 클라이언트에 다음 공개 인증서만 안전한 경로로 전달합니다.
/etc/stunnel/certs/demo-server.crt
다음 개인키는 절대로 서버 밖으로 복사하지 않습니다.
/etc/stunnel/private/demo-server.key
공개 인증서도 공격자가 바꿔치기하면 잘못된 인증서를 신뢰하게 됩니다. 이미 신뢰하는 SSH 경로나 조직의 인증서 배포 시스템을 사용하고 SHA-256 지문을 별도 경로로 비교합니다.
6-7. 클라이언트 stunnel 설정
클라이언트에 공개 인증서를 /etc/stunnel/pinned-server.crt로 저장했다고 가정합니다. /etc/stunnel/client.conf를 다음처럼 작성합니다.
foreground = yes
setgid = stunnel4
setuid = stunnel4
debug = notice
client = yes
[legacy-service]
accept = 127.0.0.1:18080
connect = tunnel.example.com:8443
verifyPeer = yes
CAfile = /etc/stunnel/pinned-server.crt
checkHost = tunnel.example.com
sni = tunnel.example.com
sslVersionMin = TLSv1.2
여기서 역할이 비슷해 보이는 옵션을 구분해야 합니다.
verifyPeer = yes:CAfile에 둔 리프 인증서와 피어 인증서의 Subject와 공개키를 비교하는 고정 방식입니다. CA 체인은 검증하지 않습니다.CAfile:verifyPeer에서는 허용할 리프 인증서를,verifyChain에서는 체인 검증의 신뢰 앵커를 제공합니다.checkHost: 인증서 SAN의 DNS 이름이 예상한 서버와 같은지 확인합니다.sni: 같은 IP의 여러 TLS 서비스 중 어떤 서버를 원하는지 알려줍니다.
SNI는 서버 인증 기능이 아닙니다. sni만 넣고 인증서 체인이나 호스트를 검증하지 않으면 서버 사칭에 취약합니다.
인증서 고정은 서버 인증서를 갱신할 때 새 리프 인증서를 클라이언트에 미리 배포해야 한다는 운영 부담이 있습니다. 교체 기간에는 공식 인증 가이드에 따라 기존 인증서와 새 인증서를 함께 허용한 뒤 이전 인증서를 제거합니다.
공개 CA가 발급한 운영 인증서는 보통 시스템 CA 묶음으로 체인을 검증합니다.
verifyChain = yes
CAfile = /etc/ssl/certs/ca-certificates.crt
checkHost = tunnel.example.com
sni = tunnel.example.com
조직 내부 CA를 사용한다면 CAfile은 별도의 내부 CA 묶음을 가리키거나, 내부 CA를 시스템 신뢰 저장소에 먼저 설치해야 합니다. 내부 CA가 들어 있지 않은 기본 시스템 CA 묶음만 지정해서는 검증에 성공하지 않습니다.
숫자형 verify 옵션은 오래된 구문이므로 새 설정에서는 사용하지 않습니다.
6-8. 클라이언트 실행과 전체 통신 확인
sudo systemctl enable --now stunnel@client.service
sudo systemctl status --no-pager stunnel@client.service
curl --fail --show-error http://127.0.0.1:18080/
마지막 명령이 https://가 아닌 http://인 이유가 중요합니다. curl과 로컬 stunnel 사이에서는 평문 HTTP를 사용하고, 두 stunnel 사이에서 TLS가 처리됩니다.
7. OpenSSL로 인증서와 TLS를 검증하는 방법
TLS 포트가 열렸다는 사실만으로 인증서가 올바른 것은 아닙니다. 클라이언트에서 다음처럼 확인합니다.
openssl s_client \
-connect tunnel.example.com:8443 \
-servername tunnel.example.com \
-verify_hostname tunnel.example.com \
-verify_return_error \
-CAfile /etc/stunnel/pinned-server.crt \
-showcerts \
</dev/null
-verify_return_error가 중요합니다. 일반적인 s_client는 인증서 오류를 출력한 뒤에도 진단을 위해 연결을 계속할 수 있습니다. 이 옵션을 넣으면 검증 오류를 실패로 처리합니다.
-showcerts가 보여 주는 것은 서버가 전송한 인증서 목록입니다. OpenSSL이 실제로 검증에 사용한 인증 경로와 항상 같지는 않습니다. 위 명령에서는 협상된 TLS 버전과 마지막 Verify return code를 먼저 확인합니다. 리프 인증서의 이름과 유효기간은 다음처럼 별도로 읽습니다.
openssl s_client \
-connect tunnel.example.com:8443 \
-servername tunnel.example.com \
-CAfile /etc/stunnel/pinned-server.crt \
-verify_hostname tunnel.example.com \
-verify_return_error \
</dev/null 2>/dev/null | \
openssl x509 -noout \
-subject -issuer -dates -ext subjectAltName
확인할 항목은 다음과 같습니다.
- 실제 연결한 IP와 포트
- 협상된 TLS 버전
- 인증서 Subject와 Issuer
- 유효 시작일과 만료일
- SAN의 DNS 이름
- 서버가 전송한 중간 인증서 목록
- 최종 검증 결과
인증서와 개인키가 한 쌍인지 확인하려면 공개키 해시를 비교합니다.
openssl x509 \
-in /etc/stunnel/certs/demo-server.crt \
-pubkey -noout | openssl sha256
sudo openssl pkey \
-in /etc/stunnel/private/demo-server.key \
-pubout | openssl sha256
두 결과가 같아야 합니다.
8. mTLS로 클라이언트까지 인증하기
일반적인 TLS에서는 클라이언트가 서버 인증서를 확인합니다. mTLS는 서버도 클라이언트 인증서를 요구합니다.
서버에는 허용할 클라이언트 인증서를 발급한 전용 CA를 지정합니다.
verifyChain = yes
CAfile = /etc/stunnel/client-ca.pem
stunnel 서버 모드에서 verifyChain = yes는 클라이언트 인증서를 요구하는 requireCert = yes를 내포합니다. 클라이언트에는 자신의 인증서와 개인키를 추가합니다.
이 설정은 client-ca.pem으로 이어지는 유효한 모든 클라이언트 인증서를 신뢰하며, 특정 리프 인증서의 허용 목록과 일치하는지 검사하지는 않습니다. 따라서 전용 클라이언트 CA의 발급 정책 자체가 접속 허용 정책이어야 합니다. 정확한 리프 허용 목록이 필요하면 verifyPeer를 검토합니다.
cert = /etc/stunnel/client-fullchain.pem
key = /etc/stunnel/private/client.key
위 두 줄은 6-7의 클라이언트 설정에 추가하는 값입니다. 기존의 verifyPeer 또는 verifyChain, CAfile, checkHost, sni는 그대로 유지하여 클라이언트도 서버 인증서를 독립적으로 검증해야 합니다.
각 클라이언트에 서로 다른 인증서를 발급하면 개별 식별과 폐기가 가능해집니다. 다만 실제 폐기를 집행하려면 서버가 최신 CRLfile·CRLpath 또는 OCSP·OCSPaia로 폐기 상태를 검사해야 합니다. 유효한 체인만 확인해서는 폐기 여부를 알 수 없습니다.
mTLS는 애플리케이션 로그인과 권한 관리를 자동으로 대체하지 않습니다. 장치 또는 시스템 연결 인증과 사용자 권한은 별도 계층으로 관리하는 편이 안전합니다.
9. STARTTLS와 전용 TLS 포트를 혼동하지 않기
SMTP 465처럼 일반적으로 연결 시작부터 TLS를 사용하는 서비스와 SMTP 587처럼 흔히 평문 프로토콜 대화 중 STARTTLS로 전환하는 서비스는 다릅니다. PostgreSQL, SMTP, IMAP처럼 프로토콜 내부 협상을 사용하는 대상에 연결 방식을 맞추지 않고 TLS 바이트를 바로 보내면 wrong version number, unknown protocol 같은 오류가 발생할 수 있습니다.
stunnel은 대상 프로토콜에 따라 다음과 같은 협상 기능을 제공합니다.
protocol = smtp
또는 환경에 따라 imap, pgsql 같은 값을 사용할 수 있습니다. stunnel 5.80의 protocol = pgsql은 PostgreSQL의 전통적인 SSLRequest 협상을 구현합니다. PostgreSQL 17 이상에는 ALPN을 사용하는 direct TLS 방식도 있지만 이 stunnel 설정이 이를 대신하는 것은 아닙니다. 공식 프로토콜 문서와 libpq 연결 옵션에서 서버·클라이언트 버전과 연결 방식을 확인합니다. 실제 사용 전에는 설치된 stunnel 버전의 공식 매뉴얼에서도 지원 여부와 동작을 확인해야 합니다.
10. 자주 발생하는 오류와 진단 순서
한꺼번에 모든 설정을 바꾸지 말고 DNS → TCP → TLS → 백엔드 순서로 나눠 확인합니다.
DNS 확인
getent ahosts tunnel.example.com
예상하지 않은 주소가 나오면 인증서보다 DNS를 먼저 수정합니다.
TCP 도달성 확인
nc -vz tunnel.example.com 8443
Connection refused: 대상에 도달했지만 해당 주소·포트에서 서비스가 듣지 않을 가능성이 큽니다.- 시간 초과: 방화벽, 라우팅, 보안 그룹, NAT 경로를 확인합니다.
wrong version number 또는 unknown protocol
TLS 클라이언트를 평문 포트에 연결했거나, 반대로 평문 클라이언트를 TLS 포트에 연결했을 때 흔합니다. STARTTLS 대상에 처음부터 TLS를 보낸 경우도 확인합니다.
unable to get local issuer certificate
신뢰 CA 파일이 잘못되었거나 서버가 중간 인증서를 빠뜨린 경우가 많습니다. CAfile과 서버의 full chain 구성을 확인합니다. 검증을 끄는 방식으로 해결하면 안 됩니다.
hostname mismatch
접속 DNS, connect, checkHost, 인증서 SAN이 같은 이름을 가리키는지 확인합니다. DNS 인증서에 IP로 접속하면서 checkHost를 억지로 맞추지 않습니다. 인증서 SAN에 IP가 포함된 환경은 checkIP를 사용합니다.
certificate required
mTLS 서버가 클라이언트 인증서를 요구하는데 클라이언트가 인증서와 개인키를 보내지 않은 상태입니다.
Address already in use
다른 프로세스가 같은 주소와 포트를 사용 중입니다.
sudo ss -lntp
원인을 확인하지 않고 프로세스를 강제 종료하지 않습니다.
TLS는 성공하지만 응답이 없음
백엔드 프로토콜, connect 주소, 이중 TLS, STARTTLS 방식, 백엔드 타임아웃을 확인합니다. 서버에서 백엔드를 직접 호출해 TLS 문제와 애플리케이션 문제를 분리합니다.
curl --fail --show-error http://127.0.0.1:9000/
Permission denied
개인키 파일 권한, 서비스 실행 계정, 1024 미만 포트 사용 여부를 확인합니다. 개인키를 모든 사용자가 읽을 수 있게 권한을 넓히는 방식은 피합니다.
시작됐지만 포트를 열지 않음
서비스 섹션과 accept가 빠졌는지 확인합니다. stunnel에는 별도의 구성 검사 전용 옵션이 없으므로 초기에는 foreground = yes와 debug = info로 포그라운드 실행하여 오류를 봅니다. 상세 디버그 로그는 민감정보와 로그 용량 문제를 만들 수 있으므로 상시 운영에서는 사용하지 않습니다.
11. stunnel 버전과 보안 공지 확인 방법
2026년 8월 27일 확인 기준 stunnel의 업스트림 최신 릴리스는 5.80이며, 8월 4일 공개되었습니다. 5.80은 긴 프로토콜 메시지를 기록할 때 발생할 수 있는 범위 밖 메모리 접근 문제인 CVE-2026-70368과 SOCKS 서버 모드의 localhost 필터 우회 문제인 CVE-2026-70367을 수정했습니다.
CVE-2026-70368은 5.68~5.79에서 공격자가 제어하는 프로토콜 입력이 1,024바이트를 넘는 로그로 기록될 수 있는 조건에 영향을 주며, 확인된 경로에는 서버 측 protocol = imap이 포함됩니다. 주된 영향은 서비스 거부입니다. CVE-2026-70367은 5.25~5.79에서 공격자가 리스너를 사용할 수 있는 protocol = socks 서버 모드에 영향을 줍니다. 모든 stunnel 구성에 두 취약점이 같은 방식으로 적용되는 것은 아닙니다.
업스트림은 영향을 받는 환경에 5.80 이상으로 올릴 것을 권고합니다. 다만 Ubuntu·Debian 같은 배포판은 이전 버전 번호를 유지하면서 보안 패치를 역이식할 수 있습니다. 단순히 숫자만 비교하지 말고 다음을 함께 확인합니다.
2026년 8월 27일 확인 기준 Ubuntu 24.04의 공식 패키지 페이지에는 3:5.72-1build2가 표시됩니다. 이 버전 번호는 두 업스트림 영향 범위에 들어가므로 “배포판이 알아서 역이식했을 것”이라고 가정하면 안 됩니다. 현재 설치 후보와 Ubuntu의 CVE 상태, 실제 활성 기능을 함께 확인하고 미해결이면 지원되는 보안 업데이트나 공급업체가 권고하는 대응을 적용합니다.
apt-cache policy stunnel4
stunnel4 -version
apt changelog stunnel4
- 업스트림 보안 공지
- Ubuntu 또는 사용 중인 배포판의 CVE 상태
- 설치된 패키지의 보안 업데이트 이력
- SOCKS·IMAP 협상처럼 실제로 활성화한 기능
직접 빌드한 오래된 버전은 배포판 자동 보안 업데이트의 보호를 받지 못할 수 있습니다.
12. 운영 보안 체크리스트
네트워크
- 클라이언트 평문 리스너가
127.0.0.1에만 열려 있는가? - 서버 백엔드가 외부에 직접 노출되지 않는가?
- 서버 TLS 포트가 필요한 인터페이스와 출발지에만 허용되는가?
- 클라우드 방화벽, 호스트 방화벽, Docker 게시 포트가 같은 의도를 갖는가?
- IPv4뿐 아니라 IPv6 노출도 확인했는가?
인증서와 키
- CA 기반이면 체인과 예상 이름을, 핀닝이면 지정한 리프 인증서와 예상 이름을 모두 검증하는가?
- 개인키와 PSK의 소유권·읽기 권한이 필요한 서비스 계정까지로 최소화되었는가?
- 키가 Git, 컨테이너 이미지, 로그, 메일에 들어가지 않았는가?
- 인증서 만료 알림과 갱신 후 재시작 절차가 있는가?
- mTLS 클라이언트에 서로 다른 인증서를 발급했는가?
- mTLS 폐기 정책이 있다면 최신 CRL 또는 OCSP 검사가 실제로 적용되는가?
TLS
- SSL 2.0·3.0과 TLS 1.0·1.1을 사용하지 않는가?
- 최소 버전이 TLS 1.2 이상인가?
- 특별한 정책이 없다면 최신 OpenSSL과 stunnel의 안전한 기본 cipher·curve를 유지하는가?
sni만 넣고 검증을 생략하지 않았는가?- 숫자형
verify같은 오래된 설정을 새 구성에 복사하지 않았는가?
운영
- stunnel과 OpenSSL 보안 업데이트를 적용했는가?
- 포트 열림뿐 아니라 실제 TLS 핸드셰이크를 모니터링하는가?
- 로그에 자격 증명이나 원문 프로토콜 데이터가 남지 않는가?
- 갱신·재시작·롤백 절차를 사전에 시험했는가?
- 장애 시 평문 주소로 자동 우회하지 않는가?
- 원본 클라이언트 IP가 사라져도 감사와 접근 제어에 문제가 없는가?
13. 자주 묻는 질문
SSL 터널링을 적용하면 서비스가 완전히 안전해지나요?
아닙니다. 전송 구간의 도청과 변조 위험을 줄이지만 서버 침해, 계정 탈취, 취약한 애플리케이션, 잘못된 권한은 별도로 해결해야 합니다.
자체 서명 인증서도 사용할 수 있나요?
통제된 테스트나 정확한 인증서 고정에는 사용할 수 있습니다. 다만 인증서를 신뢰할 수 있는 경로로 배포하고 지문을 비교해야 합니다. 클라이언트가 많아지면 공개 CA나 내부 CA가 발급·교체·폐기 관리에 더 적합할 수 있습니다.
서버와 클라이언트 모두 인증서가 필요한가요?
이 글의 인증서 기반 단방향 TLS에서는 서버 인증서가 필요합니다. 서버가 클라이언트까지 인증하는 mTLS를 사용하면 클라이언트 인증서와 개인키도 필요합니다. PSK 같은 비인증서 방식은 별도입니다.
443번 포트를 사용해야 하나요?
아닙니다. 443은 HTTPS의 기본 포트일 뿐입니다. 조직의 방화벽 정책을 우회하려고 임의의 프로토콜을 443번에 숨기는 방식은 보안 정책과 충돌할 수 있습니다.
외부에서는 아무 정보도 볼 수 없나요?
아닙니다. 통신 내용은 보호되지만 IP, 포트, 접속 시각, 연결 시간, 데이터 양 같은 메타데이터는 관찰될 수 있습니다.
TLS를 쓰면 속도가 많이 느려지나요?
초기 핸드셰이크와 암호화 비용은 발생합니다. 실제 영향은 CPU, 연결 수, 메시지 크기, 연결 재사용에 따라 다릅니다. 지연에 민감하거나 동시 접속이 많은 서비스는 반드시 부하 테스트합니다.
웹 서버에도 stunnel을 써야 하나요?
가능은 하지만 일반적인 웹 서비스에는 Nginx, Caddy, HAProxy 같은 HTTPS 리버스 프록시가 더 적합합니다. stunnel은 HTTP가 아닌 레거시 TCP 서비스를 단순하게 감싸는 데 강점이 있습니다.
mTLS를 사용하면 애플리케이션 로그인을 없애도 되나요?
반드시 그렇지는 않습니다. mTLS는 장치나 시스템 연결을 인증하고, 애플리케이션 로그인은 사용자의 권한과 행위를 관리합니다. 두 계층을 함께 유지하는 편이 일반적으로 안전합니다.
마무리
SSL 터널링은 오래된 TCP 서비스를 크게 수정하지 않고도 네트워크 전송 구간에 TLS를 추가할 수 있는 실용적인 방법입니다. 하지만 “TLS를 켰다”는 사실만으로 안전한 구성이 완성되지는 않습니다.
클라이언트는 CA 기반이면 체인과 이름을, 인증서 고정이면 지정한 리프 인증서와 이름을 함께 검증해야 합니다. 개인키를 최소 권한으로 보관하고, 서버 포트를 방화벽으로 제한하며, 터널 종료 이후의 평문 구간과 애플리케이션 인증도 별도로 점검해야 합니다.
새 시스템이라면 애플리케이션이나 데이터베이스가 제공하는 자체 TLS를 우선 검토합니다. stunnel은 레거시 호환, 단계적 전환, 특정 TCP 서비스 보호처럼 목적이 명확할 때 가장 유용합니다.