이 글은 REALITY와 XTLS Vision을 '핸드셰이크'와 '전송' 두 단계로 나눠 설명합니다. REALITY가 대상 사이트에서 인증서 체인을 받아 핸드셰이크를 마치는 방식과 권한 없는 접속이 어디로 전달되는지, XTLS Vision이 TLS 레코드를 식별해 그대로 넘기는 방식과 어떤 계층의 암호화를 아끼는지를 다룹니다. 읽고 나면 내 노드에 어떤 조합이 맞는지 판단할 수 있고, CDN 중계·WebSocket 전송·v2fly 코어 같은 환경에서 왜 쓸 수 없는지, v2rayN과 v2rayNG에서 REALITY 필드와 flow 파라미터를 어떻게 채워야 하는지도 알 수 있습니다.
결론: 하나는 핸드셰이크, 하나는 전송을 바꾼다
REALITY는 VLESS가 전송 보안 계층을 구현하는 방식 중 하나로, 작용 지점은 핸드셰이크 단계입니다. 서버는 자체 도메인 인증서를 보유하지 않고 대상 사이트(dest)에서 인증서 체인을 받아 핸드셰이크에 사용하며, 클라이언트는 공용 CA로 서버를 검증하는 대신 설정에 미리 넣어 둔 공개키로 신원을 확인합니다. 검증을 통과하지 못한 접속은 통째로 dest로 전달되어, 대상 사이트의 실제 페이지와 실제 인증서 체인을 보게 됩니다.
XTLS Vision은 핸드셰이크 이후에 작용합니다. HTTPS 트래픽을 프록시할 때 클라이언트와 대상 웹사이트 사이에는 이미 TLS 계층이 하나 있습니다. XTLS는 클라이언트가 바깥쪽 핸드셰이크를 마친 뒤 안쪽 TLS 레코드를 바깥 레코드 계층에 넣어 다시 암호화하지 않고, 이미 암호화된 레코드를 그대로 전달합니다. 서버도 이를 식별해 대상 사이트로 그대로 넘깁니다. 아끼는 것은 중복된 한 계층이지, 유일한 계층이 아닙니다.
두 기술 모두 Xray 코어에서 구현되며, 따로 쓸 수도 있고 조합할 수도 있습니다. VLESS + TCP + REALITY + flow=xtls-rprx-vision은 현재 서버에서 흔히 쓰는 구성입니다. REALITY만 켜고 flow를 켜지 않아도 핸드셰이크 단계의 위장 효과는 그대로지만, 대용량 트래픽에서는 암·복호화 CPU 비용을 한 번 더 치러야 합니다.
TLS 핸드셰이크 단계: 지연과 지문은 어디서 생기나
TLS 1.3의 전체 핸드셰이크는 1회 왕복이 필요하고, 세션 재개는 0-RTT까지 가능합니다. 핸드셰이크 메시지의 SNI, 암호 스위트 목록, 확장 순서, 키 공유 그룹이 모여 클라이언트 지문을 이루고, 서버가 돌려주는 인증서 체인, 서명 알고리즘, 세션 티켓이 서버 지문을 이룹니다. 이 가운데 하나라도 '일반 HTTPS 사이트'와 다르면 구분 가능한 특징이 될 수 있습니다.
기존 자체 구축 노드는 자체 도메인으로 인증서를 발급받거나 자체 서명 인증서를 쓰는 방식이었습니다. 전자는 도메인과 인증서 갱신 비용이 들고, 후자는 인증서 체인이 불완전하거나 SNI와 인증서가 어긋나기 쉬워 폴백 설정이 제대로 되어 있지 않으면 쉽게 눈에 띕니다.
| 관찰 지점 | 자체 서명 인증서 + 폴백 | REALITY |
|---|---|---|
| 서버가 제시하는 인증서 | 자체 서명 인증서, 브라우저에서 신뢰할 수 없음 경고 | 대상 사이트의 실제 인증서 체인 |
| SNI와 인증서 일치 여부 | 불일치가 흔함 | 일치 |
| 권한 없는 443 포트 접속 | 폴백 설정에 따라 프록시 동작이 드러날 수 있음 | dest로 전달되어 실제 페이지 반환 |
| 클라이언트 신원 검증 방식 | 인증서 체인 검증 또는 검증 생략 | 사전 설정 공개키 + X25519 키 교환 |
| 도메인·인증서 비용 | 도메인 필요, 인증서 갱신 필요 | 자체 도메인과 인증서 불필요 |
표에 나온 차이는 핸드셰이크 초반 몇 개의 메시지에 집중됩니다. REALITY의 목표는 이 메시지들을 '해당 대상 사이트에 직접 접속했을 때'와 구분할 수 없게 만드는 것이지, 트래픽을 숨기는 것이 아닙니다. 트래픽 자체는 여전히 표준 TLS이고 레코드 형식, 버전 번호, 확장이 일반 HTTPS와 같으며, 인증서 출처와 검증 방식만 다른 세트로 바뀝니다.
REALITY: 실제 사이트의 인증서 체인을 빌려 쓴다
서버에서 REALITY를 설정하려면 네 가지 핵심 값이 필요합니다. dest(대상 사이트, 예: www.python.org:443), serverNames(dest와 일치하는 도메인 목록), privateKey(xray x25519 명령으로 생성하는 서버 개인키), shortIds(클라이언트를 구분하는 숏 ID 목록)입니다. 클라이언트는 이에 대응하는 publicKey, serverName, shortId를 가지며, 세 가지가 서버와 일대일로 맞아야 합니다.
연결을 받으면 서버는 개인키와 ClientHello의 임시 공개키로 X25519 연산을 수행하고, 예상한 결과가 나오면 프록시로 처리합니다. 나오지 않으면 연결 전체를 dest로 전달하고 대상 사이트가 정상적으로 응답합니다. 그래서 대상 사이트를 고르는 기준도 분명합니다. 실제로 존재하고, TLS 1.3과 X25519를 지원하며, 정상적인 웹페이지가 있고, 가능하면 HTTP/2까지 지원해야 폴백 동작이 자연스럽습니다.
REALITY + VLESS TCP
권장자체 도메인과 인증서가 필요 없고, 443 단일 포트가 핸드셰이크 위장과 프록시를 동시에 담당합니다. 권한 없는 접속에는 대상 사이트의 실제 페이지가 보입니다.
적합: 직접 연결 환경, 단일 서버 구축, 능동 탐지 대응 필요
도메인 인증서 + TLS + WebSocket
설정이 직관적이지만 인증서를 주기적으로 갱신해야 하고, 전송 계층에 WebSocket을 얹을 수 있어 이후 CDN 중계를 붙이기 편합니다.
적합: 도메인이 이미 있고 표준 443 TLS를 써야 하는 경우
자체 서명 인증서 + WebSocket + CDN
원본 서버 IP는 CDN 뒤에 숨겨지지만 인증서 체인이 불완전한 경우가 많아 폴백을 세심하게 설정해야 합니다. 그렇지 않으면 핸드셰이크 특징이 여전히 뚜렷합니다.
적합: 원본 서버 IP를 숨겨야 하고 설정 단계가 늘어나도 괜찮은 경우
결론: CDN을 쓸지 먼저 정하고, 그다음 핸드셰이크 방식을 고른다
REALITY와 XTLS Vision은 모두 RAW(TCP) 직접 연결에 의존하므로, CDN 중계를 쓰기로 했다면 둘 다 얹을 수 없고 '도메인 인증서 + WebSocket' 조합으로 돌아가야 합니다. 직접 연결로 정했다면 REALITY는 도메인을 사지 않고도 실제 인증서 체인을 확보하는 방법입니다.
제약도 분명히 짚어야 합니다. dest와 serverNames는 반드시 일치해야 하며, 잘못 넣으면 핸드셰이크는 끝나도 페이지에서 인증서 오류가 납니다. shortId와 publicKey는 쌍으로 배포해야 하고, 클라이언트가 잘못 입력하면 아예 연결되지 않습니다. WebSocket, gRPC, HTTP/2 같은 전송 방식을 얹을 수 없어 CDN을 경유할 수 없습니다. v2fly 코어(V2flyNG)는 VLESS 지원은 충실하지만 REALITY 관련 필드를 해석하지 않으므로, REALITY 노드는 Xray 코어에서 실행해야 합니다. v2rayNG에 내장된 것이 바로 Xray 코어입니다.
XTLS Vision: 핸드셰이크 이후 한 계층의 암호화를 덜어낸다
HTTPS를 프록시할 때 데이터 경로는 이렇습니다. 클라이언트와 대상 웹사이트 사이에 TLS 한 계층, 클라이언트와 프록시 서버 사이에 또 한 계층이 있습니다. 바깥 TLS는 클라이언트에서 프록시 서버까지의 구간을 보호합니다. 안쪽 TLS 레코드는 클라이언트가 보낼 때 이미 한 번 암호화되어 있고, 일반적인 구현은 이를 평범한 바이트 스트림으로 취급해 바깥 레코드 계층에 넣어 다시 암호화합니다.
XTLS는 바깥 핸드셰이크가 끝난 뒤 첫 애플리케이션 데이터를 식별합니다. TLS ClientHello라면 이 연결의 데이터 자체가 이미 암호화되어 있다는 뜻이므로 바깥에서 다시 암호화하지 않고 그대로 전달하고, TLS가 아닌 트래픽(예: 평문 HTTP)은 기존 방식대로 암호화해 전송합니다. 판단 기준은 첫 패킷 내용이며 별도 스위치는 필요 없습니다.
Vision은 XTLS의 개선 구현으로, 초기 xtls-rprx-direct가 일부 환경에서 겪던 호환 문제를 해결하고 UUID 검증과 첫 패킷 식별을 추가했습니다. 설정에서는 flow 필드로 나타납니다. 서버 clients에 "flow": "xtls-rprx-vision"을 쓰고 클라이언트 outbound에도 같은 값을 넣어야 하며, 양쪽이 다르면 연결이 실패하거나 일반 VLESS로 되돌아갑니다.
- 장점: 최고 속도로 내려받을 때 대칭 암호화와 복호화를 한 번 덜어 CPU 사용량이 줄어들며, 소프트 라우터와 저전력 기기에서 가장 두드러집니다.
- 장점: 대용량 트래픽에서 처리량이 원시 TCP에 가까워지고, 첫 패킷이 직통으로 나가면서 지연 변동이 줄어듭니다.
- 단점: TLS 트래픽에만 적용되며, 평문 HTTP는 기존 방식대로 암호화됩니다.
- 단점: 양쪽 모두 flow를 입력해야 하고, 클라이언트 코어가 Vision을 지원해야 합니다.
- 단점: REALITY와 마찬가지로 RAW 전송만 가능해 WebSocket과 gRPC를 얹을 수 없습니다.
설정 핵심: 키, flow, 폴백 검증
키 쌍 생성
서버에서 xray x25519를 실행해 개인키와 공개키를 얻습니다. 개인키는 서버 설정에만 남기고, 공개키는 노드 정보 함께 클라이언트에 배포합니다.
인바운드 설정 입력
inbound는 VLESS + TCP로 443을 수신하고, streamSettings.security를 reality로 설정한 뒤 dest, serverNames, shortIds, privateKey를 채웁니다.
클라이언트에 공개키 입력
v2rayN 노드 편집에서 주소, 포트 443, UUID를 넣고 flow는 xtls-rprx-vision을 선택한 다음 publicKey, serverName, shortId를 입력합니다.
폴백 동작 검증
브라우저로 해당 도메인과 포트에 직접 접속하면 dest 사이트의 페이지가 보여야 합니다. 인증서 오류가 뜬다면 dest와 serverNames가 일치하지 않는 것입니다.
코어 로그 확인
연결이 실패하면 로그에서 REALITY, invalid user, flow 관련 키워드를 확인하고, 먼저 공개키와 shortId가 쌍으로 배포되었는지 점검합니다.
{
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{ "id": "8f7a2c1e-5b3d-4a9f-9c21-7d6e4b0a1f33", "flow": "xtls-rprx-vision" }
],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"dest": "www.python.org:443",
"serverNames": ["www.python.org"],
"privateKey": "aP3nQ8vK2sL7dF9xR4tY6uW1zC5bN0mJ2hG8kD3sQ1E",
"shortIds": ["6ba85179e30d4fc2"]
}
}
}
예시에서 dest와 serverNames는 같은 도메인을 가리키며, 이것이 REALITY가 정상적으로 핸드셰이크하는 전제입니다. shortIds에는 여러 개를 나열할 수 있고 클라이언트마다 하나씩 쓰면 되므로, 클라이언트를 바꿀 때 서버 키를 다시 생성할 필요가 없습니다. 클라이언트 쪽 publicKey는 위 개인키의 짝이 되는 공개키이지 다른 임의 값이 아니며, 잘못 넣으면 바로 연결되지 않습니다.
자주 묻는 질문: 탐지, 호환성, 코어 차이
다른 사람이 내 443 포트에 직접 접속하면 무엇이 보이나요?
올바른 공개키를 지니지 않은 연결은 dest 사이트로 전달되어, 그 사이트의 실제 페이지와 인증서 체인이 보입니다. 전제는 serverNames와 dest가 일치하고 폴백 경로가 망가지지 않은 경우입니다.
REALITY 노드에 CDN을 붙일 수 있나요?
불가능합니다. REALITY와 XTLS Vision은 모두 RAW(TCP) 직접 연결에 의존하며, CDN이 감당할 수 있는 WebSocket, gRPC, HTTP/2 같은 전송 방식을 얹을 수 없습니다. '도메인 인증서 + WebSocket' 조합으로 돌아가야 합니다.
클라이언트에 flow를 넣었는데 연결되지 않나요?
먼저 서버 clients에도 같은 flow가 적혀 있는지 확인하고, 클라이언트가 Xray 코어를 쓰는지도 확인합니다. v2fly 코어는 REALITY 필드를 해석하지 않으므로 노드 파라미터에 넣어도 적용되지 않습니다.
REALITY만 켜고 Vision은 켜지 않아도 되나요?
됩니다. REALITY는 핸드셰이크 위장을, Vision은 전송 계층의 중복 암호화를 해결하는 서로 독립적인 기능입니다. flow를 켜지 않아도 핸드셰이크 효과는 그대로이며, 다만 대용량 트래픽에서 CPU 사용량이 조금 더 높아집니다.
dest는 꼭 대형 사이트를 골라야 하나요?
TLS 1.3과 X25519를 지원하고, 정상적인 웹페이지가 있고, 내 노드와 다른 도메인이면 됩니다. dest와 serverNames는 반드시 일치해야 하며, 자기 도메인을 넣거나 API 데이터만 돌려주는 도메인은 쓰지 마세요.
상황별 조합: 직접 연결, CDN, 코어 선택
단일 서버 구축, 독립 IP 보유, 원본 서버를 숨길 필요 없음: VLESS + TCP + REALITY + xtls-rprx-vision. 443 포트 하나가 핸드셰이크 위장과 전송 최적화를 동시에 담당하고, 도메인과 인증서가 필요 없으며 설정 항목이 가장 적습니다. 클라이언트는 v2rayN이든 v2rayNG든 모두 됩니다.
도메인과 인증서가 이미 있고 CDN을 써야 함: VLESS + WebSocket + TLS. 이 경우 REALITY와 Vision은 모두 쓸 수 없고, 최적화 방향은 핸드셰이크와 전송 계층이 아니라 CDN 쪽과 전송 방식 선택에 있습니다. 두 노선이 갈리는 지점은 '원본 서버 IP를 노출할 것인가'이므로, 이 질문에 먼저 답해야 뒤의 파라미터가 의미를 갖습니다.
결론: 전송 방식을 먼저 정하고, 핸드셰이크 방식은 나중에 정한다
CDN이 필요하다면 REALITY와 Vision은 선택지에 없습니다. 직접 연결로 정한 뒤에는 '자체 도메인을 유지할 것인가'로 둘 중 하나를 고르면 됩니다. 유지하고 싶지 않다면 REALITY, 이미 도메인이 있고 표준 TLS가 익숙하다면 도메인 인증서를 쓰면 됩니다. 두 방식 모두 핸드셰이크 왕복은 1-RTT로 같아서, 차이는 지연이 아니라 인증서 출처와 탐지 특징에 있습니다.