이 글의 핵심

v2rayNG에서 연결 버튼을 누른 직후 앱이 멈추거나 로그에 오류가 계속 표시되는 안드로이드 사용자를 위한 안내입니다. 문제가 설정 읽기, 로컬 리스닝, 원격 연결 중 어느 단계에서 발생하는지 먼저 확인한 뒤 오류 흐름을 따라 구체적인 필드를 찾습니다. 로그 수집 방법과 자주 발생하는 오류, 설정 예시를 살펴봅니다. 한 번에 한 곳만 수정해 원래 찾을 수 있었던 문제를 여러 문제로 만들지 않도록 하세요.

먼저 확인할 점: 커널이 시작되지 않는 문제인가, 시작 후 연결에 실패하는 문제인가

v2rayNG는 안드로이드 클라이언트이며, 이 글에서는 클라이언트가 사용하는 Xray 커널 로그를 기준으로 문제를 살펴봅니다. 연결 버튼을 누른 뒤 VPN 아이콘이 표시되어도 안드로이드에서 관련 서비스가 실행 중이라는 뜻일 뿐, 노드가 정상 작동한다고 단정할 수는 없습니다. 반대로 웹페이지가 열리지 않아도 커널 시작 실패라고 볼 수는 없습니다. 로컬 리스닝이 시작된 뒤 처음 접속할 때 시간 초과가 발생한다면 원격 연결 경로를 확인해야 합니다.

문제 단계로그 단서우선 확인할 항목
설정 읽기failed to read config files、invalid characterJSON 문법, 설정 출처
설정 구성failed to build 뒤에 표시되는 구체적인 필드 오류프로토콜 이름, 필드 형식, 커널 지원 여부
로컬 리스닝 시작bind: address already in use리스닝 주소와 로컬 포트
원격 요청 시작i/o timeout、connection refused서버 주소, 원격 포트, 연결 경로
보안 또는 전송 핸드셰이크x509:、bad handshake인증서 도메인, TLS 및 전송 설정

하나의 로그에서 >로 여러 단계의 원인이 연결되어 표시될 수 있습니다. 앞부분의 ‘시작 실패’는 결과이고, 뒤에 나오는 문자, 포트, 인증서 관련 설명이 근본 원인에 더 가깝습니다. 로그를 확인할 때는 전체 오류 흐름을 보존한 뒤 끝부분부터 구체적인 대상을 찾아보세요. 눈에 띄는 빨간색 한 줄만 잘라내지 마세요.

로그를 깔끔하게 수집하는 방법: 연결 버튼을 누르는 순간부터

현재 노드와 사용자 지정 설정을 먼저 보존한 다음 로그를 수집하세요. v2rayNG는 버전에 따라 번역이나 메뉴 위치가 조금 다를 수 있습니다. 일반적으로 메인 화면 왼쪽 위 메뉴에서 ‘로그’를 열고, ‘설정’에서 로그 수준을 조정합니다. 같은 이름의 메뉴가 없다면 현재 버전의 화면을 기준으로 확인하세요. 데스크톱 v2rayN의 ‘매개변수 설정’ 경로를 그대로 따라 해서는 안 됩니다.

  1. 환경 정보 기록

    안드로이드 ‘설정’ → ‘앱’ → ‘v2rayNG’의 앱 정보에서 클라이언트 버전을 확인하고, 안드로이드 버전, 노드 프로토콜, 사용자 지정 설정 사용 여부도 기록하세요. 로그에 커널 버전이 표시되면 함께 보관하세요.

  2. 연결 중지

    v2rayNG 메인 화면으로 돌아가 연결 버튼을 눌러 서비스를 중지하세요. 안드로이드 VPN 서비스 전환으로 문제가 더해지지 않도록 다른 VPN 앱도 잠시 중지하세요.

  3. 로그 수준 조정

    ‘설정’ → ‘로그 수준’에서 먼저 info를 선택하세요. 단서가 부족하면 잠시 debug로 변경합니다. 전체 사용자 지정 JSON을 사용한다면 그 안의 log.loglevel도 확인하세요.

  4. 문제 재현

    ‘로그’를 열고 지우기 기능으로 기존 기록을 삭제한 뒤 메인 화면으로 돌아가 연결을 한 번 시도하세요. 서비스가 계속 실행 중이면 웹페이지를 한 번 열고 로그로 돌아와 같은 시간대의 기록을 확인합니다.

  5. 앞뒤 로그 보존

    처음 실패한 시점 전후의 로그를 빠짐없이 복사하세요. 공유하기 전 구독 주소, UUID, 비밀번호, 서버 정보는 가리되 필드 이름, 오류 순서, 같은 주소 사이의 대응 관계는 유지하세요.

