JSON Guide · Browse by Field

V2Ray JSON Configuration Guide

From local listeners to remote outbounds, see how a configuration receives, routes, and forwards traffic.

New to these clients? Start with the quick-start guide to import a subscription and test the connection. Use this page to look up fields, understand the generated configuration, and find out why a setting is not taking effect. Examples focus on v2rayN for desktop and v2rayNG for Android.

JSON structure Xray / V2Fly Routing and DNS Log checks

1. JSON Structure and Configuration Scope

Start by distinguishing the client from the core

v2rayN, v2rayNG, and v2flyNG are graphical clients that manage subscriptions, nodes, system proxy settings, and startup. The core is what actually handles connections according to the configuration. Project V provides the foundations for this technical ecosystem, V2Fly continues the community development of V2Ray, and Xray is an independently evolving core. They share many concepts, but their field support is not identical. Before using a working JSON file, check which core it targets and which program the current client actually launches.

This guide follows the traditional JSON configuration structure, with remote protocol examples mainly using common Xray syntax. v2rayNG typically uses Xray, while v2flyNG is an Android alternative based on the V2Fly core. v2rayN can manage different cores, with available features depending on the selected core and client settings. A shared “custom configuration” menu does not mean new fields for one core can be copied straight into another. If you encounter an unknown field or unsupported protocol, check the core before repeatedly changing subscriptions.

How the top-level object connects a request

The outermost configuration is an object. inbounds define where requests enter, outbounds define where they go, and routing determines how traffic is directed between them. dns configures the built-in resolver, policy manages some connection and statistics settings, and log helps you inspect runtime behavior. These fields are not steps in a script that run in the order they appear. A field near the top of the file does not necessarily process traffic before one farther down.

This deliberately minimal local test configuration listens only on a local SOCKS port and connects directly to the requested destination. Use it to check JSON syntax, the port, and application proxy settings; it does not create a remote proxy connection. If another program is using the port, change it in both the configuration and the test application. Starting simple makes it easier to identify which step introduces a problem when you later add remote outbounds, routing, and DNS.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "local-socks",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    }
  ]
}

Valid syntax does not guarantee a valid configuration

JSON object keys and strings use double quotes. Arrays use square brackets, and booleans are written as true or false, not quoted text. Do not add a comma after the last item in an object. Even if a loader accepts comments, strict JSON is easier to validate across clients. Avoid repeating keys in the same object: some parsers overwrite the earlier value, while others reject the file, making the effective configuration hard to determine.

After checking syntax, make sure each field is at the right level. For example, port belongs to an inbound object, while a remote server’s port belongs in that protocol’s server settings. streamSettings is generally a sibling of the outbound’s settings, not a child of the server address object. Structural errors may not prevent the file from loading; a setting might simply be ignored. When reading an example, identify the outer braces before copying fields—this is often more reliable than searching for individual keywords.

Keep a known-working baseline and note the purpose of each change, such as “add a direct route for the private network only.” Node credentials, subscription URLs, and destinations in logs may contain private information; replace them before sharing a configuration. Remote addresses in this guide use example domains and identifiers for illustration, not working services. Only the local direct-connect configuration above can be used for testing as-is, provided its port is free.

2. inbounds: Local Request Entry Points

Listen address, port, and protocol

An inbound answers three questions: which address to listen on, which port to use, and which protocol to accept. Setting listen to 127.0.0.1 makes the IPv4 listener accessible only from the local device. Setting it to 0.0.0.0 listens on all IPv4 interfaces and may allow devices on the local network to connect. This is not a general-purpose compatibility switch; consider the firewall, network environment, and access controls. For a local browser, loopback listening is easier to manage.

port is the local service port, not the remote node’s port. Using the same number for both has no special effect. SOCKS and HTTP are different inbound protocols, so the application must use the matching type. Getting the address and port right is not enough: system proxy settings, browser extensions, and terminal variables may point to different listeners. A page loading in one browser does not prove that every application is using the same inbound.

