JSON Structure Overview: Confirm the Object Hierarchy and Processing Chain First
What the top-level object does
A V2Ray configuration file is a JSON object. Common top-level fields include log, dns, inbounds, outbounds, routing, policy and stats. These fields are not executed sequentially in the order they appear in the file; when the core starts, each is parsed into its corresponding module. Placing routing before inbounds does not change the actual processing order. Traffic flow is determined by inbound tags, routing conditions, outbound tags and references between modules. When reading a configuration, find every tag first, then follow its references instead of simply reading from the first line to the last.
Both inbounds and outbounds are arrays because one core can listen on multiple local ports and prepare multiple exits at the same time. routing.rules is also an array, and rules are normally matched from top to bottom. By contrast, dns, log and policy are generally objects describing DNS behavior, logging behavior and session policies. Data types must be exact: use square brackets for arrays, braces for objects, and true or false for Boolean values—not quoted strings.
Building a minimal structure
The following skeleton contains one SOCKS inbound, one direct outbound and one basic routing rule. It is intended to illustrate the hierarchy, not to serve as a complete remote-server configuration. After a SOCKS request reaches the local listening port, the routing module checks the destination; a private-address match is sent to the direct outbound. Unmatched traffic also uses the available default outbound, so production configurations normally define a primary proxy exit explicitly and distinguish direct, proxy and blocked results with rules.
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
}
],
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "direct"
}
]
}
}
Separate JSON Syntax Errors from Semantic Errors
A syntax error occurs when the core cannot understand the file at all. Common causes include trailing commas, missing quotation marks, unbalanced brackets and comments in standard JSON. JSON does not accept // or /* */ comments. If a client interface allows comments, it is usually converting them before saving; that does not mean the file read directly by the core supports the same syntax. A semantic error means the JSON parses but the field combination is invalid—for example, a routing rule references a nonexistent outboundTag, a port is written as a string, or the protocol does not match the structure of settings.
Clients also add fields related to the runtime directory, resource locations and core-specific differences when generating a configuration. v2rayN is suited to managing desktop configurations on Windows, macOS and Linux, while v2rayNG and v2flyNG follow different core paths for Android. A subscription entry in a graphical client is not the same as the final runtime configuration: the client usually merges node parameters, local listening settings, routing rules and DNS options before handing the result to the core. When troubleshooting, inspect the configuration actually generated or exported by the client rather than checking only one node in the subscription link.
tag is the connection point between modules
Think of tag as a stable internal name in the configuration. Inbound tags are referenced by routing rules through inboundTag; outbound tags are referenced by outboundTag and balancerTag; DNS servers can also use tags to associate with a specific outbound. Tags are case-sensitive, so renaming one requires updating every reference. Use short, meaningful names such as socks-in, proxy, direct and block. Avoid using server addresses or frequently changing notes as tags.
When troubleshooting an entire configuration, draw a simple chain: which local port the application uses, which inbound tag owns that port, which domains, IPs, ports or protocols the routing rules match, which outbound tag is selected, and which protocol and transport the outbound uses. If every reference in the chain resolves to a real object, most structural problems can be found. The following chapters break down each module along this path.
inbounds: Define How Local Traffic Enters the Core
Listen addresses, ports and protocols
An inbound receives connections from browsers, system proxies, devices on the local network or other applications. An inbound object usually contains at least tag, listen, port, protocol and settings. listen determines which network interface is bound. For a desktop client used only locally, listening on 127.0.0.1 is preferable because other devices on the LAN cannot access the port directly. Only listen on all interfaces when sharing is genuinely required, and then check the system firewall, access controls and SOCKS authentication settings.
port is an integer and must not conflict with another program. v2rayN commonly generates local SOCKS and HTTP ports from its client settings, but a manually chosen port should never be assumed to be free. When the core reports that an address is already in use, first check whether another client instance is still running, then close the conflicting process or change the listening port. See Troubleshooting v2rayN startup failures caused by an occupied port for the relevant steps.
protocol determines the inbound protocol structure. Common local choices are socks and http. A SOCKS inbound suits applications that support SOCKS5 and can forward UDP when configured; an HTTP inbound suits applications using HTTP proxy settings. Transparent proxying and port forwarding involve the system network stack and additional permissions, so they should not be enabled casually in a basic configuration. First get an application with an explicitly configured proxy address working, then expand the scope gradually.
Providing both SOCKS and HTTP inbounds
{
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}
},
{
"tag": "http-in",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
]
}
The two inbounds must use different ports. For SOCKS5, enter 127.0.0.1:10808; for an HTTP proxy, enter 127.0.0.1:10809. If the operating system accepts only one proxy port, use the value generated for the client’s system-proxy mode rather than entering a SOCKS port in a field that accepts only HTTP proxies. A successful TCP connection to the port proves only that a local listener exists; it does not prove that the remote outbound, DNS or routing is correct.
The purpose and limits of traffic sniffing
When sniffing.enabled is enabled, the core can recover a destination domain from application-layer information in some connections. Common http and tls values in destOverride allow the HTTP Host or the server name from a TLS handshake to override the original destination. This helps domain-based routing: even if an application first resolves a domain to an IP, the routing module may still obtain the domain and match geosite or full-domain rules.
Sniffing does not decrypt web content, and it cannot recover a domain from every connection. Protocols without recognizable host information retain the original destination. Some applications are sensitive to destination changes, so enabling overrides may produce unexpected behavior. When troubleshooting one application, temporarily disable sniffing for comparison. If the application works afterward, inspect the domain rules, DNS results and the scope of destOverride instead of immediately blaming the outbound protocol.
UDP, authentication and LAN access
The SOCKS inbound field udp controls whether UDP forwarding requests are accepted. Enabling it does not mean every outbound transport automatically supports destination UDP, nor does it guarantee that an application sends UDP through SOCKS. Confirm the application behavior, outbound capabilities and routing rules together. DNS queries handled by the core’s built-in DNS follow a different path from UDP queries sent directly by an application and should be evaluated separately.
Once listening is expanded to the LAN, do not treat access control as if the port were still available only to the local machine. SOCKS can use auth and an account list for username-and-password authentication, but graphical support for these fields varies by client. A safer approach is to expose only the interfaces and firewall source addresses that are actually needed, and make sure a mobile device cannot expose the port on an untrusted network. For local-only use, keeping a loopback listener is simplest and reduces the risk of accidentally exposing a port.
Route traffic by inbound source
Multiple inbounds can support different proxy protocols and serve as separate traffic entry points. For example, create main-in for everyday applications and direct-in for applications that require direct access, then distinguish them with inboundTag in the routing rules. This is clearer than repeatedly changing a global mode, but applications must be able to use different proxy ports. Document each port’s purpose beside the configuration and ensure the client does not overwrite the tags when regenerating it.
Troubleshoot inbounds in four layers: confirm that the process successfully bound the port, that the application uses the correct proxy type, that the inbound accepts the expected TCP or UDP request, and that sniffing or routing has not changed the destination. Check the next layer only after the previous one works. If a browser says the proxy server is not responding, start with the listener and port. If the core already logs the connection but the destination is unreachable, move on to the outbound, routing and DNS sections.
outbounds: Define Proxy, Direct and Blocked Exits
The default outbound and tag references
An outbound object describes how the core sends a connection to the next hop or final destination. A common setup defines three logical exits: a primary proxy, a direct connection and a blocked exit. The proxy may use VLESS, VMess or Trojan, with parameters supplied by the server; direct connections usually use freedom; blocking usually uses blackhole. Give each a clear tag, then select it from routing rules with outboundTag.
When no routing rule matches, the core uses the default outbound. Details may vary between core implementations and configuration layouts, so critical traffic splits should not depend on an ambiguous array position. A more maintainable approach is to give normal traffic an explicit rule and send private addresses, domains that should connect directly and blocked targets to well-defined tags. When reviewing a client-generated configuration, also check whether it inserted an extra DNS outbound or loopback outbound.
VLESS client outbound structure
The example below shows the basic hierarchy of a VLESS outbound. The server address is an example domain, and the user ID is shown only to demonstrate the format; it cannot be used for a real connection. settings.vnext is an array of servers, with each server object containing an address, port and user list. The user object’s id must match the server configuration, and the common VLESS value for encryption is none. Transport, TLS and the server name belong in streamSettings, not in the user object.
{
"outbounds": [
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "edge.example.com",
"port": 443,
"users": [
{
"id": "11111111-1111-4111-8111-111111111111",
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "edge.example.com",
"allowInsecure": false
}
}
},
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
}
address is the server address the core actually connects to, while serverName is the name used during the TLS handshake. They may be identical or different depending on the deployment. Do not make them the same simply because it looks tidier. The port, transport type, security layer, server name and other parameters must match the server as a complete set. Any mismatch can appear as an immediate disconnect after connection, a failed TLS handshake or a long wait.
What freedom and blackhole actually do
freedom makes the core access the destination directly through the local network. It does not bypass the core: traffic still passes through the inbound and routing stages, but at the outbound stage it is no longer sent to a remote proxy server. Private addresses, local development services, LAN devices and sites that explicitly require a local exit commonly use this outbound. If the system itself cannot resolve or reach the destination, sending it to freedom will not fix the local network problem.
blackhole discards connections selected by a rule and can block specific domains, IPs or protocols. This is different from having no outbound match: the former deliberately chooses a blocking exit, while the latter usually indicates a configuration reference error. Use routing results in the logs to tell them apart. Keep blocking rules specific; an overly broad domain suffix or IP range can also affect legitimate subdomains and unrelated services sharing the same address.
Multiple servers and load-balancing strategies
Putting multiple servers into one outbound object does not automatically provide the switching behavior you expect. For multiple exits, create a separate tag for each one and manage them through a routing balancer or the selection mechanism provided by the client. This lets you test each server independently and see exactly which rule selected which exit. A graphical client may dynamically generate a proxy outbound for the currently selected node, so manually added parallel outbounds may be rebuilt the next time the client applies its configuration.
A subscription supplies node parameters and should not be confused with the runtime configuration. If nodes disappear or fields fail to parse after a subscription update, first check the subscription format and client compatibility; see the subscription failure and parsing troubleshooting checklist. If a node imports successfully but only one exit cannot connect, compare its address, port, user ID, transport and security settings instead of repeatedly updating the entire subscription.
A layered method for troubleshooting outbounds
First confirm that routing selected the intended outbound tag. Next verify that the server address resolves and that the local network can reach its port. Then check protocol authentication fields, and finally review streamSettings. If you replace the node, disable DNS, change routing mode and adjust TLS all at once, the result cannot show which change helped. Keeping a known-working direct outbound is also valuable: it helps identify whether the problem is core startup, route selection or one proxy exit.
streamSettings: Match Transport and Security as a Set
The difference between protocol, transport and security layers
An outbound’s protocol describes a proxy protocol such as VLESS, VMess or Trojan. streamSettings.network describes the transport carrying the connection, while streamSettings.security describes choices such as TLS, REALITY or no additional security layer. These layers solve different problems, but each must match the server. Knowing only that the connection uses VLESS is not enough; you also need the transport—TCP, WebSocket or gRPC—and its server name, path, service name and security parameters.
After importing a share link or subscription, the client converts these parameters into fields understood by the core. Core families do not completely agree on field names, accepted values or extensions. v2rayNG commonly uses the Xray core, while v2flyNG follows the v2fly core path; v2rayN manages desktop cores and node configurations. When migrating between clients, check the capabilities of the core actually used by the destination client instead of copying internal JSON and assuming every field will parse identically.
TCP and TLS example
For ordinary TCP transport, set network to tcp. With TLS enabled, set security to tls and provide tlsSettings. serverName participates in certificate-name validation and the handshake, so use the value supplied by the server. Setting allowInsecure to false enables normal certificate verification. Changing it to true only bypasses some checks; it cannot fix a wrong port, wrong protocol or stopped server, and should not be kept as a general troubleshooting solution.
{
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "edge.example.com",
"allowInsecure": false,
"alpn": ["h2", "http/1.1"]
}
}
}
alpn negotiates the application-layer protocol. Whether it is needed and the order of its values must match the server deployment. More fields do not make a configuration more complete; when the server does not require ALPN settings, automatic negotiation is usually preferable. Do not rotate through arbitrary ALPN values when a connection fails, because the handshake also depends on the port, server name, intermediary proxies and server certificate configuration.
WebSocket paths and request headers
WebSocket transport uses wsSettings, whose common field is path. The path must match the server exactly, including the leading slash and any query string. Some deployments also require a particular Host request header; the exact structure depends on core support. During migration, a common mistake is copying only the server address and port while omitting the path or hostname. The result may be a successful TLS connection followed by an ordinary web-page response or a closed connection.
{
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"serverName": "edge.example.com",
"allowInsecure": false
},
"wsSettings": {
"path": "/vless-connect",
"headers": {
"Host": "edge.example.com"
}
}
}
}
The address, TLS server name and HTTP Host may point to the same domain, or they may separately serve as the connection target, certificate identity and reverse-proxy routing key. Adjust them only when the server architecture is understood. Path case usually matters, and a trailing slash may produce a different routing result. If the client separates these fields into inputs such as “address,” “disguise domain” and “path,” confirm the mapping against the exported JSON.
gRPC and service names
gRPC transport generally uses grpcSettings, with serviceName as the key field. It is not an ordinary web path, so do not add slashes mechanically. Some deployments also use multiplexing-related options, but basic troubleshooting still starts with the service name, TLS server name and port. If the server uses gRPC but the client selects WebSocket, matching TLS certificates will not make the two transports compatible.
{
"streamSettings": {
"network": "grpc",
"security": "tls",
"tlsSettings": {
"serverName": "edge.example.com",
"allowInsecure": false
},
"grpcSettings": {
"serviceName": "vless-grpc"
}
}
}
How to verify a REALITY configuration
REALITY is a security-layer capability often paired with VLESS, but it is not synonymous with VLESS. Client parameters commonly include a server name, public key, short ID and fingerprint; use the fields supported by the target core. First confirm that the current core supports this security layer, then check the address and port, followed by the server name, public key, short ID and transport. Mixing TLS and REALITY settings into the same outbound usually indicates a problem during import or manual merging.
When troubleshooting the transport layer, avoid guessing values one by one. Compare against one reliable set of server parameters: confirm the protocol, network type and security type, then open the corresponding settings object. If logs mention certificate names, handshakes, service paths or protocol responses, follow that branch. If they show only a timeout, first rule out DNS resolution, an unreachable destination port and an incorrectly selected outbound.
Multiplexing and performance parameters
Some configurations support multiplexing on an outbound, allowing multiple logical connections to share one underlying connection. This may reduce repeated handshakes, but server support, long-lived connection characteristics and application traffic can produce the opposite effect. With no clear issue, start with the client defaults. If a single connection works but concurrent access fails or stalls after long periods, disable multiplexing for comparison while leaving other fields unchanged. Tune performance only after the connection works; do not use performance settings to hide a protocol or transport mismatch.
routing rules: Match in order and select an exit
Routing only selects an exit; it does not create connectivity
The routing module sends a connection to a specified outbound based on conditions such as the destination domain, destination IP, port, inbound source, network type or protocol. It does not change the outbound’s protocol parameters or make an unreachable server work. If one domain fails while others work, focus on routing and DNS. If all traffic fails through one proxy exit, first verify that outbound connectivity.
The rules array is normally checked from top to bottom, and the first match determines the result. Put specific rules before broad rules. For example, if one full domain should use the proxy while its entire suffix is set to direct, the full-domain rule must come first. For large rule sets, maintain clear groups such as blocking, private addresses, special proxy routes, special direct routes, regional categories and fallback rules.
How to write domain, ip and port conditions
Domain conditions can use exact domains, suffixes, keywords, regular expressions and geosite categories. Prefixes and meanings differ between match types, so do not interpret an ordinary string as an exact match. IP conditions can contain a single address, a CIDR range or a geoip category. Ports can be a single number or a range string. When one rule contains several condition types, those types generally must all match; multiple values within the same type are usually alternatives.
{
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": ["full:updates.example.com"],
"outboundTag": "proxy"
},
{
"type": "field",
"domain": ["domain:example.net", "geosite:cn"],
"outboundTag": "direct"
},
{
"type": "field",
"ip": ["geoip:private", "geoip:cn"],
"outboundTag": "direct"
},
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
}
}
full: is suited to matching one complete hostname, while domain: generally covers the domain and its subdomains. geosite: depends on the core finding the relevant geographic-category resource; geoip: likewise depends on IP data resources. Missing files, incorrect paths or a client update that fails to load resources can cause startup errors or unexpected matching. The homepage’s “Update geosite data” control represents a real maintenance task, but its frequency should follow the client’s update mechanism and rule requirements.
How domainStrategy affects domain and IP rules
domainStrategy determines whether routing resolves a domain in order to match IP rules. AsIs tends to preserve the requested domain and does not proactively resolve it for IP rules. IPIfNonMatch generally resolves the IP only when domain rules do not match, then tries IP rules. IPOnDemand triggers resolution related to IP rules earlier. Exact behavior also depends on the core implementation and DNS configuration.
Choose the strategy based on the rules that matter most. If the configuration mainly relies on geosite and explicit domain lists, avoiding unnecessary resolution reduces path complexity. If it relies heavily on geoip, domains must resolve to an IP that routing can evaluate. The source of the result also matters: system DNS, the core’s built-in DNS and remote resolution may return different addresses, changing IP-category matches. After adjusting domainStrategy, observe both DNS logs and routing results.
Split traffic by inbound, network and protocol
inboundTag can restrict a rule to traffic from selected entry points. For example, after creating a separate direct inbound, send all traffic from it to direct. network distinguishes TCP from UDP and is useful when an outbound does not handle one network type. Some cores can also identify application-layer protocols, provided sniffing obtains enough information. Protocol-based rules should supplement explicit domain or port conditions, not replace them.
{
"type": "field",
"inboundTag": ["direct-in"],
"network": "tcp,udp",
"outboundTag": "direct"
}
Checking rule conflicts and priority
The most common routing conflict occurs when every rule is valid but a broad rule matches first, leaving a later specific rule no chance to run. Start with the target connection and record its domain, resolved IP, port, network type and inbound tag, then evaluate the rules from the first one onward. Do not only search for the rule you expect; check whether an earlier rule can also match. See Custom routing rules and match priority explained for ways to organize domain, IP and geosite rules.
Another common issue is a rule that references the wrong tag. After the client switches nodes, it may regenerate the primary outbound tag, leaving a manual rule that points to an old tag ineffective or causing a startup error. Use the logical tags preserved by the client rather than node remarks. If the client offers a “bypass LAN addresses” option, confirm that it generates private-address direct rules at the right priority instead of adding another overlapping set manually.
Fallback rules and maintainability
A rule that specifies only the network type and sends traffic to the primary exit is often used as a fallback. Put it at the end of the array because it matches most connections. Keep blocking, private-address and special-routing rules at the front, then handle everything else at the end for predictable behavior. A configuration may work without an explicit fallback and rely entirely on the default outbound, but readers must then infer behavior from array position and core defaults, which is harder to maintain.
Large rule sets should not scatter every domain throughout the main configuration. Use a rule-set management method supported by the client when appropriate, but still verify that the generated JSON references the correct resources. If traffic behavior changes suddenly after updating rule data, compare category contents, resource-loading logs and rule order before changing the server node. Routing is a deterministic matching system; troubleshooting means finding the rule that actually matched.
DNS configuration: Control resolution sources, domain matching and result scope
The relationship between core DNS and system DNS
The dns module provides servers and matching rules for domains the core needs to resolve, but it does not guarantee that every DNS request from the operating system enters the core. Whether an application calls the system resolver directly, passes a domain through SOCKS or sends a separate DNS packet determines the actual path. Seeing dns.servers in the configuration does not prove that every browser lookup uses those servers. Distinguish between resolving a proxy server address for an outbound, resolving a target domain for routing and resolving a domain directly in the application.
The value of core DNS is coordination between resolution choices and routing rules. For example, a specific domain can use a designated DNS server and the result can then feed IP routing, or a domain group can be restricted to an expected IP range. A complete design must also consider which outbound carries the DNS query, which step uses the result and whether the domain is still retained when the application finally connects.
The servers array and conditional servers
servers can contain simple server addresses or objects with matching conditions. An object can use domains to specify the domains it applies to and expectIPs to constrain acceptable results. The example below sends a particular category to a resolver reachable from the local network and other domains to a different resolver. The addresses are illustrative; in a real deployment, choose DNS services that the current network and outbound path can actually reach.
{
"dns": {
"hosts": {
"router.internal.example": "192.168.1.1"
},
"servers": [
{
"address": "223.5.5.5",
"domains": ["geosite:cn"],
"expectIPs": ["geoip:cn"]
},
{
"address": "1.1.1.1",
"domains": ["geosite:geolocation-!cn"]
},
"localhost"
],
"queryStrategy": "UseIP"
}
}
How domains matches depends on the domain-rule system, and the required category resources must be available. expectIPs does not rewrite results into a particular subnet; it filters or checks whether the response is expected. If the condition is too narrow, a valid address outside the category may be rejected and the domain will appear to fail resolution. For troubleshooting, temporarily compare with a simple server configuration, then restore domain conditions and result constraints step by step.
Static mappings with hosts
hosts provides static mappings for specific names and suits fixed local services, test environments or records that must clearly be overridden. It is not a replacement for large-scale dynamic DNS. When the destination changes, a static value does not update automatically; an old mapping left in the configuration can keep sending a domain to the wrong address. For problems where only one domain always reaches an old server, check hosts, the system hosts file and the client’s custom DNS mappings.
A static mapping may still be passed to the routing module. Confirm whether routing evaluates the original domain or the resolved IP, and which outbound that IP matches. If a local domain maps to a private address, it usually also needs a private-address direct rule. Otherwise the connection may be sent incorrectly through the proxy outbound, making the LAN service unreachable.
queryStrategy and address-family selection
queryStrategy constrains which address types are queried. Common goals are to use all available addresses, request IPv4 only or request IPv6 only; the exact values depend on the core implementation. Base the choice on whether the current network truly has connectivity for that address family. A network may return an IPv6 address without providing a stable IPv6 path, causing delays or timeouts when the application tries it first. Conversely, forcing IPv4 excludes destinations available only over IPv6.
Do not diagnose address-family problems by repeatedly switching strategies alone. Compare DNS results, the system routing table and core connection logs to see which address type was actually attempted. If the proxy server is specified by domain, the address family used to resolve it also affects whether the core can establish the outbound. A target website resolving normally while the server domain fails indicates a problem at a different stage.
How DNS queries choose an outbound
A DNS server is itself a destination, so query traffic also needs a network exit. Some configurations can assign a tag to a DNS server or route it through a dedicated outbound, and clients may generate a dedicated DNS route. Avoid loops: DNS needed to resolve a proxy server address cannot depend on a proxy outbound that has not been established, or the first connection may fail during startup. A common design gives server-address resolution a working local path, then lets target-domain queries choose direct or proxy exits according to the rules.
When a DNS server is written as a domain instead of an IP, it must be resolved first, adding another dependency. Establish a clear, explainable path in the basic configuration before introducing encrypted DNS, conditional servers and complex outbound bindings. For split resolution of domains in mainland China and outside mainland China, including combinations of servers, domains and expectIPs, continue with the V2Ray DNS configuration guide.
Cache and troubleshooting order
DNS results may be cached by the application, operating system, client or core. After changing the configuration, an immediate repeat visit may still use an old result. Save the configuration, restart the relevant core, close and reopen the test application if needed, and then inspect fresh logs. There is no need to reset the entire system network state every time; first identify which layer cached the result. If the core logs no new query after restart, the application may not be delegating DNS resolution to it.
Use a fixed DNS troubleshooting order: verify that the server address is reachable, confirm that an unconditional rule returns a result, then add domains, and finally add expectIPs and outbound binding. Add only one variable at each step. If resolution succeeds but the connection still fails, move to routing and outbound checks; not every connection error is a DNS problem.
policy, stats and log: Session policies, statistics and diagnostics
policy controls connection-session behavior
policy sets user-level and system-level runtime policies. Common settings include handshake timeouts, idle connection timeouts, delays after closing one direction and whether user traffic statistics are enabled. It does not handle domain routing or change the transport protocol. Default policies are usually sufficient for desktop clients; adjust them only when addressing long-lived connection cleanup, server-side user levels or an explicit need for statistics.
User levels map from level in the protocol user object to policy.levels. If no level is specified, the default level is normally used. A level is a policy index, not a quality or permission score. In a manual configuration, a user with level: 1 will not receive the intended policy if only level 0 is defined. Client nodes generally do not need extra levels unless the server and client configuration explicitly use them.
{
"policy": {
"levels": {
"0": {
"handshake": 4,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5,
"statsUserUplink": false,
"statsUserDownlink": false
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true,
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"stats": {}
}
handshake limits how long the connection setup may wait. A value that is too short can terminate a valid connection during network fluctuations; a value that is too long keeps an unresponsive connection consuming resources. connIdle handles idle connections, which can affect applications that keep a connection open without sending data. uplinkOnly and downlinkOnly control retention time after a half-close. Without a clear reason, do not push these values too low just to release connections faster.
The stats object and statistics switches
Writing an empty stats object alone does not necessarily produce every statistic. The relevant direction-specific switches must be enabled in policy.system or the user-level policy, then read by the client or API. Inbound and outbound statistics track total traffic for the corresponding tags, while user statistics relate to protocol users and level policies. Statistics add state-management overhead; if the client does not read them, they can remain disabled.
Traffic figures shown by a graphical client may come from the core statistics API or from the client’s own connection totals. Seeing numbers in the interface does not mean every stats field is enabled. Conversely, enabling statistics without reading the API may produce no visible result. Troubleshooting should first identify where the data is generated and then how it is read, rather than treating a display problem as a forwarding failure.
Access logs, error logs and log levels
Common log fields include access-log location, error-log location and loglevel. Adjust the log level as needed for troubleshooting. Warning-level logging is usually enough for daily use and reduces output; temporarily increase detail for configuration references, DNS selection or handshake problems. After reproducing and recording the issue, restore a level suitable for long-term operation so logs do not grow indefinitely with irrelevant information.
{
"log": {
"access": "access.log",
"error": "error.log",
"loglevel": "warning",
"dnsLog": false
}
}
Relative log paths are usually relative to the core’s working directory, not the directory containing the configuration file. When a client such as v2rayN starts the core, the client may choose the working directory, so confirm the actual runtime directory before looking for files manually. If the directory is not writable, the core may fail to create logs or even fail to start. To avoid path differences, use the client’s log viewer when possible; for custom paths, choose a clearly writable directory on the current system.
Access logs record connection targets and results; error logs record resolution, handshakes, resource loading and module failures. Logs may contain destination domains, addresses and local connection details. Remove unrelated personal configuration data before sharing troubleshooting output. User IDs, server parameters and subscription contents from a complete configuration should not be pasted into public spaces; usually the error lines, relevant module and a simplified field structure are enough.
DNS logs and routing diagnostics
Some cores support observing DNS queries through dnsLog or more detailed logging. Whether this option exists and exactly how it behaves depends on the current core and the configuration generated by the client. After enabling it, focus on the queried domain, selected server, returned address and failure reason. Routing diagnostics focus on the inbound tag, destination, matched rule and final outbound. Together, these records explain why a domain resolved to a particular address and was then sent to a specific exit by an IP rule.
The first error in the log is usually more valuable than later cascading errors. For example, after a resource file fails to load, many geosite rules may fail; after a port-binding failure, the application may show a series of proxy-unavailable messages. Read the startup sequence in chronological order and find the earliest core error, then decide whether later entries are merely consequences. If the core starts successfully and fails only for a particular destination, trace that individual connection instead.
The boundary of APIs and management interfaces
Some configurations use an API inbound to provide statistics, logs or runtime controls to the client. It is usually generated automatically by a graphical client and bound to a local address. Manually changing its inbound tag, service list or routing rules can prevent the client from reading core status. An API port is not a normal SOCKS or HTTP proxy port and should not be exposed casually to the LAN. If the client starts the core normally but the status bar does not update, compare the API inbound, the route to the API outbound and the port expected by the client.
policy, stats, log and the API belong to the runtime-management layer. They help observe connections but cannot replace checks of the inbound, routing, DNS and outbound chain. First make sure traffic passes correctly with a minimal configuration, then enable statistics and management features one by one. This makes it possible to distinguish a forwarding failure from a client that cannot display status.
Combining configurations and troubleshooting: From syntax checks to tracing one connection
Build a verifiable end-to-end path first
When combining configurations, do not begin with a file containing many rules, multiple exits and complex DNS. Start with one local SOCKS inbound, one proxy outbound with a complete known-good parameter set, one direct outbound and a few explicit routes. Confirm that the core starts, the application connects and both exits work independently before adding domain categories, conditional DNS, blocking rules and statistics. Test after each layer; when a problem appears, the latest change is the first place to look.
In a graphical client, configuration usually comes from three layers: subscriptions or manual nodes provide remote parameters, client settings provide local ports and system-proxy behavior, and routing and DNS templates provide traffic-splitting logic. The runtime file is the merged result. If the node details are correct but runtime fails, export or inspect the actual configuration and confirm that the client did not overwrite the server name, transport or outbound tag. After upgrading or switching cores, check whether legacy fields are still accepted.
Run syntax and configuration tests
The core usually provides a command that tests a configuration without running it persistently. The command name and arguments vary by core; common forms are shown below. Use the same core binary and configuration path that the client actually invokes. If the graphical client offers a configuration checker or log view, prefer it because it supplies the correct working directory and resource paths.
v2ray test -c config.json
xray run -test -config config.json
A successful test means that the JSON parses and the basic modules can be initialized. It does not prove that the remote server is reachable or that every route behaves as expected. When a test fails, start with the field path and earliest error in the output. If geographic data cannot be loaded, check resource files and the working directory; if a tag is missing, inspect routing references; if an address is already in use, inspect the inbound port; if a field is unknown, confirm that the current core family supports the configuration.
A combined example for local structure validation
The example below contains no remote proxy credentials. Instead, it uses direct and blocking exits to show the relationships between the main modules. It can validate the local inbound, DNS, routing and logging hierarchy. When adding a proxy outbound, insert the server’s protocol and transport fields as one complete object, then change the fallback rule’s outboundTag to the corresponding proxy tag.
{
"log": {
"loglevel": "warning"
},
"dns": {
"servers": ["localhost"]
},
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
],
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": ["full:block.example"],
"outboundTag": "block"
},
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "direct"
},
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "direct"
}
]
}
}
Choose a troubleshooting entry point by symptom
| Symptom | Check first | Next step |
|---|---|---|
| Core will not start | JSON syntax, unknown fields, resource paths and port conflicts | Read the first error during startup |
| Application cannot connect to the local proxy | Listen address, port, proxy type and process status | Confirm that the inbound logs a connection |
| All proxy destinations time out | Routing exit, server resolution, destination port and transport | Test the direct and proxy outbounds separately |
| Only some domains fail | DNS conditions, rule order, sniffing and static mappings | Record the resolution result and matched rule |
| LAN addresses are unreachable | geoip:private, LAN bypass and inbound listening scope | Verify that the private rule comes before the fallback |
| Client status is not displayed | API inbound, statistics switches and management routing | Distinguish a forwarding failure from a display failure |
Symptom-based categories reduce unrelated changes. When the core will not start, remote reachability is irrelevant. When an application cannot reach the local port, check the inbound before replacing a remote node. When only some domains fail, focus on how those domains differ from working ones in DNS, routing and sniffing. Every test should include a control case, such as a working domain through the same exit, the same domain through direct, or the same node with complex routing disabled.
A fixed checklist after configuration changes
After saving, run JSON and configuration tests to confirm there are no syntax or module-initialization errors. Then check every tag reference: each outboundTag exists, each inboundTag resolves to an inbound, and tags used by balancing or the API were not renamed. Confirm that local ports are free and that the client’s proxy type matches the inbound protocol. Once the core starts, test direct, proxy and private-address rules separately, then test UDP or special applications.
DNS changes require recording the query server and returned address; routing changes require recording the rule that actually matched; transport changes require comparison with server parameters; policy changes require observing long-lived connections rather than loading one page once. Recording results by module is more reliable than deciding that something “seems faster.” If the issue occurs only in one application, also check whether it resolves domains itself, supports SOCKS UDP, ignores the system proxy or keeps old connections open.
Keeping client-generated and manual configurations together
v2rayN is the preferred management client for desktop platforms and is suited to maintaining subscriptions, nodes, routing and DNS through its interface. v2rayNG and v2flyNG are for Android and follow different core paths. To install or choose another client, visit the installer page. When a client generates the runtime configuration, make changes through its custom-configuration, routing or DNS settings whenever possible; direct edits to temporary generated files are usually lost after restarting or switching nodes.
If you need to maintain JSON manually, store stable configuration separately from the client’s temporary files and make it clear which process starts the core. Do not let two clients listen on the same port, and do not leave the system proxy pointing to a stopped instance. A subscription update changes the node source but does not prove that custom routing and DNS still fit; review the merged result afterward, especially outbound tags and transport fields.
Create reproducible troubleshooting records
A useful troubleshooting record includes the time, client and core type, relevant inbound tag, destination domain or address, selected outbound, first error message and the fields changed in this attempt. There is no need to copy a complete configuration containing sensitive connection parameters. Reducing the issue to a minimal structure usually makes it easier to distinguish syntax, core compatibility, network reachability and rule logic.
After completing the basic configuration, return to the guide to review the client workflow. For subscription failures, routing priority, split DNS or port conflicts, continue by issue type in the article index. The goal of a systematic configuration is not to pile up fields, but to give every entry point, matching condition and exit a clear purpose that can be explained through logs and controlled tests.