debug는 자세한 정보를, info는 일반적인 처리 과정을 기록합니다. warning은 이상 징후를 알리고 error는 작업 실패를 나타냅니다. 이는 ‘노드 품질’을 평가하는 점수가 아닙니다. 특정 요청에서 오류가 발생해도 커널 전체가 종료된 것은 아닐 수 있고, 오류가 보이지 않는다면 로그 수준이 너무 높게 설정되었을 수도 있습니다. 문제를 해결한 뒤에는 불필요한 기록을 줄이도록 로그 수준을 원래대로 돌려놓으세요.

JSON 문법 오류: 형식을 먼저 고친 다음 노드 확인

사용자 지정 설정을 가져온 직후 시작에 실패했다면 먼저 파싱 오류를 확인하세요. JSON의 속성 이름과 문자열에는 영문 큰따옴표를 사용해야 합니다. 한글 따옴표, 쉼표 누락, 불필요한 마지막 쉼표는 오류를 일으킬 수 있습니다. 설정을 옮기거나 문제를 찾기 쉽도록 아래 예시는 주석 확장 기능을 지원하는 특정 로더에 의존하지 않는 엄격한 JSON 형식으로 작성했습니다.

오류: invalid character '}' looking for beginning of object key string

원인: 객체의 마지막 속성 뒤에 쉼표가 남아 있습니다. 파서는 다음 속성 이름을 기다리다가 닫는 중괄호를 만났습니다.

해결 방법: 오류 위치 바로 앞의 속성을 확인하고 마지막 쉼표를 삭제하세요. 주변 객체도 확인하되 닫는 중괄호를 무작정 지우지 마세요.

오류: unexpected end of JSON input

원인: 내용이 중간에 끝났습니다. 복사가 완전하지 않거나 닫는 괄호가 빠졌거나 문자열이 닫히지 않은 경우가 흔합니다.

해결 방법: 원본 설정을 다시 가져오세요. 마지막 부분의 따옴표, 대괄호, 중괄호가 짝을 이루는지 확인하고, 감으로 괄호를 연달아 추가하지 마세요.

아래 일부 설정 예시에서는 "warning" 뒤에 쉼표가 하나 더 있습니다. 오류가 쉼표가 아니라 다음 줄의 닫는 중괄호를 가리킬 수도 있으므로 위치를 찾을 때는 바로 앞 줄도 확인하세요.

{
  "log": {
    "loglevel": "warning",
  }
}

수정한 예시는 다음과 같습니다. log 작성 방법만 보여주는 예시로, 단독으로 연결할 수 있는 완전한 설정은 아닙니다. 기존 파일에서 해당 객체만 바꾸고 인바운드, 아웃바운드, 라우팅 설정은 덮어쓰지 마세요.

{
  "log": {
    "loglevel": "warning"
  }
}

포트 충돌: 로컬 리스닝을 확인하고 서버 포트는 바꾸지 마세요

bind 오류는 로컬 리스닝 단계에서 발생합니다. 원격 서버 연결 실패와는 다른 문제입니다. 아래 포트 번호는 문제 해결을 위한 예시이며 모든 v2rayNG 설치 환경의 기본값은 아닙니다. ‘설정’에 표시된 로컬 포트와 실제 로그를 기준으로 확인하세요.

10808
예시: 로컬 SOCKS 리스닝 포트
10809
예시: 로컬 HTTP 리스닝 포트
443
예시: 노드의 원격 서버 포트

오류: bind: address already in use

원인: 해당 주소의 리스닝 포트를 다른 프로세스가 사용 중이거나 사용자 지정 설정의 두 인바운드가 같은 포트를 사용하고 있을 수 있습니다.

