“클라이언트는 실행되는데 요청이 실패하는” 상황에 필요한 안내입니다. 클라이언트 로그, 코어 오류 로그, 액세스 로그를 구분한 뒤 로컬 리스닝, DNS, 원격 연결, TLS, 프로토콜 인증 순으로 확인합니다. 오류 메시지는 문제를 찾기 위한 단서이지 최종 결론이 아닙니다. 가장 안쪽의 원인을 확인한 다음 관련 설정 항목을 수정하는 편이 재설치만 반복하는 것보다 효과적입니다.
1. 로그 출처를 구분하고 요청을 한 번 재현하기
v2rayN은 클라이언트이며, 실제 연결 처리는 V2Ray 또는 Xray 코어가 담당합니다. 구독 다운로드나 화면 설정 저장 실패는 클라이언트 측 문제이고, 포트 리스닝이나 프록시 핸드셰이크 실패는 대개 코어에서 발생합니다. 안드로이드의 v2rayNG는 Xray 코어를, v2flyNG는 V2Fly 코어를 사용합니다. 코어와 버전에 따라 로그 표현이 다를 수 있으므로 로그 한 줄 전체를 그대로 검색해 답을 찾으려 하지 마세요.
코어 로그는 액세스 로그와 오류 로그로 나뉩니다. 액세스 로그에서는 요청이 인바운드에 도달했는지, 어떤 대상에 접속했는지, 어떤 아웃바운드와 연결됐는지 확인할 수 있습니다. 오류 로그는 시작 및 연결 단계의 문제를 보여 줍니다. accepted가 표시되더라도 인바운드에서 요청을 받았다는 뜻일 뿐, 원격 연결이 성공했거나 웹페이지 내용이 반환됐다는 의미는 아닙니다.
위 연결 흐름은 문제 발생 단계를 구분하기 위한 것으로, 모든 연결이 같은 순서로 도메인을 조회하는 것은 아닙니다. DNS 조회는 라우팅 판단 과정에서 이뤄질 수도 있고 원격 측에 맡길 수도 있습니다. 먼저 요청이 어느 단계에서 실패했는지 확인하고, DNS와 라우팅, 노드 설정을 한꺼번에 바꾸지 마세요.
- 현재 설정을 저장하고 시스템 시간, 선택한 노드, 실제로 실행 중인 코어를 기록합니다.
- 클라이언트의 로그 화면을 열고 구독 업데이트 메시지가 아니라 코어 실행 로그가 표시되는지 확인합니다.
- 다른 앱의 네트워크 요청을 잠시 멈추고, 고정된 대상에 접속해 실패 상황을 한 번만 재현합니다.
- 실패 전후의 로그를 연속해서 보관하고 시간, 대상 주소, 연결 식별자로 같은 요청의 기록을 연결합니다.
2. loglevel 선택: 평소에는 warning, 문제 해결 시에는 debug
log.loglevel은 오류 로그의 상세 수준을 지정합니다. 연결 속도를 설정하거나 액세스 로그를 켜고 끄는 항목은 아닙니다. 평소에는 warning으로 로그를 줄이고, 문제를 재현할 때만 info 또는 debug로 잠시 변경하세요. 원인을 찾으면 원래 수준으로 되돌려 로그가 계속 쌓이거나 연결 정보가 불필요하게 노출되지 않도록 합니다.
| 레벨 | 기록 내용 | 사용 상황 |
|---|---|---|
debug | 디버깅 상세 정보 및 그보다 심각한 메시지 | 복잡한 연결 문제를 짧게 재현할 때 |
info | 일반적인 실행 정보, 경고, 오류 | 시작, 연결, 처리 과정 확인 |
warning | 경고 및 오류 | 일상적인 사용, 일반 정보 줄이기 |
error | 오류 | 명확한 실패 정보만 기록 |
none | 오류 로그 출력 끄기 | 문제 해결 중에는 사용하지 않는 것이 좋습니다 |
아래는 V2Ray 형식 설정의 최상위 log 객체 예시이며, 그대로 실행할 수 있는 전체 설정 파일은 아닙니다. 그래픽 클라이언트에서는 로그 설정 메뉴를 이용하세요. 클라이언트가 생성한 설정 파일은 다음 실행 때 덮어써질 수 있습니다. 로그를 파일이나 콘솔 중 어디에 출력할지는 로그 경로 설정과 클라이언트의 출력 수집 방식에 따라 달라집니다.
{
"log": {
"loglevel": "debug"
}
}
3. connection refused와 포트 충돌: 주소가 로컬인지 원격인지 확인
connection refused는 연결이 명시적으로 거부됐다는 뜻입니다. 대상 포트에서 서비스가 대기 중이지 않거나 방화벽이 연결을 거부할 때 흔히 발생합니다. 먼저 오류에 표시된 IP와 포트를 확인하세요. 127.0.0.1이면 로컬 인바운드를, 노드 서버 주소이면 원격 서비스를 점검합니다. 이 두 상황을 혼동하면 노드 설정을 바꿔도 로컬 프록시에 연결되지 않는 문제가 계속될 수 있습니다.
오류: connect: connection refused
원인: 해당 TCP 연결이 대상 측에서 거부된 것입니다. 이 메시지만으로 계정이 만료됐다고 단정할 수는 없습니다.
해결: 같은 줄에 표시된 대상 주소와 포트를 확인하세요. 로컬 주소라면 코어가 실행 중인지, 원격 주소라면 노드 포트와 서비스 리스닝 상태를 확인합니다.
오류: bind: address already in use
원인: 코어가 리스닝 주소를 바인딩하지 못했습니다. 보통 해당 주소와 포트를 다른 프로세스가 이미 사용 중일 때 발생합니다.
해결: 포트를 사용하는 프로세스를 확인하고 중복 실행된 인스턴스를 종료하세요. 또는 로컬 리스닝 포트를 변경한 뒤 앱의 프록시 주소도 함께 수정합니다.
아래 숫자는 흔히 사용하는 설정 조합의 예시일 뿐, 현재 버전의 고정 기본값이 아닙니다. HTTP와 SOCKS가 하나의 혼합 인바운드로 통합된 설정도 있습니다. 클라이언트에 실제로 표시되는 리스닝 항목을 기준으로 확인하세요. 원격 포트가 443이라고 해서 TLS 서비스를 사용한다고 단정할 수도 없습니다.
Windows에서는 아래 명령으로 두 로컬 포트의 상태를 확인할 수 있습니다. 출력 마지막 열은 PID입니다. TCP 행의 상태가 LISTENING인지 확인하세요. 작업 관리자의 “세부 정보” 탭에서 프로세스 이름도 확인하고, 포트 번호만 보고 정체를 모르는 프로세스를 종료하지 마세요.
netstat -ano | findstr ":10808"
netstat -ano | findstr ":10809"
- 리스닝 항목이 전혀 없음: 먼저 코어 시작 오류를 해결한 다음 브라우저나 터미널에서 테스트합니다.
- 리스닝은 되지만 프로토콜이 다름: HTTP 프록시 주소를 SOCKS 전용 포트로 임의 지정하면 안 됩니다.
- 포트를 10810으로 바꿨는데 앱이 여전히 10808을 가리킴: 계속 연결에 실패합니다. 시스템 프록시와 터미널 환경 변수도 각각 확인하세요.
4. timeout 및 DNS 오류: 가장 구체적인 원인부터 확인
i/o timeout은 읽기나 쓰기가 제한 시간 내에 끝나지 않았다는 뜻이고, context deadline exceeded는 작업이 마감 시간을 넘겼다는 뜻입니다. 둘 다 곧바로 “노드가 만료됐다”는 의미는 아닙니다. DNS 조회, TCP 연결, TLS 핸드셰이크, 응답 대기 중 어느 단계에서도 시간 초과가 발생할 수 있습니다. 오류 체인에서 가장 구체적인 하위 오류를 찾아 어느 주소의 어떤 단계에서 문제가 생겼는지 확인하세요.
오류: dial tcp ...: i/o timeout
원인: 문맥상 TCP 연결 단계라면 제한 시간 안에 연결을 설정하지 못했다는 뜻입니다. 라우팅, 패킷 손실, 차단 또는 원격 서버에 도달할 수 없는 문제가 원인일 수 있습니다.
해결: 원격 주소와 포트를 확인하고 현재 네트워크와 다른 사용 가능한 네트워크에서 각각 테스트하세요. 대기 시간만 늘려 문제를 가리지 않도록 합니다.
오류: no such host
원인: 도메인 조회 결과를 얻지 못했습니다. 도메인 오타, 레코드 누락 또는 DNS 조회 경로 문제가 원인일 수 있습니다.
해결: 오류에 표시된 도메인이 노드 주소인지 접속 대상인지 먼저 확인한 뒤 해당 DNS 리졸버와 도메인 레코드를 점검합니다.
오류: failed to find an available destination
원인: 아웃바운드 선택 또는 연결 실패를 상위 단계에서 요약한 메시지입니다. 이 문구만으로 DNS 문제라고 단정할 수 없습니다.
해결: 이어지는 중첩 오류를 확인하세요. DNS 조회 오류라면 DNS를, 연결 시간 초과라면 원격 서버 도달 여부를, 연결 거부라면 리스닝 서비스를 점검합니다.
예를 들어 브라우저 접속이 실패했는데 로그에는 노드 도메인의 DNS 오류만 있다면, 아직 노드와 연결을 설정하지 못한 상태입니다. 이때 대상 사이트의 라우팅 규칙부터 바꾸면 안 됩니다. 반대로 노드 연결은 성공했고 특정 대상 도메인만 실패한다면 해당 도메인의 DNS 조회와 라우팅 결과를 확인하세요.
- 노드의
address가 도메인인지 IP인지, 포트가 서버 설정과 일치하는지 확인합니다. - 시스템에서 도메인을 조회해 로컬 DNS가 정상인지 먼저 확인합니다. Windows에서는
nslookup뒤에 실제 노드 도메인을 입력하면 됩니다. - 시스템 DNS 조회가 성공해도 코어의 DNS 조회까지 성공하는 것은 아닙니다.
dns.servers, 리졸버 연결 상태, 관련 라우팅을 추가로 확인하세요. - 한 번에 변수 하나만 바꾸고 변경 전후의 오류를 기록하세요. 그래야 어떤 설정이 영향을 줬는지 판단할 수 있습니다.
5. TLS 핸드셰이크 실패: 시간, 인증서 이름, 전송 설정 확인
TCP 연결 성공은 원격 포트에 접속할 수 있다는 뜻일 뿐입니다. 이후에도 인증서, 서버 이름, 프로토콜 협상 문제로 TLS 연결이 중단될 수 있습니다. streamSettings.security가 서버 설정과 일치하는지 확인하고, 일반 TLS를 사용한다면 tlsSettings.serverName도 점검하세요. 서버 주소와 인증서 이름은 서로 다를 수 있지만 서버 배포 방식에 맞아야 합니다.
오류: x509: certificate has expired or is not yet valid
원인: 시스템 시간이 올바르지 않거나 서버 인증서의 유효 기간이 아닙니다.
해결: 먼저 시스템 날짜, 시간, 시간대를 맞추세요. 시간이 정확하다면 서버 운영자가 인증서 갱신 여부와 실제 배포 상태를 확인해야 합니다.
오류: x509: certificate signed by unknown authority
원인: 클라이언트가 인증서 체인을 신뢰할 수 있는 루트 인증서까지 검증하지 못했습니다. 서버 인증서 체인 누락이나 로컬 신뢰 환경 문제가 원인일 수 있습니다.
해결: 인증서 체인 전체와 시스템 인증서 환경을 확인하세요. 검증 건너뛰기를 상시 해결책으로 사용하지 마세요.
오류: remote error: tls: handshake failure
원인: 원격 서버가 TLS 핸드셰이크를 거부했습니다. 이 메시지만으로 원인을 하나로 특정할 수 없습니다.
해결: 서버 설정과 비교해 SNI, TLS 설정, 포트를 확인하세요. 필요하다면 같은 시각의 서버 로그도 함께 살펴보세요.
TLS 연결이 성공한 뒤에도 전송 또는 인증 오류가 발생할 수 있습니다. WebSocket의 path와 요청 Host, gRPC의 serviceName은 서버 설정과 일치해야 합니다. websocket: bad handshake는 경로, Host, 리버스 프록시 전달 설정 또는 전송 방식이 맞지 않을 때 흔히 발생하며, 단순히 인증서 문제로 볼 수 없습니다.
6. 로그에서 설정 항목 찾기: 실패 단계에 맞춰 수정
문제가 발생한 단계에 맞춰 설정 항목을 확인해야 합니다. 시작 단계에서 리스닝 실패가 발생하면 인바운드를, 원격 연결이 실패하면 아웃바운드 주소를 먼저 살펴보세요. 요청이 잘못된 경로로 나가면 라우팅 태그를 확인합니다. 아래는 일반적인 V2Ray 형식 JSON을 기준으로 한 대응표이며, 실제 구조는 사용하는 코어와 클라이언트가 생성한 설정에 따라 다를 수 있습니다.
| 로그에서 확인된 증상 | 우선 확인할 항목 | 중점 확인 사항 |
|---|---|---|
| 시작 시 바인딩 실패 | inbounds[].listen、inbounds[].port | 주소가 로컬 주소인지, 포트가 사용 중인지 |
| 앱에서 로컬 인바운드에 연결 실패 | inbounds[].protocol | 앱에서 사용하는 HTTP 또는 SOCKS가 일치하는지 |
| 원격 연결 거부 또는 연결 시간 초과 | outbounds[].settings의 주소와 포트 | 노드 정보, 원격 리스닝, 네트워크 경로 |
| 도메인 조회 실패 | dns.servers 및 관련 DNS 정책 | 도메인 레코드, 리졸버 연결 상태, DNS 트래픽 경로 |
| 인증서 이름 또는 TLS 핸드셰이크 이상 | streamSettings、tlsSettings.serverName | 보안 유형, SNI, 인증서, 시스템 시간 |
| 요청이 잘못된 아웃바운드로 전달됨 | routing.rules[].outboundTag、outbounds[].tag | 규칙 순서와 태그가 올바르게 연결됐는지 |
VMess, VLESS, Trojan, Shadowsocks는 인증 항목이 서로 다릅니다. 프로토콜 인증 단계까지 진행된 경우에만 UUID, 비밀번호 또는 암호화 방식을 확인하세요. 모든 오류를 “비밀번호 오류”로 단정하면 안 됩니다. EOF가 반복되더라도 읽기 중 연결이 종료됐다는 뜻일 뿐입니다. 핸드셰이크 전인지 데이터 전송 후인지 함께 확인해야 합니다.
- 수정 전: 설정을 백업하고 오류 메시지 원문과 발생 시간을 기록합니다.
- 수정: 현재 실패 단계와 직접 관련된 항목 하나만 바꿉니다. 구독으로 관리하는 노드는 업데이트 시 수동 변경 내용이 덮어써질 수 있습니다.
- 확인: 설정을 적용하기 위해 코어를 재시작한 뒤 같은 앱과 같은 대상으로 다시 테스트합니다.
- 마무리: 접속이 복구됐는지 확인하고 로그 레벨을 낮춘 다음, 더 이상 필요하지 않은 진단 파일을 정리합니다.
7. 로그 문제 해결 FAQ
로그가 비어 있으면 요청이 프록시에 전달되지 않은 건가요?
먼저 코어가 실행 중인지, 오류 로그 레벨이 none이 아닌지, 현재 화면에 코어 로그가 표시되는지 확인하세요. 이상이 없으면 오류 로그에 새 내용이 없을 수도 있습니다. 요청이 들어오는지 확인하려면 액세스 로그를 잠시 확인하고 앱의 프록시 주소가 실제 리스닝 포트를 가리키는지 점검합니다.
브라우저는 되는데 터미널에서는 계속 연결 오류가 나요.
터미널이 시스템 프록시를 사용하지 않을 수도 있습니다. 현재 터미널의 프록시 환경 변수가 리스닝 중인 주소를 가리키는지, 프로토콜이 맞는지 확인하세요. 예를 들어 10809가 실제 HTTP 인바운드일 때만 http://127.0.0.1:10809를 사용합니다. 로그에 해당 요청이 없다면 앱에서 로컬 인바운드까지의 경로부터 점검하세요.
v2rayNG가 화면을 끄면 연결이 끊기고 TLS 오류도 로그에 없어요.
먼저 안드로이드 VPN 연결이 유지되는지, 클라이언트 프로세스가 종료되지 않았는지 확인한 다음 앱의 배터리 사용 및 백그라운드 실행 권한을 살펴보세요. 시스템이 코어를 종료하면 로그가 갑자기 끊길 수 있습니다. 원격 인증서 문제와는 다른 증상입니다. 백그라운드 제한을 조정한 뒤 화면을 잠그고 다시 테스트해 연결이 끊긴 시각을 기록하세요.
debug 로그를 다른 사람에게 보내기 전에 무엇을 삭제해야 하나요?
구독 주소, UUID, 비밀번호, 액세스 토큰과 공개하고 싶지 않은 서버 주소, 접속 도메인, 개인 경로를 제거하세요. 코어 종류, 오류 키워드, 포트 용도, 전후 로그 순서는 남겨두면 좋습니다. 주소는 “노드 주소”나 “대상 주소”로 통일해 바꾸고, 마지막 줄만 잘린 화면 캡처만 보내지 마세요.