Add an HTTP listener

This example combines two inbounds and can replace the inbounds array from Chapter 1. SOCKS is for applications that support it; HTTP is for standard HTTP proxies and clients that support CONNECT. Both ports must be free. An HTTP inbound is not a general-purpose UDP listener, and enabling UDP for SOCKS does not mean every application will send SOCKS UDP requests—it only makes that option available to compatible applications.

"inbounds": [
  {
    "tag": "local-socks",
    "listen": "127.0.0.1",
    "port": 10808,
    "protocol": "socks",
    "settings": {
      "auth": "noauth",
      "udp": true
    }
  },
  {
    "tag": "local-http",
    "listen": "127.0.0.1",
    "port": 10809,
    "protocol": "http",
    "settings": {}
  }
]

tag is an internal configuration name that routing rules can reference precisely through inboundTag. Use a short name that describes its purpose, and make sure it is unique. A tag does not configure the system proxy automatically, nor does naming it “global” route all traffic through it. To send different inbounds to different outbounds, name them separately and define the mapping explicitly in your routing rules instead of guessing from client display names.

Sniffing can add destination details

Some applications pass only a destination IP to the core, leaving domain-based rules with nothing to match. Sniffing can try to identify an HTTP hostname or TLS server name from supported initial protocol data. It is commonly configured under sniffing in the inbound object. Set enabled to enable it, then use destOverride to specify which types to detect. Sniffing is not decryption and cannot recover a domain from every connection; encrypted handshake data or an unsupported protocol may leave it with nothing to add.

"sniffing": {
  "enabled": true,
  "destOverride": ["http", "tls"],
  "routeOnly": true
}

This is a common Xray inbound option. routeOnly uses sniffed information for routing without directly rewriting the actual connection destination; support depends on the core. When troubleshooting, distinguish between “a domain was detected,” “a rule matched,” and “the destination connection succeeded.” A sniffing result confirms only the first step—not that DNS, the remote transport, or the destination service is working.

Android VPN routing is not the same as a regular proxy

In v2rayNG, the client works with Android’s VPN permission to capture traffic. Copying in a SOCKS inbound alone will not provide the same whole-device routing. Per-app proxying, excluded apps, and background restrictions are also configured outside the JSON. After importing a custom configuration, check how the client connects to it and which local ports it reserves, so they do not conflict with listeners it creates automatically.

On desktop, distinguish the system proxy from TUN as well. The system proxy affects programs that honor its settings; TUN also involves a virtual interface, permissions, and routing, and cannot be fully described by a single inbound field. If only some apps work, first check whether their traffic reaches the core, then inspect the outbound. For differences between browsers and terminals, see Troubleshooting the system proxy by application. Do not start by changing remote protocol parameters.

3. outbounds: Egress and Protocol Settings

Default outbound and tag references

An outbound determines how the core connects to a destination. A configuration can include a remote proxy, a direct connection, and a blocking outbound, but they do not automatically divide traffic between themselves. In traditional configurations, requests that match no routing rule usually use the first outbound in the list. Changing the array order can therefore change what happens to unmatched requests. Choose a default behavior first, then add exceptions; that is clearer than rearranging tags and guessing from the results.

Routing uses outboundTag to reference an outbound’s tag, and the two values must match exactly. A node’s display name in the client may differ from its tag in the runtime configuration. A common mistake is copying a rule that still points to an old name, leaving the core unable to find the intended outbound. Another is making direct the default and assuming unmatched traffic will use the proxy. Start by checking the first item in the outbound array, then trace each routing reference.

Basic direct and block outbounds

freedom connects directly to the requested destination, while blackhole terminates traffic. These two outbound objects can be referenced independently and added to an existing outbound array. A direct connection still uses the device’s own network and does not guarantee that the destination is reachable. A block outbound is not a backup route. For testing, you can temporarily send a specific domain through direct and compare it with the proxy path; record the change and restore it when done.

