JSON structure overview and processing flow
See how data moves through the configuration
A V2Ray configuration is not a collection of independent switches; it is an ordered processing chain. Application traffic first enters inbounds, where the core obtains the destination address, port, network type, and inbound tag. Then routing evaluates rules from top to bottom to decide which outbound should handle it. A domain may enter the dns resolution flow during matching or connection setup. Finally, the selected proxy, direct, or blocking exit in outbounds processes the connection. policy, log, and statistics settings do not directly change the destination, but they affect connection lifetime, observability, and troubleshooting. Understanding this chain is more reliable than memorizing fields in isolation.
The top-level object commonly includes log, dns, inbounds, outbounds, routing, and policy. Every inbound and outbound in these arrays should have a clear tag, because routing rules refer to them by tag. A tag is only an internal configuration identifier: it does not create a network connection and must not be confused with a node remark from a subscription. A common mistake is writing outboundTag: proxy when the actual outbound is named proxy-main. The JSON may pass syntax validation, but runtime routing will not work as intended.
{
"log": {
"loglevel": "warning"
},
"dns": {
"servers": [
"1.1.1.1",
"localhost"
]
},
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "node.example.com",
"port": 443,
"users": [
{
"id": "11111111-1111-4111-8111-111111111111",
"security": "auto"
}
]
}
]
}
},
{
"tag": "direct",
"protocol": "freedom"
},
{
"tag": "block",
"protocol": "blackhole"
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": []
}
}
Objects, arrays, and field types
JSON is strict about formatting: objects use braces, arrays use brackets, keys and strings use double quotes, Boolean values must be written as true or false, and numbers must not be quoted. The final field cannot be followed by a comma, and explanatory comments should not be mixed into the configuration. Many “core failed to start” errors are not protocol issues at all, but result from full-width punctuation, curly quotes, duplicate keys, or mismatched bracket levels introduced while copying snippets. Keep the file in UTF-8, run a pure JSON syntax check first, and only then verify whether the current core supports the fields.
Field support depends on the core family. v2rayNG commonly uses the Xray core, v2flyNG uses the V2Fly core, and v2rayN can manage multiple core and configuration types. VMess, VLESS, transport security, and routing fields overlap substantially, but capabilities such as REALITY must be selected according to the actual core in use. Pasting a field exclusive to another core most often results in an “unknown field” error, outbound initialization failure, or the client dropping the field when saving. When migrating a configuration, confirm protocol support first and move sections incrementally rather than replacing the entire file at once.
Where graphical clients end and core configuration begins
v2rayN, v2rayNG, and v2flyNG all convert interface settings, subscription nodes, and routing options into core configuration. Options such as “system proxy,” “VPN service,” and “bypass LAN” do not necessarily map to JSON fields with the same names. Some belong to the operating system integration layer, while others enter routing.rules. During troubleshooting, first identify the layer involved: is the client capturing traffic, is the generated configuration correct, did the core start successfully, and did the target connection reach the intended outbound? Looking only at subscription node status often misses the system proxy and routing layers.
When maintaining a configuration manually, use stable tag names such as socks-in, http-in, proxy, direct, and block. Short, unambiguous names reduce later spelling errors. Complex configurations should also document each outbound’s role instead of treating a node remark as a permanent identifier. Client updates may change node names, while role-based tags should remain stable so routing rules depend only on logical exits such as proxy, direct, and block.
inbounds: where traffic enters
Listen address, port, and exposure
inbounds defines how local applications hand traffic to the core. Desktop clients commonly use SOCKS and HTTP inbounds, while mobile clients may capture application traffic through a system network interface. In a manual configuration, first verify listen and port. Listening on 127.0.0.1 accepts connections only from the local device, which suits browsers, terminals, and system proxy settings. Listening on every network interface expands the reachable scope, so also assess the LAN environment, authentication, and system firewall. Unless other devices explicitly need access, local-only listening is easier to control.
The port must not already be used by another program, and separate inbounds cannot listen on the same address-and-port combination. The local port shown in v2rayN is usually managed by the client. If you edit the configuration manually while the interface still stores the old port, the system proxy may continue pointing to the old value, making it appear that the core is running but websites do not load. Check the listening information in core logs, the local port shown by the client, and the operating system proxy settings; all three must agree.
{
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true,
"ip": "127.0.0.1"
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls"
],
"routeOnly": true
}
},
{
"tag": "http-in",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
]
}
SOCKS, HTTP, and transparent capture
A SOCKS inbound suits applications that support SOCKS5 and can receive UDP requests through udp: true. An HTTP inbound mainly handles HTTP proxying and CONNECT tunnels and is compatible with many desktop applications. Both require the application or system proxy to point explicitly to the corresponding port. Transparent capture is different: the operating system routing table, virtual network interface, or forwarding rules may send traffic into the core, and recovering the original destination is more involved. Graphical clients generally handle these platform differences; do not turn a desktop SOCKS example into a transparent inbound without understanding the system forwarding path.
The contents of settings depend on the inbound protocol. SOCKS fields such as auth and udp cannot be applied to an HTTP inbound, and vice versa. For local-only listening, noauth is common. If LAN devices need access, enable the corresponding sharing feature in the client and configure access control first. Simply broadening the listen address is not enough: the system firewall, network profile, and authentication still determine the actual exposure.
Traffic sniffing and routeOnly
sniffing identifies domains from HTTP requests or TLS handshakes, allowing domain rules to match even when the original destination is only an IP address. destOverride specifies the protocol types that may be identified, while routeOnly means the sniffed result is mainly used for routing decisions rather than directly rewriting the final connection target. Enabling sniffing does not guarantee a domain for every connection: encrypted client greetings, non-standard protocols, established connections, and traffic without host information may still expose only an IP.
If some sites behave abnormally after sniffing is enabled, troubleshoot along two paths. First, temporarily disable sniffing to see whether domain detection or destination rewriting is responsible. Second, keep sniffing enabled but use routeOnly so the domain participates only in rule matching. If rules are mainly IP-based, sniffing offers limited benefit; if the configuration relies heavily on domain, geosite, or suffix rules, it is usually more valuable. Let the rule design determine the choice rather than treating sniffing as a fixed performance switch.
| Inbound type | Common use | Key checks |
|---|---|---|
| SOCKS | Browsers, terminals, and applications that support SOCKS5 | UDP toggle, listen address, local port |
| HTTP | System proxy and software that supports HTTP CONNECT | Port consistency, proxy protocol selection |
| Transparent capture | Unified capture through system forwarding or a virtual network interface | Platform permissions, destination recovery, routing loops |
outbounds: proxy, direct, and block
Outbound roles and selection order
outbounds defines the exits available to the core. A typical configuration includes a proxy outbound, a freedom direct outbound, and a blackhole blocking outbound. Routing rules select one through outboundTag. When no rule matches, the core commonly uses the first available item in the outbound array, so array order matters. To proxy by default, put the proxy exit first. To connect directly by default, adjust the order explicitly and add rules for traffic that needs the proxy; renaming tags alone does not change behavior.
An outbound combines a protocol layer, server account, transport, and security layer. For VMess or VLESS, settings describes the server address, port, and user information, while streamSettings describes transports such as TCP, WebSocket, and gRPC, along with security methods such as TLS or REALITY. All four parts must match the server parameters. A correct address with the wrong transport, or a correct port with the wrong security layer, will cause the handshake to fail.
{
"outbounds": [
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "edge.example.com",
"port": 443,
"users": [
{
"id": "22222222-2222-4222-8222-222222222222",
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "edge.example.com",
"allowInsecure": false
}
}
},
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {
"response": {
"type": "none"
}
}
}
]
}
Server address, user, and transport layer
address may be a domain or IP address, while port must be numeric. With a domain, the core must resolve it first, so DNS configuration can indirectly affect proxy outbound setup. User identifiers, encryption settings, and flow fields vary by protocol; do not rename a VMess user object and treat it as a VLESS configuration. When a share link or subscription already contains these parameters, let the client parse them where possible. Manual entry is useful for checking fields, but be careful not to omit the path, host name, service name, or server name.
streamSettings.network specifies the underlying transport. WebSocket usually also requires a path and request-host header; gRPC requires a service name; TCP may use custom headers or a security layer as well. Sharing a transport name does not mean the other parameters can be omitted. During troubleshooting, separate the “protocol account” from the “transport handshake”: account errors usually fail during protocol authentication, while transport errors appear earlier as connection closes, TLS name mismatches, or unreachable service paths.
TLS, REALITY, and core differences
serverName in a TLS configuration is used for server-name verification and the handshake. Fill it in with the value supplied for the node; it is not necessarily the same as the connection address. allowInsecure controls certificate verification and should normally remain false. For a REALITY node, confirm that the client is using an Xray core with support for the feature and enter the supplied server name, public key, short ID, fingerprint, and other fields in full. v2flyNG uses the V2Fly core, so choose a client based on protocol compatibility rather than its interface alone.
v2rayN manages desktop nodes and configurations for different cores and suits Windows, macOS, and Linux. v2rayNG targets Android, with Xray protocol support as the main selection criterion; v2flyNG is an alternative for the V2Fly core path. All three are configuration-management layers, and connectivity still depends on protocol parameters, core support, and the network path. To install a client, choose a platform on the client download page.
Direct, block, and chained exits
freedom establishes the destination connection directly through the local network, making it suitable for LAN addresses, device addresses, or domains that explicitly require a local exit. blackhole terminates connections matched by a rule. Blocking rules should appear early enough and use the narrowest practical scope, so they do not also block update services, login endpoints, or LAN devices. If logs only show that a connection was closed, first check whether the destination accidentally matched block instead of immediately replacing the proxy node.
A complex configuration may pass one outbound to another through proxySettings or another mechanism, creating a forward proxy or chained exit. The longer the chain, the higher the troubleshooting cost: a DNS, transport, or authentication failure at any layer can interrupt the final connection. Before building a chain, verify each exit independently, then connect them one layer at a time. Keep intermediate outbound tags unique and prevent rules from selecting exits intended only for internal chain links.
routing: rule order and matching scope
Top to bottom; the first match wins
routing.rules is the core of traffic splitting. Rules are checked from top to bottom in array order, and a connection normally stops being evaluated after the first match. Put specific rules before general ones: direct LAN traffic can precede a broad proxy rule, explicit blocks should precede wide suffix rules, and a fallback policy belongs in the final rule or default outbound. Many routing failures are not caused by incorrect conditions, but by a broader rule earlier in the list capturing the traffic first.
Rules can match domains, IPs, ports, network types, inbound tags, sniffing results, or user identifiers. When several kinds of conditions appear in one rule, they generally must all be true; multiple values in the same field array usually mean that any one value may match. Before writing a rule, turn the requirement into explicit conditions. For example, “send UDP traffic entering through the SOCKS inbound with destination port 53 to the DNS outbound” contains inbound-tag, port, and network conditions; omitting any one may broaden the match.
{
"routing": {
"domainStrategy": "IPIfNonMatch",
"domainMatcher": "hybrid",
"rules": [
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "direct"
},
{
"type": "field",
"domain": [
"domain:example.internal",
"full:router.example.internal"
],
"outboundTag": "direct"
},
{
"type": "field",
"domain": [
"geosite:category-ads-all"
],
"outboundTag": "block"
},
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
}
}
Four common domain-matching forms
full: matches a complete domain name and only the exact host; domain: usually covers the specified domain and its subdomains; regexp: uses a regular expression and is flexible but easier to broaden accidentally; geosite: references category data available to the core. An unprefixed form may have context-specific meaning. For maintainability, state the match type explicitly. When troubleshooting a domain rule, record the actual host name being accessed rather than relying only on the page title or product name.
Regular-expression rules require two layers of escaping. The expression itself uses backslashes, and placing it in a JSON string requires JSON escaping as well, so visual inspection can easily miss an error. If full: or domain: can express the requirement, there is no need to prefer a regular expression. Category data is not live network status; it is a set of domains or addresses updated with the data file. Match results therefore depend on the data currently loaded by the core.
IP, port, network, and inbound tags
IP rules can contain a single address, a CIDR range, or a geoip: category. geoip:private is commonly used for direct connections to LAN and reserved addresses, but it should still be checked against the actual network. If a corporate or home network uses unusual address ranges, add those ranges explicitly. Ports can be a single value or range, and common network types include tcp, udp, or both. The more precise the conditions, the fewer unintended side effects the rule has.
inboundTag lets the same destination use different policies at different entry points. For example, traffic entering through socks-in can use the proxy while a local service entering through another inbound stays direct. This is easier to maintain than duplicating core instances. The inbound tag must exist and match exactly. Tag comparisons are usually case-sensitive, and extra spaces, capitalization changes, or client-generated configuration updates can invalidate a rule.
How domainStrategy triggers resolution
AsIs prioritizes the domain already present in the connection and does not actively resolve it for IP rules; IPIfNonMatch resolves the domain only after domain rules fail, then tries IP rules; IPOnDemand triggers resolution more aggressively when IP conditions are needed. More aggressive strategies improve IP-based routing but create a deeper DNS dependency. If the DNS server is unreachable or returns unexpected results, routing may become slow or select the wrong path.
Choose the strategy to match the rule set. When domain rules dominate and IP rules handle only literal addresses, AsIs is more intuitive. If domains must ultimately match regional IP categories, consider IPIfNonMatch. After changing it, test both domain targets and literal IP targets and confirm the selected outbound in the logs. For a systematic checklist when a connection succeeds but access fails, see the step-by-step DNS, routing, and system proxy troubleshooting checklist.
DNS configuration: resolution paths and routing consistency
Built-in DNS and system DNS
The dns module provides internal domain resolution rules and server selection for routing decisions, proxy-server name resolution, and requests handled by the core. It does not necessarily replace all operating-system DNS behavior: an application may issue encrypted DNS itself, a browser may bypass system settings, or traffic may never enter the core. During troubleshooting, first determine who initiated the lookup, then inspect V2Ray DNS settings. Not every domain failure belongs to the same module.
servers may contain ordinary addresses, localhost, or server objects with domain matching. A simple list lets the configured strategy choose a resolver; server objects can send specific domains to a designated DNS service. If the resolver itself is named by a domain, the core must resolve that address first, creating an extra dependency. Use clearly reachable addresses for basic resolution and confirm whether queries to them should go direct or through the proxy.
{
"dns": {
"queryStrategy": "UseIP",
"disableCache": false,
"disableFallback": false,
"servers": [
{
"address": "1.1.1.1",
"domains": [
"domain:example.com"
],
"skipFallback": true
},
{
"address": "8.8.8.8",
"domains": [
"geosite:geolocation-!cn"
]
},
"localhost"
],
"hosts": {
"router.example.internal": "192.168.1.1"
}
}
}
hosts, caching, and query strategy
hosts provides static domain mappings and suits fixed LAN services or test environments. It usually takes precedence over external queries, so an old mapping can continue overriding real DNS results. If a server address changes but connections still reach the old IP, check hosts before checking the DNS cache. Keep static mappings limited to genuinely stable targets and update them whenever the network changes.
disableCache controls the core’s DNS cache. Keeping the cache reduces repeated lookups and connection delays; temporarily disabling it can help diagnose changing resolutions, but doing so permanently increases query volume. queryStrategy controls which address families are preferred. Common choices include using IP addresses, IPv4 only, or IPv6 only; exact names and support depend on the current core. If the local network lacks a stable IPv6 path but IPv6 results are preferred, a domain may resolve successfully while connections time out.
Assigning resolvers by domain
domains in a server object limits the domains handled by that resolver. Its syntax is similar to routing domain rules and can use exact domains, suffixes, or category data. skipFallback prevents matching domains from entering the general fallback process, which suits internal domains that must use a specific resolver. If the match is too broad, a failure at that server can remove other resolution paths.
disableFallback changes fallback behavior globally and should not be enabled before you understand server matching order. A safer approach is to retain fallback, use logs to see which server handles a specific domain, and then add skipFallback only for a small set of confirmed targets. DNS splitting and traffic routing should remain consistent: if a domain’s lookup uses the proxy but the resulting connection is sent direct, the apparent egress locations may differ. The reverse mismatch creates the same diagnostic difficulty.
Startup dependencies for proxy-server domains
If an outbound’s address is a domain, the core must resolve it before establishing the proxy connection. At that point no proxy tunnel exists yet, so a resolver reachable only through the proxy can create a startup loop. The answer is not to replace the node blindly, but to ensure that at least one basic DNS path works before the proxy is established and that the proxy server’s domain uses it. This is especially important with complex DNS outbounds.
To determine whether DNS is the root cause, narrow the scope in four steps: verify the outbound path with a fixed IP target; check whether the node server domain resolves; inspect the target domain’s A and AAAA results; then observe whether the resolution result changed the selected outbound. If a fixed IP also fails, DNS is probably not the only issue. If the node connects but some domains fail, continue with domain matching, caching, and address-family selection.
| Symptom | Check first | Common boundary |
|---|---|---|
| All domains fail | Basic resolver reachability, node-domain resolution | A resolution loop before the proxy is established |
| Some domains fail | Server-object domains, hosts, fallback | Overly broad rules or stale static mappings |
| Resolution succeeds but the connection times out | Address family, routing match, target outbound | Inconsistent IPv6 path or exit selection |
policy: connection lifetime and statistics
System policy and user levels
policy controls connection timeouts, handshake duration, idle detection, and statistics switches. It does not choose a node and does not replace routing rules. The configuration usually contains levels and system: levels applies connection policies by user level, with the level in a protocol user object corresponding to one of these keys; system controls global statistics dimensions. A user without an explicit level normally uses the default level policy.
Level keys appear as strings in JSON, such as "0". Protocols may reference user levels differently, and clients may omit unused policies when generating configuration. Before adding a level manually, confirm that the relevant inbound or user object actually references it. Creating a "1" policy without any user setting level: 1 does not automatically raise the level of every connection.
{
"policy": {
"levels": {
"0": {
"handshake": 4,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5,
"statsUserUplink": false,
"statsUserDownlink": false,
"bufferSize": 0
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true,
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"stats": {}
}
handshake and connIdle
handshake limits how long the connection-establishment phase may wait, usually in seconds. A value that is too low can terminate high-latency networks, first DNS lookups, or complex handshakes before they complete; a value that is too high keeps clearly failed connections around longer. First distinguish a slow connection setup from slow transfer after setup. Only the former is directly related to handshake wait time. If logs show that the outbound is already established, increasing the handshake timeout usually will not help.
connIdle controls how long a connection may remain open without data activity. Messaging, long polling, remote terminals, and background synchronization may be quiet for extended periods, so an overly short idle timeout causes periodic disconnects. Ordinary web connections can usually tolerate shorter cleanup. Before changing this value, check whether the issue consistently appears after the same idle interval. If connections also drop during active transfer, inspect the network path, server limits, and transport layer instead of changing only the policy.
uplinkOnly, downlinkOnly, and buffering
uplinkOnly and downlinkOnly control connection cleanup during half-closed states. After one direction ends, the core can continue waiting for the other direction to finish its remaining transfer. Waiting too briefly may truncate the tail of a response; waiting too long retains completed connections unnecessarily. These settings usually work best at reasonable defaults and should be changed only when logs and capture behavior clearly point to the half-close stage.
bufferSize affects buffering for each connection, and its meaning and unit may vary by core implementation. A larger buffer does not mean higher speed and can increase memory use with many concurrent connections. Before tuning performance, identify whether the bottleneck is CPU, networking, protocol handshake, or application read speed. Without measurements, the client or core defaults are usually more stable.
Enabling statistics
Statistics switches under system determine whether inbound and outbound traffic is counted, and a top-level stats object is usually required as well. User-level statistics also require statsUserUplink and statsUserDownlink to be enabled for the relevant level. Writing only stats: {} does not create every dimension automatically. Likewise, enabling policy switches without an interface that consumes the statistics will not make charts appear in the client UI.
Statistics add some state-management overhead, so enable them according to actual observability needs. When troubleshooting traffic splitting, outbound counters help show whether traffic reached the proxy, direct, or block exit; user-level counters matter only when investigating an individual user. A personal desktop setup usually needs outbound statistics alone. If the graphical client already displays traffic, follow its generated configuration and reading mechanism instead of creating multiple overlapping statistics paths.
After changing policy, test three types of connections: fast short-lived connections, sustained transfers, and connections that resume after a long idle period. Opening one web page cannot show whether idle cleanup is appropriate or validate half-close waiting. If the client regenerates the configuration whenever a node is saved, manual policy changes may be overwritten. Add supported options through the client’s custom configuration feature, or recheck the generated result after each subscription update.
Logs, statistics, and runtime status
Log levels and file output
log is the first place to look when troubleshooting a configuration. Common levels from most to least detailed include debug, info, warning, error, and none, though exact support depends on the current core. For normal operation, warning usually preserves important anomalies while reducing noise; when investigating routing or handshakes, temporarily raise it to info or debug. After reproducing the issue, restore the usual level so large logs do not consume disk space or hinder later searches.
access and error specify paths for access and error logs. With relative paths, the base directory depends on where the core starts; graphical clients, terminals, and system services may all use different working directories. If the configuration says logs were written but the files cannot be found, check the process working directory and file permissions first. The destination directory must already exist and be writable by the current user. Do not point log paths inside an installation package or temporary extraction directory.
{
"log": {
"access": "./logs/access.log",
"error": "./logs/error.log",
"loglevel": "warning",
"dnsLog": false
},
"stats": {}
}
How to read a connection record
Read logs by connecting the stages of one request in time order: inbound acceptance, target identification, routing match, outbound selection, server-address resolution, transport setup, protocol handshake, and close reason. A standalone “connection closed” message cannot tell you whether the node failed, the target closed the connection, or routing blocked it. Correlate the target address, inbound tag, and outbound tag from the same time window to identify the failing layer.
If the logs contain an inbound record but no outbound selection, check routing rules and configuration loading first. If an outbound was selected but server-address resolution failed, move to DNS. If remote TCP is established but the protocol handshake fails, verify the user, transport, and security parameters. If the handshake succeeds but a particular target fails, inspect target routing, address family, and application behavior. Working by stage avoids repeatedly changing unrelated modules.
Access logs versus error logs
Access logs best answer “which target was handled through which entry point,” while error logs answer “why did processing stop?” To verify routing, choose an easy-to-recognize test domain, clear old logs, send one request, and inspect only the new entries. Opening many pages at once creates background requests, update checks, and media connections, making it difficult to tell whether a rule matched correctly.
A domain and an IP in the logs may come from different stages. A sniffed domain is used for rule matching, while the IP returned by DNS is used to establish the connection; which one appears in the access log depends on the core and log location. Seeing an IP does not mean the domain rule was skipped, and seeing a domain does not mean no resolution occurred. Evaluate domainStrategy, sniffing settings, and routing logs together.
Statistics, APIs, and client interfaces
The statistics module records counters, while an API or client interface reads and displays them. When using the core API, you usually also need a dedicated inbound, service tag, and routing rule that sends API traffic to the appropriate outbound. This structure varies significantly between cores and client management layers. Before adding it manually, confirm that the client has not already created a tag with the same name. Duplicate API inbound ports prevent startup, while duplicate routing tags may send status queries through an ordinary proxy exit.
Graphical clients such as v2rayN usually provide entry points for runtime logs, core logs, and connection status. During troubleshooting, prioritize the actual startup configuration and error messages shown by the client, because interface settings may be converted before launch. Editing a standalone JSON while the client loads a different temporary file is a common source of confusion. Confirm the configuration source by checking the startup log for the configuration path, listen port, and outbound tags, then compare them field by field with the file you are editing.
Privacy and retention in logs
Access logs may contain domains, destination addresses, local account identifiers, and connection times. Before enabling a verbose level, confirm where logs are stored and who can use them. After troubleshooting, lower the level and remove debug files that are no longer needed. When sharing an error excerpt, keep only stages directly related to the issue and replace node addresses, user identifiers, paths, and account fields. Deleting one server-address line is not enough, because nearby handshake information may also reveal configuration details.
The goal of log analysis is a reproducible chain of cause and effect, not collecting the most data possible. Reproduce one issue at a time, record the differences before and after each change, and preserve clear timestamps and test targets. If you replace the node, change DNS, edit routing, and switch the system proxy simultaneously, even a successful result will not show which change mattered. A useful troubleshooting record includes the client, core family, inbound method, matched outbound, and final failure stage.
Configuration validation, migration, and troubleshooting
Pass three layers first
Configuration validation has three layers. First is JSON syntax: brackets, commas, quotes, and field types must be correct. Second is configuration semantics: the current core must recognize the protocol fields, referenced tags must exist, and ports must not conflict. Third is the runtime path: the inbound must receive traffic, routing must match, the outbound must establish, and the target must respond. None replaces another. Parseable JSON proves only that the text structure is valid, not that node parameters or routing are correct.
Some cores provide a configuration-test mode that reads the file and reports errors without running the service long term. The command varies by core and packaging, so follow the help output for the core bundled with the client. On desktop, the safer approach is to use the client’s check, core restart, or startup-log functions. Confirm that the command invokes the same core file the client actually uses; another core on the system path may support different fields.
v2ray run -test -config ./config.json
xray run -test -config ./config.json
If a command reports that its argument form is unsupported, run the program’s help command to see the local syntax instead of assuming the configuration is wrong. After validation succeeds, start the core and check the logs for the expected listen port. If the port does not appear, the runtime configuration, permissions, or port occupancy still needs attention. If it appears but no application traffic arrives, return to the system proxy or application proxy settings.
Binary-search troubleshooting by module
With a complex configuration, the fastest approach is not reading every field line by line but reducing it to the smallest working path. Keep one local SOCKS inbound, one proxy outbound with known-complete parameters, one direct outbound, and empty routing rules. Confirm the proxy exit first, then add private-address direct routing, followed by domain categories, DNS splitting, and blocking rules. Keep a rollback copy after every layer. When a failure appears, it is limited to the module just added.
If the node reports a successful connection but websites do not open, check in this order: application capture, inbound listening, node connection, DNS, routing, and system proxy. Client status only shows that a process started or a test completed; it does not prove that the current browser traffic entered this inbound. The clearest test is whether the access log contains the target request. If not, fix the traffic entry point first; if it does, analyze the outbound.
Configuration changes after subscription updates
A subscription update refreshes the node list and some node parameters, and the client may regenerate the runtime configuration according to the current routing mode. Manual edits to temporary files usually will not persist. Put persistent routing rules in the client’s custom rules, preset configuration, or supported template location. After an update, check the node protocol, transport, server name, outbound tags, and rule order, especially when importing nodes from different core families.
A single vmess:// or vless:// share link usually represents one node, while a subscription URL returns an updatable node list. Their import entry points and update behavior differ in clients. For the distinction, see the guide to share links and subscription URLs. If an update returns no nodes or an error, also see subscription update troubleshooting and automatic update settings.
Cross-core migration checklist
When migrating from a V2Fly configuration to Xray, or the other way around, first retain the basic structure supported by both: a SOCKS inbound, a standard direct exit, and simple domain and IP routing. Then verify user fields, transport, security, and flow-control settings protocol by protocol. If an unknown field appears, do not simply delete the entire security section and continue; it may be required for the node to work. Confirm whether the target core can express the same protocol capability. If it cannot, choose the client and core that match the node.
REALITY nodes generally require an Xray core path with the corresponding support; on Android, v2rayNG is often the first choice. For V2Fly configurations and nodes, choose v2flyNG as appropriate. On desktop, v2rayN is the usual choice, but confirm the active core in its core settings. For a broader view of the ecosystem and core relationships, see the Project V, V2Fly, Xray, and client ecosystem map and the Xray vs. V2Fly core selection guide.
| Troubleshooting stage | Typical symptom | What to check |
|---|---|---|
| Configuration loading | The core exits immediately, unknown fields, JSON errors | Syntax, field support, tag references |
| Inbound capture | The core runs but the access log shows no requests | Local port, system proxy, application settings |
| Outbound setup | Connection timeout or handshake failure | Node address, protocol, transport, security layer |
| Resolution and routing | Some domains fail or the exit differs from expectations | DNS, sniffing, rule order, address family |
| Target access | The proxy is established but a specific service fails | Target port, network type, application behavior |
Establishing a maintainable configuration baseline
A configuration that remains maintainable should use clear tags, a limited rule hierarchy, an explainable DNS path, and moderate logging. Record the purpose of each change, not just the field value; “send the LAN range direct” is easier to evaluate later than “add a geoip entry.” Group rules logically into blocks, special direct routes, special proxy routes, and fallback, ordering each group from specific to broad.
After making changes, verify at least a local address, an ordinary domain, a literal IP, a TCP request, and an application that requires UDP. Restart the client once to confirm that the configuration is not only active temporarily in the current process. If you rely on a subscription, run one update and check that custom rules remain. For the quick workflow, return to the v2rayN and v2rayNG configuration tutorial; to choose an installer again, visit the V2Ray client download page.