해결 방법: 먼저 관련 서비스를 중지한 다음 다시 시도하세요. 그래도 실패하면 전체 설정의 inbounds를 확인하고 각 항목의 listen, port, 프로토콜을 대조하세요.

로그에 127.0.0.1:10808 바인딩 실패가 명확히 표시되면 로컬 SOCKS 포트를 사용하지 않는 10818로 바꾼 뒤 다시 시작해 볼 수 있습니다. 해당 SOCKS 포트를 사용하는 다른 앱의 설정도 함께 변경해야 합니다. 노드 편집 화면에서 원격 포트만 바꿔서는 로컬 리스닝을 해제할 수 없으며, 정상적인 노드 설정을 망가뜨릴 수 있습니다.

요점: 주소가 로컬인지 원격인지 먼저 확인한 뒤 포트를 바꾸세요

오류에 127.0.0.1과 bind가 있다면 먼저 휴대폰의 로컬 리스닝을 확인하세요. 서버 주소와 연결 시간 초과가 표시되면 원격 연결을 확인합니다. 두 방향의 설정을 동시에 바꾸지 마세요.

필드 오타와 커널 지원 여부: JSON 문법이 맞는 것만으로는 부족합니다

JSON 파싱에 성공했다는 것은 형식이 올바르다는 뜻일 뿐, Xray가 모든 값을 받아들인다는 뜻은 아닙니다. 예를 들어 streamSettings의 network에는 커널에서 지원하는 전송 식별자를 사용해야 합니다. WebSocket은 ws로 지정해야 하며 임의로 websocket이라고 입력하면 안 됩니다. 클라이언트 화면에 표시된 프로토콜 이름과 설정에 입력할 문자열이 다를 수도 있습니다.

아래는 WebSocket과 TLS의 구조를 확인하기 위한 예시입니다. 도메인과 경로는 예시 값이므로 실제 환경에서는 서버 설정과 일치해야 합니다. 이 객체만으로는 완전한 아웃바운드 설정이 되지 않습니다.

{
  "streamSettings": {
    "network": "ws",
    "security": "tls",
    "tlsSettings": {
      "serverName": "example.com"
    },
    "wsSettings": {
      "path": "/tunnel",
      "headers": {
        "Host": "example.com"
      }
    }
  }
}

알 수 없는 필드가 항상 시작 오류를 일으키는 것은 아닙니다. 일부 로딩 과정에서는 해당 필드를 무시해 의도한 설정이 적용되지 않을 수 있습니다. 반대로 새 매개변수를 지원하지 않는 구형 커널에 사용하면 설정 구성 단계에서 실패할 수 있습니다. 업데이트 전에 클라이언트와 커널 정보를 기록하고 기존 설정을 보관하세요. 업데이트 후에는 기존 노드를 먼저 테스트하고 프로토콜과 전체 라우팅 설정을 동시에 바꾸지 마세요.

전송 및 인증서 오류: 대개 시작 이후 발생합니다

커널에서 이미 로컬 리스닝이 시작되었고 웹페이지에 접속할 때만 아래 오류가 나타난다면 JSON의 쉼표를 계속 확인할 필요는 없습니다. 먼저 노드 주소와 원격 포트를 확인한 다음 TLS, 보안 매개변수, 전송 설정을 대조하세요. 아래는 자주 나타나는 오류 예시이며, 전체 접두어나 부가 문구는 커널 버전에 따라 달라질 수 있습니다.

오류: websocket: bad handshake

원인: WebSocket 핸드셰이크에서 예상한 응답을 받지 못했습니다. 경로, Host, 원격 포트, 리버스 프록시 전달 설정이 일치하지 않을 때 자주 발생합니다.

해결 방법: 서버에서 제공한 정보와 path, Host, TLS 사용 여부를 대조하세요. ‘핸드셰이크’라는 문구만 보고 UUID를 바꾸지 마세요.

오류: x509: certificate has expired or is not yet valid

원인: 인증서의 유효 기간이 맞지 않습니다. 서버 인증서가 만료되었거나 휴대폰 시간이 잘못되었을 수 있습니다.

해결 방법: 먼저 안드로이드 시스템의 ‘날짜 및 시간’에서 자동 설정을 켜고 현재 날짜가 맞는지 확인하세요. 시간이 정확한데도 오류가 계속되면 서비스 제공업체에 인증서를 확인해 달라고 요청하세요.