{
  "tag": "direct",
  "protocol": "freedom",
  "settings": {}
},
{
  "tag": "block",
  "protocol": "blackhole",
  "settings": {}
}

Where VLESS fields belong

This example shows an Xray VLESS outbound. The example domain and user ID are for illustration only; replace them with the actual server parameters to connect. address is the address used for the remote connection, port is the server’s listening port, and users contains the protocol’s user parameters. id is the user identifier, and the meaning of encryption is defined by the protocol. Here, none does not mean the transport layer has no security.

{
  "tag": "proxy",
  "protocol": "vless",
  "settings": {
    "vnext": [
      {
        "address": "edge.example.org",
        "port": 443,
        "users": [
          {
            "id": "00000000-0000-4000-8000-000000000001",
            "encryption": "none",
            "level": 0
          }
        ]
      }
    ]
  },
  "streamSettings": {
    "network": "ws",
    "security": "tls",
    "tlsSettings": {
      "serverName": "edge.example.org"
    },
    "wsSettings": {
      "path": "/tunnel"
    }
  }
}

Protocol settings and transport settings are separate layers. A correct VLESS user ID will not help if the WebSocket path is wrong; a successful TLS handshake does not prove that user authentication passed. Match the flow-control option, transport type, and other parameters to the server exactly. Two nodes do not become equivalent just because they use the same protocol name. This example omits optional fields; do not guess at values for fields that are not shown.

Do not mix settings from different protocols

VMess also commonly uses the vnext structure, but its user parameters mean something different from VLESS. Trojan typically uses a servers array and a password field. Do not put a Trojan password in VLESS id, or reuse one protocol’s user object as-is for another. After importing a node link, check the protocol and key parameters in the client before trying to assemble a long configuration file by hand.

An outbound may also include advanced options for domain resolution, interface binding, or chained forwarding. These capabilities vary significantly by core, so add them only when needed. A “backup outbound” does not automatically provide failover: adding another outbound will not make the core test speeds and select the faster one. Automatic selection typically requires a selection mechanism, monitoring logic, or client support. Configure it according to the documentation for your core.

If just one outbound fails after import, keep the rest of the working configuration and change only that outbound’s address, authentication, or transport parameters for comparison. Avoid changing the outbound order and routing at the same time: even if the connection starts working, you will not know whether the node was fixed or traffic simply took a different route.

4. Transport and Security: Check Each Layer Separately

Protocol, transport, and encryption are different things

Break a node configuration into three layers: the proxy protocol defines identity and forwarding behavior, the transport carries the connection, and the security layer handles the corresponding handshake and protection. VLESS, WebSocket, and TLS are examples from different layers. A correct protocol name is only a starting point. If the server expects WebSocket and the client uses a plain stream connection, matching the port alone will not make it work.

streamSettings.network selects the transport, and streamSettings.security selects the security mechanism for that layer; put the detailed settings in their corresponding objects. Support for transport names and older or newer fields varies across cores. Do not treat an example for a newer core as a universal template. Keep the original imported parameters, then change them in response to a specific error—especially avoid bulk-replacing network names that merely look similar.

WebSocket and TLS: what to check

This is a transport snippet to put inside the relevant outbound object, not a complete configuration to run by itself. The path must match the server or front-end reverse proxy, including its leading slash. If the actual path includes query parameters, preserve their meaning too. serverName is used for TLS server-name handling, while WebSocket Host belongs to the HTTP request layer. They are often the same value, but they are not the same field.

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

The actual connection address may be a domain or an address specified by the service provider. The certificate name, TLS server name, and HTTP hostname are used at different stages. If the address is reachable but the certificate name does not match, check the domain provided by the server and your device’s clock. Do not simply set allowInsecure to true: bypassing verification can hide a certificate or destination problem and is not a routine fix.

ParameterPurposeWhat to check
addressRemote connection targetDomain resolution and reachability
serverNameTLS server-name handlingCertificate name and server requirements
headers.HostWebSocket HTTP hostnameVirtual host matching on the front-end server
pathWebSocket request pathCase, slashes, and the full path

