V2Ray JSON structure and configuration scope
Client settings vs. core configuration
A JSON config describes how the proxy core accepts connections, selects an outbound, resolves domain names and manages sessions. It is not a complete set of client settings. Window state, subscription groups, the system proxy toggle and update intervals saved by v2rayN usually belong to the client; when starting the core, the client uses this data to generate a runtime config. v2rayNG and v2flyNG each manage configuration in their own way, too. First confirm whether you are viewing a client data file, an exported custom config or the file actually used for the current run. Otherwise, even a correct edit may never take effect.
A core config typically has top-level log, inbounds, outbounds, routing, dns and policy fields. Inbounds and outbounds are arrays and can contain multiple objects; routing and policy are objects that contain nested rules or levels. The order of fields in the file does not determine execution order. Behavior depends on array order and references between objects. For example, routing rules are checked from top to bottom, and connections that match no rule usually use the first outbound in the array.
Build a readable minimal config
The complete configuration below is for a local direct-connection test. It checks whether the JSON loads, the loopback address can be bound and the machine can reach a destination directly. It creates only a SOCKS inbound and a direct outbound; it does not provide a remote proxy or configure the system proxy. An application must explicitly use this SOCKS endpoint to send traffic through the core. Before testing, stop any other instance using the same port so a port conflict is not mistaken for a configuration error.
{
"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": {}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": []
}
}
When reading a config, start by sketching the flow: an application connects through a tagged inbound, routing checks the destination and inbound tag, then sends the connection to an outbound. DNS comes into play when resolution is needed, while policy governs behavior such as handshakes and idle connections. This model is more useful than memorizing fields line by line because the same symptom can originate at different layers. For example, changing the outbound protocol cannot help if the application never reaches the inbound; changing DNS will not fix a misspelled outbound tag.
Syntax, merging and the scope of edits
This guide uses standard JSON throughout: keys and strings use double quotes, booleans are written as true or false, ports are numbers, and the final item in an array or object has no trailing comma. Some loaders allow comments or extensions, but that does not mean every client does. Avoid duplicate keys in particular: parsers may overwrite them, reject the file or behave differently in ways that are hard to spot. When combining two configs, merge them into a single top-level object rather than pasting two root objects back to back.
Before editing, save a working baseline and note which configuration entry point you changed. If a config is managed through a graphical interface, make changes on the relevant settings page where possible. Manually editing an auto-generated file can be undone after a restart, node switch or subscription update. To keep custom fields, first check the client's custom-config mode, override rules and routing import options instead of relying on a temporary file path. Data directories vary by operating system, so do not copy an absolute path from someone else's screenshot.
The core you use is part of the context. V2Fly and Xray differ in some protocols, transport names and extension fields; a shared JSON format does not make their configs interchangeable. Check the active core in the client, then verify that core's supported fields. If you encounter an unknown field or protocol error, narrow the issue down to the relevant object rather than replacing the entire config. See the glossary for terminology; use the tutorial for step-by-step instructions.
inbounds: inbound connections and local listeners
Listen address, port and protocol
inbounds defines where the core accepts connections. Each inbound needs a protocol and its related settings; SOCKS and HTTP are common local options. listen sets the listening address, port sets the port, and tag gives routing a name to reference. A tag is only an internal config identifier: it does not create a system proxy or make a browser connect automatically. Whether an application uses the proxy still depends on its own proxy options, operating system settings or another traffic capture method.
For local use only, set the listen address to 127.0.0.1 to restrict it to the IPv4 loopback interface. Listening on 0.0.0.0 accepts connections on all local IPv4 interfaces. Whether devices on your LAN can reach the listener also depends on the firewall and network, but the exposure is broader either way. Do not widen the listening scope just to troubleshoot. If LAN sharing is necessary, plan authentication, permitted sources and firewall rules, and be clear about who can use the proxy.
The port must match the settings in the applications that use it. SOCKS and HTTP are different protocols; similar port numbers do not make them interchangeable. The snippet below defines two separate inbounds for local testing with each protocol. The ports are examples, not universal client defaults. If your client uses a mixed-protocol inbound, follow its generated config rather than adding duplicate listeners.
{
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
},
{
"tag": "http-in",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
]
}
UDP and destination detection
The SOCKS udp setting allows that inbound to handle supported UDP requests; it is not a master switch that gives every application proxy support for UDP. The application must support the relevant connection method, and both the outbound protocol and remote server must be able to forward this traffic. The operating system's standard HTTP proxy setting does not capture UDP traffic from the whole device. If web browsing works but real-time communication does not, check application support, inbound handling and outbound capability separately instead of repeatedly toggling this boolean.
sniffing attempts to identify destination details from the initial data of some connections. A common setup uses destOverride to try to detect HTTP hostnames or TLS server names, which can help domain-based routing when the original destination is only an IP address. Detection is not available for every connection and cannot decrypt arbitrary encrypted traffic. Results depend on whether the protocol exposes identifiable information and which options the client actually enables. Enabling detection does not guarantee that every domain rule will match.
{
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls"
]
}
}
Put the object above inside the relevant inbound, not at the config root. Kernels differ in whether detected information rewrites the connection destination or is used only for routing. If you need to preserve the original IP destination, check whether the active core supports a route-only option. Detection is not a substitute for sound DNS configuration: when an application passes a domain through SOCKS, the core may already receive the domain and need no additional detection to match domain rules.
What to check when a listener fails
If startup reports that an address is already in use, first check for another client instance or an old core process still running. Then check whether the current config defines the same address and port more than once. After changing the listening port, update the browser's manual proxy settings, application proxy settings and any system proxy setting written by the client. Changing only the number in JSON leaves applications connecting to the old port, making it look as if the core is unresponsive. See Local Port Conflicts and Changing the Listening Port for troubleshooting steps.
If startup succeeds but no traffic appears in the logs, confirm that the application is connecting to the current inbound and check whether it has a proxy bypass list. Also distinguish the IPv4 loopback address from the IPv6 loopback address; listening on one does not imply support for the other. During troubleshooting, use a single inbound and a single test application so each request has a clear source. Once the inbound works, restore other listeners and more complex routing. This makes it easier to isolate the problem than changing several network layers at once.
outbounds: outbound connections and tag references
Protocols, outbounds and default behavior
outbounds describes how traffic leaves the core. Direct connections usually use freedom, blocked connections usually use blackhole, and a remote proxy is handled by an outbound for its protocol. protocol determines the structure of settings; changing only the protocol name while keeping another protocol's server fields will not work. Routing references an outbound's tag through outboundTag, so tags should be unique and readable, and references must be updated whenever a tag is renamed.
Traffic that matches no routing rule usually goes to the first outbound, so array order matters. Moving direct to the top changes where unmatched traffic goes; putting a block outbound first can make many connections fail immediately. Do not treat the outbound array as a list of candidates the core will try in order. Ordinary outbound objects do not provide automatic failover, latency testing or load balancing; those require separate mechanisms and explicit configuration.
{
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
}
The snippet above defines two outbounds but does not specify which requests should be blocked. Traffic reaches the block outbound only when a routing rule references block. The snippet can help demonstrate routing behavior, but it is not a remote service configuration. A remote proxy outbound must match the details provided by the server, including address, port, authentication, transport and security parameters. Get these details from an authorized source. Do not guess them from examples for other protocols or post them in public troubleshooting reports.
Node details and core capabilities
VMess, VLESS, Trojan and Shadowsocks use different authentication fields and security models. One benefit of importing a subscription is that the client converts its share format into a representation it supports, then generates the core config. A successful conversion does not guarantee every field is ready to use: some protocols and transports require a particular core, while some links may include extension parameters the client does not recognize. Before switching cores, check that the target protocol is supported, then review the generated config and startup logs.
The remote address is used to establish a connection; the TLS server name is used for identity verification. They may differ, but neither can be changed arbitrarily. The port does not determine the protocol: different services can use the same port number. A connection timeout is not necessarily an authentication error, and an authentication rejection may simply close the connection without displaying a clear password prompt. Troubleshoot in order: address reachability, transport setup, security handshake, then protocol authentication. This helps avoid attributing every failure to a broken node.
| Field | Purpose | What to check |
|---|---|---|
tag | Outbound tag | Unique and consistent with routing references |
protocol | Selects the outbound protocol | Supported by the active core |
settings | Protocol-specific parameters | Structure matches the protocol |
streamSettings | Transport and security parameters | Match the remote service |
DNS resolution for direct connections and outbound reuse
Direct connections may still need DNS. If routing selects direct and the destination is a domain, the connection still needs an IP address. Routing's domainStrategy and a direct outbound's own resolution options apply at different stages; similar names do not make them the same switch. To control address families or resolution sources, check the DNS module, routing and direct outbound settings separately, then use logs to confirm the actual connection address. Do not rely only on whether the destination page loads.
Connection reuse is not a universal speed boost suitable for every protocol and application. It changes how multiple logical connections share an underlying connection and can affect how failures appear. If some sites stop working, long-lived connections behave unexpectedly or transport compatibility becomes an issue, establish a baseline with the default reuse settings and compare one change at a time. Do not change reuse, transport, security verification and routing all at once; even if the issue disappears, you will not know which change helped.
Keep routing tags stable when switching nodes. A client may generate a consistent outbound tag for the currently selected node, so rules do not need to change with the node name; in manual mode, you must maintain references yourself. Before importing external rules, compare their outbound names with the ones in your config to avoid referencing a nonexistent tag. For a guide to node selection by latency, region and protocol, see How to Choose Nodes and Interpret Test Metrics. Whatever the test results, config references must still be correct.
streamSettings: transport and security parameters
Separate proxy protocol from transport
The proxy protocol defines how traffic is authenticated and forwarded; the transport defines how the connection is carried; the security layer defines how it is protected and verified. These are separate fields. VLESS is not REALITY, and WebSocket is not a standalone proxy authentication protocol. A proxy protocol can use different transports, while some security options are available only with certain cores and combinations. Separate these three layers when interpreting a config to determine which object each parameter in a share link belongs to.
streamSettings is typically part of an inbound or outbound that uses a specific transport. network selects the transport, security selects the connection security method, and their respective settings objects hold the parameters. This object cannot establish a remote connection on its own or compensate for missing authentication details. The partial example below uses the traditional TCP name with TLS. Use it only if the active core accepts that name and the server offers the corresponding transport; do not paste it across cores without checking.
{
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "example.org",
"allowInsecure": false
}
}
}
example.org in the example is a documentation domain, not a proxy server address. In practice, the server name must match the certificate identity and configuration requirements provided by the authorized service. If the core documentation uses a different transport name or settings structure, use that structure rather than swapping in a similar-looking string. Check compatibility against the config specification for the active core, not the translated label on a client button.
TLS server names and certificate verification
serverName is used for TLS service identity handling and is often related to the certificate name and server selection. The remote address can be an IP or a domain; even when connecting by IP, the expected handshake may require the correct domain as the server name. Conversely, entering a common domain at random does not make a certificate valid. A name mismatch, incomplete certificate chain, incorrect system time or interference from a network device can all cause the security handshake to fail.
allowInsecure should not be the default fix. Disabling identity verification may make a certificate error disappear, but it also changes the security boundary. First check the system date, server name, certificate validity period and trust chain. Then check for devices on the network that may be replacing certificates. If the service explicitly uses a private trust system, configure its trusted certificates according to the provider's process instead of skipping verification for every error. Track certificate issues during subscription updates separately from certificate errors during a node handshake.
WebSocket, paths and intermediary forwarding
With WebSocket, the path, request host and reverse proxy forwarding rules generally need to match. The TLS server name, HTTP Host field and underlying connection address serve different purposes. Even if they happen to be the same in one deployment, they are not interchangeable. Path case, a trailing slash or changes made by an intermediary proxy can send a request to a regular webpage instead of the intended service. The result may be an HTTP status error rather than a conventional proxy authentication failure.
When troubleshooting transport issues, first confirm the transport type published by the server, then check whether TLS, a request path and host information are required. Being able to open a website in a browser does not prove that the proxy transport works: ordinary web requests and a specific upgrade request may take different paths through the service. For an imported node, first check that the client interface saved all parameters, then review the generated config. This avoids confusion caused by editing both the interface and an auto-generated file.
Transport and protocol support also varies by core. v2rayNG and v2flyNG belong to different core ecosystems; a link parameter recognized by one client is not necessarily supported by the other. The core selected in desktop v2rayN must also match the node's capabilities. If a transport is unknown, a field is unrecognized or the connection drops immediately during the handshake, check compatibility before investigating the network. Do not cycle through multiple parameters at random.
When possible, test one transport combination at a time: keep the remote details fixed, leave connection reuse and policy at their defaults, verify that the basic handshake succeeds, then restore advanced settings one by one. Record whether the error occurs during config parsing, TCP setup, TLS or proxy data exchange. These notes point to the object that needs checking and help you search the Help Center by error type instead of repeating the entire installation process.
routing: rule order and matching boundaries
Match a rule first, then select an outbound
routing assigns connections that have already entered the core to an outbound. It does not route applications that are not using the proxy into the core, and it is not the operating system proxy toggle. Rules are generally checked from top to bottom in array order, and the first match determines where traffic goes. Put narrowly defined exceptions first and broad catch-all rules later. If a rule covers all TCP and UDP traffic, later rules for those protocols will usually never run.
Different conditions in a single rule generally narrow the match together, while multiple candidate values in an array for the same field usually mean any one of them can match. For example, specifying both an inbound tag and a domain means the rule applies when that domain is accessed through the specified inbound. If a set of IPs and a set of domains should each use the same outbound, separate rules are usually clearer. Do not put several dimensions in one rule and assume that any one condition is enough to match.
{
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": [
"full:example.org"
],
"outboundTag": "block"
},
{
"type": "field",
"ip": [
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"::1/128",
"fc00::/7",
"fe80::/10"
],
"outboundTag": "direct"
}
]
}
}
This snippet pairs with the direct and block outbound tags introduced earlier: exact domain matches are blocked, the listed local address ranges go direct, and everything else uses the default outbound. It demonstrates the rule mechanism, not a complete traffic classification scheme. In particular, with AsIs, a domain destination is not actively resolved just to match an IP rule. Adding private address ranges therefore does not guarantee that every domain resolving to a LAN address will match.
Domain patterns and resolution strategy
full: matches an exact domain, domain: generally matches the specified domain and its subdomains, and regexp: uses a regular expression. Plain string matching can cover a broader range than expected and is harder to review, so use explicit prefixes where possible. A regular expression in a JSON string must also be escaped for JSON; for example, backslashes in the expression need the correct escaping. A list that looks like a set of domains does not guarantee that every entry uses the same matching semantics.
Common routing domainStrategy values include AsIs, IPIfNonMatch and IPOnDemand. The first avoids actively resolving domains for routing matches; the latter two use resolution results to try IP rules under their respective conditions. Earlier resolution can affect query counts, rule matches and failure paths, but it is not automatically faster or more accurate. Choose based on whether your rules depend on IP classification and whether DNS has a working route.
| Field | What it matches | Common caveat |
|---|---|---|
domain | Destination domain | Destination detection may be needed if only an IP is available |
ip | Destination address or resolution result | Depends on the resolution strategy |
inboundTag | Inbound the connection arrived through | Must match an actual inbound tag |
port | Destination port | Not the local listening port |
Rule data and mode switching
When using geosite: or geoip: categories, check that the corresponding data files exist, contain what you expect and are updated by the client as intended. A category name does not mean the core can identify every website on its own; it refers to data in a resource file. If a resource is missing at load time, check that the rules and resource files are compatible. If it loads but matches unexpectedly, find out which category the destination actually matched. The same rule name in different external data packages does not guarantee identical contents.
Client modes such as “global” and “bypass” usually generate or switch between sets of routing rules, while the system proxy determines which application requests are sent to the local inbound. After switching modes, check whether manually added rules are retained and where they are placed in the active config. For a full explanation of both layers, see System Proxy vs. Routing Modes. Do not assume that every application follows the same path just because of the system tray icon.
To test a rule, choose a destination that clearly distinguishes the expected outcomes. Start with only the relevant exception and catch-all rules, then check the inbound and outbound tags in the logs. If the destination matches an unexpected rule, look first for a broader rule above it, prior resolution by the application or destination detection—not for more duplicate entries. Once the minimal rule works, restore categories one group at a time to find which group widened the match.
dns: resolution paths and address family selection
Distinguish application, system and core DNS
Start by determining which layer is making the DNS query. The application may resolve names itself, the operating system may resolve them on its behalf, or the core may resolve them during routing or connection setup. These paths can coexist. If an application resolves a domain to an IP before passing it to SOCKS, the core may see only the IP as the original destination. If the application passes the domain directly to SOCKS, the core retains the domain destination. This affects whether domain routing needs extra detection and which DNS settings are involved.
A top-level dns config does not take over every domain lookup on the device. It primarily configures resolution performed by the core; whether it handles DNS requests from applications depends on traffic capture and DNS forwarding design. A browser's encrypted DNS feature may use a separate path. During troubleshooting, note whether the application resolves names itself, which proxy protocol it uses and whether another DNS mechanism is enabled. Do not infer the source of every query from the system network settings alone.
{
"dns": {
"servers": [
"localhost"
],
"queryStrategy": "UseIP"
}
}
This partial snippet tells the core to use the system resolution path and allows queries for both address families. It is a useful baseline for understanding the limits, but it does not mean system resolution can reach the destination on every network. Here, localhost is a core-defined way of requesting system resolution; it does not mean a DNS service must be listening on the local loopback address. To specify a DNS server directly, use a server format supported by the active core and check how queries are sent.
Treat resolution and routing strategies separately
queryStrategy controls the address family used for core DNS queries. Common values are UseIP, UseIPv4 and UseIPv6. Restricting queries to IPv4 may avoid an unusable IPv6 path, but destinations with only IPv6 addresses will then have no usable result. IPv6 support depends on the local interface, default route, outbound and destination. Seeing an IPv6 address in system settings alone does not prove the whole path works.
Routing's domainStrategy determines when a domain is resolved for rule matching; the DNS query strategy determines which address family to request; and a direct outbound's related strategy may determine how addresses are used for the connection. These are separate stages. If changing one field restores access, that only shows that the path changed; it does not prove the other two settings are correct. Check each one in turn against a working baseline instead of setting every similarly named strategy to the same value.
When choosing different DNS servers by domain, also check each server object's domain scope, expected address filters and fallback behavior. Receiving an answer does not guarantee that the core will accept it: if it fails the expected-address conditions, the core may try another path. For complex DNS routing, start with one clearly reachable resolver, then add categories and fallback behavior gradually. Otherwise, a failed lookup could be caused by an unreachable server, a filter or a fallback restriction.
Bootstrap dependencies and DNS loops
If the remote server address is a domain, it must be resolved before a connection can be established. An encrypted DNS server that also uses a domain needs bootstrap resolution as well. If its query path depends on a proxy outbound that has not yet been established, a dependency loop can form. The usual symptom is repeated timeouts after startup with no actual remote handshake. Map out the remote address, DNS server address and their outbounds separately to ensure at least one reachable path does not depend on its own result.
Whether DNS queries pass through routing, use a local resolver or send encrypted queries through a proxy depends on the server format and core behavior; the top-level object's location alone does not tell you. If a particular server format has special local handling semantics, do not assume ordinary routing tags apply to it. Check the active core's documentation for the server type, then verify the actual request path in logs. A configured DNS address does not mean all queries use the specified outbound.
If a domain fails, first record whether it resolves and which address family the answer uses, then check whether the resulting connection address is reachable. Successful resolution does not guarantee a connection, and reaching an address does not prove the TLS server name is correct. If only one application is affected, compare its own DNS settings first. If every application fails in the same way, check the shared resolution path and outbound. For troubleshooting by symptom, see the Help Center; do not disable certificate verification as a substitute for DNS troubleshooting.
policy: session behavior and statistics
Policy levels are not routing priorities
policy manages session-level resource and timeout behavior. levels organizes policies by user level, while system manages options such as system-level statistics. A level is neither a rule priority nor a connection speed setting. A level's policy matters only when the relevant protocol user or connection actually uses that level. Clients may generate configs that use the default level; adding another level object manually does not switch every connection to it.
Level keys are written as strings in JSON, for example "0", while inner fields use numbers or booleans as specified. Policy does not create an outbound, fix certificates or make an unselected node join a connection automatically. Before adjusting values, confirm whether the issue is a handshake timeout, idle session cleanup or missing statistics. If the error occurs while loading JSON or binding a listener, policy has not entered the process, so changing these values cannot address the cause.
{
"policy": {
"levels": {
"0": {
"handshake": 4,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5,
"statsUserUplink": false,
"statsUserDownlink": false
}
},
"system": {
"statsInboundUplink": false,
"statsInboundDownlink": false,
"statsOutboundUplink": false,
"statsOutboundDownlink": false
}
}
}
The snippet above is an explicit policy example to show how the fields relate; it is not a fixed combination recommended for every network. Time values are in seconds, and their exact effect depends on the core implementation. If the current config has no specific problem, keeping the client's generated defaults is usually easier to maintain than copying a set of “optimized” values. Make each change against a reproducible failure and a clear observation goal; do not judge a policy by how one page load felt.
Handshakes, idle connections and one-way traffic
handshake limits the time available for the relevant handshake stage, but it is not a universal cap on every lower-level timeout. DNS resolution, transport setup and application wait behavior may have their own mechanisms. A setting that is too short can terminate slow connections prematurely; one that is too long can tie up resources while unusable connections linger. First identify the stage of the connection lifecycle where the error occurs, then determine whether this policy field actually controls it.
connIdle applies to connections with no data in either direction. A connection carrying a download and a message channel that has been silent for a long time are different cases. Some applications keep sessions alive with heartbeats, which may interact with core idle cleanup and session timeouts on intermediary devices. An unlimited idle timeout cannot prevent a router or remote server from closing a session, nor can it repair a lost network path.
uplinkOnly and downlinkOnly control how long a session waits when only one direction of traffic remains. A half-closed connection is normal in some protocols and does not mean the connection has failed. For applications with specific needs around uploads, streaming responses or long polling, test with the defaults first, then compare one change at a time against logs and connection behavior. Cutting the wait time arbitrarily may end a session before the remaining response has finished.
| Field | Stage | Checks this setting cannot replace |
|---|---|---|
handshake | Handshake for the relevant session | Authentication details and security parameters |
connIdle | Idle connection | Intermediary devices and application heartbeats |
uplinkOnly | One-way traffic state | Application behavior during half-close |
statsOutboundUplink | Outbound traffic statistics | Statistics API and client display |
Statistics prerequisites and limits
Enabling statistics flags does not guarantee numbers will appear in the client interface. The core may also need its statistics module enabled and a read API configured, and the client must actively query it. User-level statistics usually depend on protocol user identity and other required details; an anonymous local inbound does not automatically create distinct user records. If you only need to know whether a local connection used an outbound, access logs are often more direct. Do not expose an administrative listener just to troubleshoot.
Connection statistics, network interface traffic and service billing may use different measurement methods. Transport overhead, connection reuse, traffic from other processes and sampling times can all affect comparisons, so the numbers will not always match. Config-level statistics show traffic changes for the relevant objects; they are not a basis for inferring the provider's final billing. For node traffic multipliers, follow the service documentation rather than trying to change billing results through policy fields.
Policy tuning also needs to account for device resources. Desktop and Android systems differ in background restrictions, sleep behavior and network switching; a dropped long-lived connection may be caused by the operating system rather than the core's idle policy. If v2rayNG or v2flyNG disconnects in the background, first check system battery management and network permissions. If v2rayN behaves oddly after a computer wakes, compare the result of reconnecting. Adjust a timeout only after confirming that the core is actively closing sessions because of that policy.
Config validation, logs and layer-by-layer troubleshooting
Validate loading before testing access
Validate in order: JSON syntax, core structure, listening, traffic entry, routing, resolution, transport and application behavior. Valid syntax only means a parser can read the JSON; it does not mean the protocol fields are supported. A successful core startup only means the basic structure and resources passed loading; it does not prove the remote connection works. Record evidence of success at each layer to avoid leaps in logic such as “the file opens, so the config is fine” or “the browser failed, so the node is broken.”
In the client, save a working config or export a copy before creating a separate test config. Prefer the client's built-in config test and log viewer, and confirm that you are testing the selected core. If you run a custom config, check whether the client still appends routing rules, rewrites ports or merges other options. A syntax check cannot explain behavior if the test file differs from the runtime file, so confirm the loaded file's source for every test.
The commands below use only the Python standard library to check and format JSON. They do not validate V2Fly or Xray field semantics or start a connection. config.json refers to the local test file in the current directory. On Windows, use the second command if you have the Python launcher. If the relevant tool is not installed, use the client's test feature instead; there is no need to change your runtime environment just to follow this guide.
python3 -m json.tool config.json
py -m json.tool config.json
Temporarily increase log detail
loglevel controls log verbosity. Keep it modest for routine use and raise it temporarily when troubleshooting. The snippet below enables error logging at the informational level but does not set a file location; output is handled by the way the core is started. Check separately whether access logs are enabled, where they are written and whether the client can display them. A higher error log level does not guarantee a complete access log.
{
"log": {
"loglevel": "info"
}
}
When checking logs, look for the first error rather than just the final “connection failed” message. An unknown protocol points to core support or a protocol name; an address conflict points to an inbound listener; a missing resource points to rule data; a DNS timeout points to the resolution path; and a certificate name error points to security identity. Repeated retries can produce stacks of similar entries. Mark the start time of one test, make a single clear request and reduce other application traffic to avoid interference.
Logs may contain destination domains, addresses, subscription link fragments or other usage details. Before sharing them, remove authentication details, full subscription URLs and unrelated access records, leaving only the error type and necessary fields. Do not post an entire config containing user details in a public forum. After troubleshooting, restore the normal log level and check where local logs are stored and how they are rotated; avoid accumulating records that have no ongoing diagnostic value.
Minimal reproduction and rollback
If the inbound works but access fails, use the direct-connection baseline on this page to test the application-to-inbound path, then restore one remote outbound with complete, known-good details. Add a single routing rule next, then restore DNS categories and policy. Use the same test application and destination at each step, recording the result and relevant logs. This ties the failure to a specific change instead of forcing you to search a config full of nodes, resources and override rules for several possible causes at once.
For subscription update failures, test “downloading subscription data” separately from “using a subscription node to connect.” The former may depend on the link, the client's update path or certificate issues; the latter depends on node parameters and the core. A working node does not prove the subscription URL is still valid, and a successful update does not prove every node can connect. See Troubleshooting Subscription Update Failures for the relevant checks. Do not rewrite the entire routing object just because an update failed.
When moving between platforms, the config's logical design can often carry over, but file paths, listener conflicts, system proxy settings and background restrictions need to be checked again. For first-run issues on Windows and macOS, see First Install and Initial Setup. On Linux, distinguish desktop-session proxy settings from application-specific environment settings; on Android, check system traffic routing and background management. A successful migration means application requests enter and leave the core as expected, not just that the file copied without errors.
To wrap up, save the working config, restore the usual log level, confirm tag references and listening scope, and verify that changes persist after a restart. Then test browsing, downloads and applications that need long-lived connections; one webpage is not a stand-in for every use case. Return to the relevant section when a field is unclear. To restart with a step-by-step workflow, use the Getting Started guide. This guide explains config relationships; the tutorial walks through the process, and neither replaces the other.