오류: connection refused

원인: 연결이 명시적으로 거부되었습니다. 대상 포트에서 서비스가 리스닝 중이지 않거나 중간 장치가 거부 응답을 보냈을 수 있습니다.

해결 방법: 오류에 표시된 대상 주소와 포트를 확인한 다음 서버의 리스닝 상태를 확인하세요. 로컬 SOCKS 포트만 바꿔서는 원격 연결 거부를 해결할 수 없습니다.

오류: i/o timeout

원인: 제한 시간 안에 네트워크 작업이 끝나지 않았습니다. 이 문구만으로는 DNS, 연결 경로, 서버 중 어디에 문제가 있는지 판단할 수 없습니다.

해결 방법: 오류 앞에 표시된 작업 대상을 보존하고 도메인 확인, 연결 시도, 핸드셰이크 단계를 구분하세요. 같은 설정에서 Wi-Fi와 모바일 네트워크를 바꿔 비교해 보세요.

TLS의 serverName은 서버 이름과 관련된 핸드셰이크 및 인증서 확인에 사용되고, WebSocket의 Host는 HTTP 요청에 사용됩니다. 두 값은 같을 수도 있고 서버 구성에 따라 다를 수도 있습니다. 오류를 없애려고 인증서 검증을 바로 끄지 마세요. 도메인이나 인증서 설정 문제가 가려질 수 있습니다.

수정 후 다시 테스트: 단서를 보존하고 무작정 연속으로 바꾸지 마세요

한 가지를 수정한 뒤 서비스를 중지하고 이전 로그를 지운 다음 다시 연결하세요. 먼저 커널이 계속 실행되는지 확인한 다음 웹페이지 요청을 한 번 보내고 해당 요청에서 새 오류가 발생했는지 살펴보세요. 구독을 업데이트하면 노드가 다시 생성되어 수동 수정이 덮어써질 수 있으므로, ‘이제 된다’는 사실만 기억하지 말고 수정한 이유를 기록하세요.

로그에 warning만 가득한데 전부 해결해야 하나요?

모든 경고를 시작 실패로 볼 필요는 없습니다. 먼저 시간을 기준으로 이번 연결에 해당하는 오류 흐름을 찾고 서비스가 종료되었는지, 요청이 실패했는지 확인하세요. 현재 증상과 관련된 경고만 처리해 불필요한 변수를 만들지 마세요.

구독을 가져오는 데 성공했는데 왜 시작되지 않나요?

가져오기에 성공했다는 것은 클라이언트가 구독 내용을 받아들였다는 뜻일 뿐, 생성된 모든 매개변수를 현재 커널에서 지원한다는 뜻은 아닙니다. 노드 하나를 선택해 문제를 재현하고 로그의 프로토콜 또는 필드 오류를 확인한 뒤 클라이언트 버전과 노드 요구 사항을 대조하세요.

JSON 오류에 줄 번호가 없으면 문제 위치를 어떻게 찾나요?

사용자 지정 설정이라면 먼저 로컬 편집기에서 사본의 서식을 정리한 뒤 로그에 언급된 필드를 검색하세요. 일반 노드 설정이라면 노드 편집 화면에서 해당 매개변수를 확인하세요. 줄 번호를 찾겠다고 전체 설정을 함부로 다시 작성하지 마세요.

클라이언트를 다시 설치하면 더 빨리 해결되나요?

먼저 설정을 백업하고 로그를 복사하세요. 포트 충돌, 서버 인증서, 잘못된 구독 매개변수는 재설치만으로 해결되지 않습니다. 앱 데이터에 문제가 있다고 확인했고 복원 자료도 보관한 경우에만 데이터 삭제나 재설치를 고려하세요.

마지막으로 간단한 문제 해결 기록을 남겨 두세요. 클라이언트 버전, 사용 중인 커널 정보, 처음 발생한 전체 오류, 변경한 항목 하나, 재테스트 결과를 기록하면 같은 오류가 다시 생겼을 때 기존 문제가 재발한 것인지 다음 단계에서 새 문제가 발생한 것인지 판단하기 쉽습니다.