How REALITY differs from other transports

REALITY client parameters differ from standard TLS settings; changing the name of security alone does not migrate a configuration. The server name, key-related parameters, short ID, and other values must match the server, and field support depends on the core. When importing a share link, check that the client recognized all parameters. If the corresponding settings are missing from the interface, confirm support before trying to put the values into a similarly named object.

Likewise, a gRPC service name is not a WebSocket path, and request headers for HTTP-based transports cannot be reused indiscriminately across transport types. Changing the transport usually requires a matching server-side change; switching it only on the client does not make the server compatible. Before comparing two nodes, confirm that they use the same service parameters. Otherwise, you are measuring different connection paths, not just transport performance.

Read logs by failure stage

For a connection timeout, start by checking the address, port, and path reachability. For a certificate error, check the name, certificate chain, and system clock. If the server returns an HTTP status but the upgrade fails, check the path, hostname, and front-end configuration. An authentication failure points back to the proxy protocol’s user ID or password. Start with the layer identified by the error instead of attributing every failure to DNS.

For troubleshooting, use one node, one application, and a fixed test destination. Disable proxy extensions that rewrite requests and keep the test environment consistent. Do not change the transport, certificate settings, and routing mode all at once. Change one variable at a time and note whether the failure moves earlier or later. Going from “connection could not be established” to “authentication failed” is still progress: it means the network and handshake stages are now working.

5. routing: Rule Order and Destination Matching

Rules are checked from top to bottom; the first match selects the outbound

Routing does not combine every matching result and then score them. Common field-based rules are checked in array order, and the first valid match selects its specified outbound. Put specific exceptions before broad rules. An early “send all traffic through the proxy” rule can prevent later private-network direct rules from ever being reached. When reviewing a configuration, do not just check whether a rule exists—check whether an earlier rule already catches the request.

Different condition fields in a rule usually all need to match, while values within an array for the same field are usually alternatives. For example, putting both inboundTag and domain in one rule means the request must come from the specified inbound and match the domain; it does not mean either condition is sufficient. To match a domain set or an IP range, use two separate rules. This is easier to read and makes it easier to tell from logs which condition took effect.

A readable routing example

This example first blocks a specific example domain, then sends private IP addresses direct, and finally routes the specified domain and its subdomains through the proxy. It depends on existing outbound tags named block, direct, and proxy. geoip:private requires usable GeoIP data files. If those files are not available, start by testing with explicit IP ranges; do not assume that the collection name alone includes every address.

"routing": {
  "domainStrategy": "AsIs",
  "rules": [
    {
      "type": "field",
      "domain": ["full:blocked.example.org"],
      "outboundTag": "block"
    },
    {
      "type": "field",
      "ip": ["geoip:private"],
      "outboundTag": "direct"
    },
    {
      "type": "field",
      "domain": ["domain:example.org"],
      "outboundTag": "proxy"
    }
  ]
}

full: matches an exact domain, domain: matches a domain and its subdomains, regexp: specifies a regular expression, and geosite: references a domain set. Plain text without a prefix may match more broadly, so it is not a good substitute for an exact domain. To match just one host, use an explicit prefix and avoid catching unrelated domains that happen to contain the same text.

Domain resolution strategy changes how matching works

AsIs tries to route using the original destination information, without resolving every domain just to check IP rules. So a request made by domain will not necessarily match a private IP rule just because the domain resolves to a private address. IPIfNonMatch tries to resolve the domain and check relevant rules again if no domain rule matches. IPOnDemand tries to resolve it when the matching process needs an IP address. Both options add a dependency on DNS resolution.

These options mainly affect routing decisions; they are not equivalent to “force all traffic to resolve locally” or “always send DNS through the proxy.” Whether a domain is resolved locally or passed to a remote server also depends on the selected outbound and protocol behavior. If pages load more slowly after changing the strategy, check whether DNS resolution is adding a delay. Enable an IP-based strategy only when IP-set matching is needed; earlier resolution does not automatically make rules more accurate.

How data files relate to client routing rules

geosite.dat and geoip.dat provide data for domain and IP sets; they are not ordinary comments in the core configuration. A set name must exist in the current data file. A failed file load or misspelled name can prevent startup. Categories may also change when files are updated, so note the data source and changes while troubleshooting. For update paths and rule order, see Using GeoIP and GeoSite.

v2rayN routing presets and v2rayNG custom rules may be combined with interface options when the runtime configuration is generated. If your file edits seem to have no effect, check which configuration and mode are active, then see whether the client regenerated the file. Test one exact domain and one known private IP separately, and check the outbound tag. A single page loading successfully does not prove that every routing rule behaves as intended.

6. dns: Resolution Sources and Connection Paths

Built-in DNS is not the same as system DNS

The configuration’s dns section defines how the core’s built-in resolver works; it does not automatically change DNS settings for every application on the device. A browser may use its own encrypted DNS, an app may use the system resolver, and Android VPN traffic may have its DNS handled by the client. Before troubleshooting, identify whose DNS request is failing, where it originates, and which resolver handles it. Changing core DNS while continuing to test a browser’s independent resolution path may produce no visible change.

Whether the routing module, direct outbounds, and other features use the built-in resolver depends on the specific options. If an app has already supplied an IP address, the core may not need to resolve the original domain. If the app passes the domain to the proxy, the domain may remain available for rules or remote resolution. So “no DNS query in the logs” does not necessarily mean the DNS module is broken—the request may have arrived with an IP, or the current path may not require local resolution.

Start with a simple DNS baseline

This snippet specifies two standard DNS servers and prefers IPv4 results when the core needs to resolve a domain. It is a basic example for understanding server lists and query strategies. It does not mean these addresses are reachable on every network or that queries will automatically go through a proxy. Choose resolvers you can access and are willing to use, then configure the query path according to your core’s behavior.

"dns": {
  "servers": [
    "1.1.1.1",
    "8.8.8.8"
  ],
  "queryStrategy": "UseIPv4"
}

UseIPv4 limits the address family returned by the built-in resolver. It does not disable IPv6 in the operating system or rewrite IPv6 connections made directly by applications. This can help isolate address-family issues, but it is not a universal fix for connection failures. If both the device and node have reliable IPv6 support, test against actual destinations before choosing a strategy. Avoid permanently losing a working connection path just to bypass a temporary problem.

Static mappings and domain-specific resolution

hosts can provide static mappings for names resolved by the built-in resolver. This example maps an illustrative test domain to the loopback address, making it useful for testing local service resolution. It does not edit the system hosts file or guarantee that every outbound using that domain will be rewritten; only operations that use this resolution path will use the mapping. Remove it after testing so requests intended for a remote destination are not sent back to the local device.

"dns": {
  "hosts": {
    "lab.example.org": "127.0.0.1"
  },
  "servers": [
    "1.1.1.1"
  ],
  "queryStrategy": "UseIPv4"
}

More complex DNS configurations can direct selected domains to specific resolvers or set expected IP ranges and fallback behavior. These options vary by core, so copying a server object alone does not create complete split DNS. Be explicit about what happens when a targeted resolver returns no acceptable result: whether fallback is allowed and which servers it can use. Map out the branches before adding fields; this is easier to maintain than stacking multiple resolvers.

Avoid DNS dependencies that loop back on themselves

With encrypted DNS, the resolver’s own domain may need to be resolved first. If that resolution depends on a proxy that is not yet connected, startup can get stuck in a dependency loop. Similarly, a proxy server may use a domain whose resolution is configured to require that same proxy, preventing the connection from starting. Keep one clearly reachable baseline resolution path, then add encrypted DNS and routing requirements gradually. Do not initially bind every query to the same unverified outbound.

How DNS queries are sent can also depend on the form of the server address. Standard UDP queries, encrypted DNS, and some local resolution methods do not necessarily follow the same routing behavior. Adding a tag to DNS does not guarantee that every query will use that route. Check your core’s documentation for the relevant server type, then confirm the behavior with logs and network tests. Successful domain resolution only means an address was returned; you still need to verify that the final outbound can connect to it.

If the node shows as connected but pages will not load, compare the resolution result and connection error for the same destination. Check the system clock, address family, and per-app proxy settings before changing DNS. For the full sequence, see Connected but no internet: a step-by-step checklist. Change one resolver at a time and reconnect. App caches may keep using an older result, so take that into account too.

7. policy: Connection Settings and Statistics

Policy levels are not system permission levels

policy groups some connection behavior in a policy object. Keys in levels identify user levels; for example, the string key "0" represents level zero. A protocol user object can reference a level through its level field. This is not an administrator permission level or a node quality score. If you do not need explicit user tiers, understanding the defaults is usually easier than adding several levels for every user.

Not every inbound and outbound uses user levels in the same way. A field being valid JSON does not mean the current protocol has a corresponding user context. To determine whether a policy applies, check protocol support, the level reference, and the connection type. Adding an object under levels alone does not make all traffic use it; the relationship must be established where that mechanism is supported.

Understand timeouts by connection stage

handshake limits the handshake wait for applicable connections. connIdle covers periods without meaningful data activity, while uplinkOnly and downlinkOnly apply when only one direction of a connection remains active. These are not four interchangeable “connection timeouts,” and smaller values are not automatically more responsive. Mobile network switches, persistent notifications, and slow handshakes may need different wait times; adjust them to the observed problem.

"policy": {
  "levels": {
    "0": {
      "handshake": 4,
      "connIdle": 300,
      "uplinkOnly": 2,
      "downlinkOnly": 5
    }
  },
  "system": {
    "statsInboundUplink": false,
    "statsInboundDownlink": false,
    "statsOutboundUplink": false,
    "statsOutboundDownlink": false
  }
}

The time values in the example are in seconds. They show the full configuration hierarchy; they are not optimization recommendations for every device. If the connection closes before the handshake completes, check the network path and server response before deciding the timeout is too short. If it reconnects after a long idle period, consider the app’s reconnect behavior, background restrictions, and network changes. Do not increase every timeout just because you see a disconnect.

Statistics need both the module and its switches

Statistics switches in policy.system control data collection for the relevant directions. The core usually also needs the top-level stats module enabled. For a client to read the data, it needs a suitable query interface or its own integration. Turning on a switch alone does not create a visible dashboard. Inbound statistics describe traffic at the entry, outbound statistics at the exit, and user statistics depend on identifiable users and the related configuration.

"stats": {},
"policy": {
  "system": {
    "statsInboundUplink": true,
    "statsInboundDownlink": true,
    "statsOutboundUplink": true,
    "statsOutboundDownlink": true
  }
}

This is a top-level configuration snippet. Merge it into the existing policy object; do not add a second key with the same name at the same level. Statistics usually reflect data handled by the core and do not equal your carrier’s billable traffic; connection headers, retransmissions, and other system traffic may use different accounting. Client totals may also reset on restart. Confirm the scope and lifetime of the figures before comparing numbers from different screens.

Do not try to fix performance by piling on policy settings

Slow speeds are more commonly caused by the destination service, remote node load, network route, or local traffic capture. Policy timeouts mainly manage connection lifecycles; they do not create extra bandwidth. Some cores let you adjust connection buffers, but a larger buffer is not the same as higher throughput and may increase memory pressure. Get repeatable test results before deciding whether a particular setting is related to the problem.

On Android, check battery restrictions and background operation. On desktop, check whether the system proxy, TUN, and other networking tools are interfering with each other. These factors are not all part of policy. If the connection drops only after the app goes into the background, first check whether the client is still running instead of setting an extremely long idle timeout. If speeds are slow only at certain times, compare different nodes with a direct connection. See A layered approach to diagnosing slow speeds for more guidance.

8. Merging Configurations, Checking Logs, and Rolling Back

Combine snippets into one valid configuration

The examples on this page are partial snippets, not blocks to paste together as-is. Keep one top-level object. Put all inbound objects in the same inbounds array and all outbound objects in the same outbounds array. Add routing rules to the existing rules and check their order again. Keep one clearly defined top-level object each for dns, policy, and log; duplicate keys can overwrite earlier settings.

After validating the syntax, trace a request through the configuration. Does the application connect to the right inbound? What is its tag? Does the request retain its domain or arrive with only an IP? Which rule matches first? Does the selected outbound tag exist, and are the remote transport parameters complete? This manual walkthrough catches many cases where a file starts successfully but traffic behaves incorrectly. Check the default outbound in particular, so a successful test is not actually using an unintended direct connection.

Check what the client loaded first

In v2rayN, check the selected configuration, core type, and routing mode before opening the log window. In v2rayNG, check the active profile, VPN permission, and service status. Menu names may change between client versions, so use the current interface as your guide. Custom configurations may be managed differently from regular node profiles. After editing, restart the relevant service and confirm that the logs show the file you just changed, not a different profile.

Temporarily set the log level to info to make connection targets and outbounds easier to inspect. Restore it to warning when you finish troubleshooting to reduce long-term logging. If the configuration has not parsed, its own log settings may never take effect; check the startup output in the client instead. No log file does not necessarily mean the core never tried to start.

"log": {
  "loglevel": "info"
}

Test the local inbound with a fixed destination

On desktop, if you have a command-line request tool available, specify the local proxy explicitly to separate inbound testing from system proxy behavior. The two commands below test SOCKS and HTTP respectively, using the ports from Chapter 2. The SOCKS command passes the domain through the proxy-side resolution path; the HTTP command uses the local HTTP proxy to access the same example site. These commands check whether a request can pass through the specified inbound; they are not speed tests.

curl --proxy socks5h://127.0.0.1:10808 https://example.org/
curl --proxy http://127.0.0.1:10809 https://example.org/

If the command reports that the local connection was refused, check the listening port and service status first. If the request reaches the core but times out remotely, check the outbound and network. If the command succeeds but the browser fails, check browser extensions, independent proxy settings, and DNS behavior. On Android, repeat the test in the same browser with a fixed destination while watching v2rayNG logs and per-app settings. There is no need to install extra tools just to reproduce desktop commands.

Narrow down the issue by error type

SymptomCheck firstNext step
JSON parse errorQuotes, commas, brackets, and nestingRecheck the object changed most recently
Listener failed to startPort conflicts and address permissionsStop the conflicting program or choose another port
Set not foundData file and set nameCheck the load path and rule spelling
Handshake or authentication failedTransport, security, and user parametersCompare each setting by failure stage
Only some applications failProxy entry point and traffic capture scopeCompare explicit proxy settings with system settings

The last log line may only be a consequence of an earlier failure; for example, a connection-closed message may follow a DNS or authentication error. Keep the context from the same attempt and start at the first clear error rather than looking only at the end. Before sharing logs, replace user IDs, passwords, subscription URLs, and destinations. Keep the field names, error type, and necessary structure. For a closer look at startup errors, see Reading v2rayNG core logs.

Keep a rollback point for every change

A safer sequence is to get the baseline running, add a remote outbound, verify one connection, then add routing, DNS, and policy. Save a copy at each step and note the purpose of each change. If something breaks, restore the most recent working configuration and confirm that the device and network are still working before reapplying the failed change on its own. This separates environment changes from configuration issues and prevents piling more fixes onto an already tangled file.

Keep settings you need to maintain day to day in a place the client can manage, and confirm what survives subscription updates and what gets regenerated. This guide explains how the underlying pieces fit together; you do not need to hand-write a complete JSON file every time you use the client. To revisit importing and connecting, return to the quick-start guide. To choose software for your system, go to the client downloads page.