CONFIGURATION REFERENCE
V2Ray JSON Configuration Guide
Configuration structure, field boundaries, and step-by-step validation.
This page is for users who need to read, edit, or troubleshoot configuration files. For your first connection, follow the Getting Started guide to import a subscription and verify the connection, then use this page to look up specific fields. v2rayN is the recommended desktop client. On Android, use v2rayNG or choose v2flyNG if you need its core. Find installation links on the client downloads page.
Note: Examples use the traditional JSON configuration format. Whether a field is supported depends on the V2Fly or Xray core you run and its documentation. Snippets illustrate individual sections; do not concatenate objects from different sections as-is.
1. JSON structure and configuration scope
First, distinguish the client from the core
v2rayN, v2rayNG, and v2flyNG are graphical clients for managing configurations. The core handles incoming connections, matches rules, and forwards traffic. Client features such as subscriptions, profile lists, system proxy settings, and routing presets do not necessarily map one-to-one to objects in a configuration file. Before reading the JSON, check the client’s runtime information or startup logs to identify the core, then determine whether the current profile uses a generated configuration or a custom file.
A traditional configuration typically has a top-level object containing sections such as log, inbounds, outbounds, routing, dns, and policy. Inbounds and outbounds are arrays because a process can expose multiple local entry points and choose among multiple forwarding routes. Keep the square brackets even when an array contains just one item; do not replace them with braces.
Start with a minimal baseline
This baseline routes traffic from a local SOCKS inbound to a direct outbound. It can help verify that the core starts, listens locally, and accepts traffic from an app, but it does not connect to a remote proxy. For testing, configure an app to use the local SOCKS port and choose a destination that allows direct access. Once the baseline works, add a remote outbound, routing, and DNS to isolate the effects of each new field.
{
"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": {}
}
]
}
JSON strings and keys must use double quotes. Write Boolean values as true or false, and enter port numbers as numbers, not text with units. Standard JSON does not support comments or trailing commas. Some core parsers may accept extensions, but clients, subscription converters, and other tools may enforce strict JSON. Keep manually edited files in the standard format.
Duplicate keys in a top-level object are another easy-to-miss problem. Some parsers keep the last value; others reject the file. Neither behavior is a reliable way to combine settings. For example, adding two routing objects does not merge their rules. To add a rule, edit the same rules array and place the new rule deliberately.
Where you edit determines whether changes persist
Clients typically rebuild generated configurations from the selected profile, routing preset, and current settings. Direct edits to a temporary file in the runtime directory may last only until the next start. Switching profiles, updating a subscription, or restarting the service can overwrite the file. For lasting changes, use a custom configuration entry supported by the client, or save a separate configuration and load it through the client’s custom-config feature.
Custom configurations have their own limits. Local ports, routing modes, and DNS options shown in the client UI may not override fields in a custom file. Conversely, some UI features may still generate runtime parameters around that file. The settings page is not definitive; check the configuration the core actually loads and the resulting logs. When switching configuration modes for the first time, stop the existing connection, note the current setup, then restart and verify.
| Object | Primary purpose | Does not |
|---|---|---|
inbounds | Accept connections from apps | Set browser proxy settings automatically |
outbounds | Define the outbound and remote protocol | Determine the order of all routing rules |
routing | Choose an outbound for a connection | Change system DNS settings |
dns | Provide DNS settings for the core | Take over every app's DNS queries automatically |
Shared field names across cores do not mean complete configurations are interchangeable. Extended protocols, transport options, DNS settings, and policy defaults can differ. When migrating, keep the basics—such as inbound, direct, and block—then add features one at a time, checking that the target core supports each one. For parse errors, start with the reported field path; do not change the server or subscription before the configuration can start.
2. inbounds: Local listening and app connections
Check the listen address and port before the protocol
An inbound defines where the core accepts connections. For a single device, check listen and port first: the former sets the local address to bind to, and the latter is the port an app must connect to. A valid configuration does not guarantee that the port can be bound. Startup can still fail if another client, an old core process, or another service is using the same address and port. The logs usually report a listen error.
127.0.0.1 is accessible only from the local device and is a good default. Changing the listen address to 0.0.0.0 binds to all IPv4 interfaces, but access from other devices still depends on the firewall and network. Widening the listen scope is not a standard fix for local connection problems. If you need LAN access, consider authentication, allowed sources, and whether the network is trusted.
SOCKS and HTTP are different inbound protocols
Use a SOCKS inbound for apps where you can explicitly configure a SOCKS proxy; use an HTTP inbound for software that expects HTTP proxy settings. The protocol selected in the app must match the inbound port. Pointing an HTTP proxy setting at a SOCKS port may cause the connection to drop immediately rather than show a clear protocol mismatch. A reachable port alone does not confirm that the app is configured correctly.
{
"inbounds": [
{
"tag": "local-socks",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
},
{
"tag": "local-http",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
]
}
The example provides two separate inbounds. The ports are choices for this example, not fixed ports used by every client. In practice, check the client's current local listening settings before configuring the app. Browser extensions, system proxy settings, and terminal environment variables may each point to a different port. When troubleshooting, note which setting the app actually uses instead of changing an unused inbound.
For SOCKS, udp enables the relevant UDP support; it does not automatically route every app’s UDP traffic through the core. The app must support and use SOCKS UDP, and the outbound and network path must carry it. Enabling this field alone usually cannot solve cases that require system-level traffic capture. Android VPN-based routing and manually configuring a local proxy in a desktop app are different inbound mechanisms.
What tag and sniffing do
tag is the inbound’s logical name and can be referenced by inboundTag in routing rules. It is not a listening port, and changing it will not change how connections are accepted. Use a descriptive, unique name. If you rename local-socks, update every rule that filters on that name; otherwise, those rules will silently stop matching.
{
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}
}
The object above is part of a single inbound. Sniffing attempts to extract a destination domain from supported initial traffic, such as an HTTP host header or the server name in a TLS handshake, to help with routing. It does not decrypt HTTPS and cannot recover a domain for every connection. Sniffing may not produce the expected result if the traffic lacks the relevant indicator, the app uses a different handshake, or not enough data has arrived.
Sniffing options vary between cores, and some can affect whether the destination address is replaced. Do not enable every option at once just to make a domain rule work. First check whether the app already sends the domain to the proxy. If it does, the issue is more likely to be rule order or outbound selection. For routing-only sniffing, check that the current core supports the relevant setting.
| Symptom | Check first |
|---|---|
| Core exits immediately after startup | Port conflict, listen address availability, and field format |
| Core is running, but the app has no connection records | App proxy source, and whether the protocol matches the port |
| Web pages work, but some apps fail | Whether the app uses the system proxy and whether it uses UDP |
To test an inbound, use one app and one entry point, and temporarily disable the app’s other proxy plugins. If the logs show no connection, do not change the remote outbound yet. Traffic has not reached the core, so even correct server settings cannot help. For a more detailed browser and terminal check, see Troubleshooting a System Proxy That Isn't Working.
3. outbounds: Outbound tags and remote protocols
An outbound is an action, not a rule
An outbound defines how a connection leaves the core. Common types include freedom for a direct connection to the destination, blackhole to reject a connection, and protocol outbounds that use a remote server. Routing selects an outbound; the outbound handles forwarding. One cannot replace the other. Creating an outbound named proxy does not automatically send every connection through it. The default outbound and routing rules determine what happens.
In traditional configurations, connections that do not match a routing rule usually use the first item in the outbound array. Array order therefore matters; it is not just for display. If you move direct to the first position, for example, the default behavior changes for connections that previously fell back to the first proxy outbound. Document the default outbound and test an unmatched destination after adding each rule.
{
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
}
This snippet contains only direct and block outbounds, making it useful for understanding outbound selection; it does not include a remote service. Use a block outbound to explicitly reject selected connections, but do not assume that every failed request is a remote network problem. If a routing rule sends the destination to block, changing servers will not help. Check the selected tag first, then review dialing or handshake details for that outbound.
Check remote fields as a set
The remote outbound’s protocol, address, port, identity fields, transport, and security layer are interdependent. A share link may carry most of these settings, but imports can still be affected by format differences. If the connection fails after import, compare the client’s edit screen with the original details, paying particular attention to the transport path, server name, and flow-control options—not just the address and port.
This is a structural example of a traditional VLESS outbound. The domain example.com and all-zero identifier only illustrate where fields go; they do not point to a working service. Import real remote settings from a trusted service configuration. Do not use the example identity or server name for a real connection, or copy user fields from another protocol into this object.
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "example.com",
"port": 443,
"users": [
{
"id": "00000000-0000-0000-0000-000000000000",
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "example.com"
}
}
}
The meaning of encryption depends on the protocol. In this VLESS example, none is the value for that field; it does not mean that disabling outer TLS provides the same transport protection. VMess, Trojan, and VLESS use different authentication methods and field structures. Changing only protocol does not convert one to another. Choose settings that match the remote service first, then confirm that the local core supports them.
Tag references and client updates
The outbound tag must match the routing rule’s outboundTag exactly, including capitalization. Renaming a profile in the client’s list to “Proxy” may not change the core’s outbound tag; the client may generate a fixed tag. Base custom routing rules on the final runtime configuration. A reference to a missing tag can cause a configuration error or failed forwarding, so do not rely on the core to guess the intended outbound.
Subscription updates refresh node settings and can overwrite manually edited remote addresses, user identifiers, or transport fields. To keep an independent profile, first check how the client handles copied configurations and subscription ownership, then save a separate copy. Share links and subscription URLs update differently. See Single-node share links vs. subscription URLs to decide which layer to edit.
If the basic connection works but one destination still fails, leave the remote settings unchanged and compare direct and proxy routing. Revisit outbound fields first only when logs show a dialing, authentication, or security-handshake failure. Record whether all destinations fail or just one; this avoids rebuilding a working node configuration to fix a routing issue affecting a single destination.
4. streamSettings: Transport and security layers
Read protocol, transport, and security separately
Separate node settings into three layers: the proxy protocol defines how the peers authenticate and specify a destination; the transport determines how data is carried; and the security layer handles the handshake and encryption. protocol, streamSettings.network, and streamSettings.security belong to these different layers. They must work together, but a remote port of 443 does not prove that the transport is WebSocket or the security layer is TLS.
Transport settings must match the remote endpoint. If a service uses TCP, changing network to ws in the client does not add WebSocket support. The server-side requirements include whether a reverse proxy forwards the relevant requests, whether the path matches, and whether the upstream service is listening. A client can use an existing transport, but it cannot create a new remote entry point by changing a local field.
TLS server names and server addresses
address is the dial target; serverName is the name sent in the TLS handshake. They can differ in some deployments. Certificate validation generally requires a matching, valid server name. If the address is reachable but the TLS handshake fails, check the system clock, server name, and certificate status before considering disabling verification. Skipping verification changes the trust boundary and can hide a configuration error that should be fixed.
{
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"serverName": "example.com"
},
"wsSettings": {
"path": "/tunnel",
"headers": {
"Host": "example.com"
}
}
}
}
This is an instructional transport snippet; its path and domain do not identify a real service. The path must match the server-side route exactly, including the leading slash and capitalization. HTTP Host and the TLS server name apply at different stages: the former is used for HTTP request routing, while the latter is used in the security handshake. Some deployments require them to match, but changing one does not automatically update the other.
Paths may also include query parameters. When copying a configuration as text, preserve the entire original value. A share link may URL-encode part of it, while the client’s edit screen shows the decoded form. Compare the meaning and final runtime fields; do not confuse percent-encoded sequences with decoded characters or encode them a second time, which could make the server receive a different path.
| Stage | Typical fields | Common checks |
|---|---|---|
| Dial destination | address、port | Address resolution and network reachability |
| Security handshake | security、serverName | Name match, system time, and server configuration |
| Transport connection | network、path | Transport type and reverse-proxy path |
| Protocol authentication | User identity and protocol-specific fields | Identity details, protocol, and core support |
Keep REALITY and flow control distinct
REALITY is a security-handshake method in the Xray ecosystem, not a proxy protocol name that can be swapped in arbitrarily. A common setup still uses VLESS as the protocol, with the relevant settings in the transport security layer. The server name, public parameters, and short ID must match values allowed by the server. Field names can also change as cores evolve, so use the syntax documented for the core you run.
XTLS Vision flow control is not the same field as REALITY, and it cannot be paired freely with every transport. Whether to set flow depends on the server-side protocol combination and the client’s actual core. For authentication or flow-control errors, restore the complete settings provided by the service, then confirm that the combination is supported. Avoid adding isolated fields from one guide to a different transport configuration.
For an explanation of how they differ, see How REALITY and XTLS Vision Work. When troubleshooting, follow the failed stage shown in the logs: check the address for a dialing failure, security settings for a handshake failure, and transport settings for an endpoint mismatch. Isolating the stage is more reliable than changing transport, port, and server name all at once.
5. routing: Match order and routing boundaries
Set the default outbound before adding exceptions
Routing rules are usually checked in array order, from first to last. The first matching rule determines where the connection goes. Rules are not automatically ranked by specificity, and multiple matches are not scored together. Put exceptions for individual domains before broader category rules. Before writing split-routing rules, state the fallback behavior in one sentence, then list exceptions in priority order to avoid repeatedly rearranging rules.
Within a rule, different condition fields usually all have to match. Multiple entries in a domain or IP list, however, typically match any one of them. For example, setting both inboundTag and domain means the connection must come from the specified inbound and match the domain; it does not mean either condition can match on its own. This explains why conditions may match individually but fail when combined.
{
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": ["full:example.com"],
"outboundTag": "block"
},
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "direct"
},
{
"type": "field",
"domain": ["geosite:cn"],
"outboundTag": "direct"
},
{
"type": "field",
"ip": ["geoip:cn"],
"outboundTag": "direct"
}
]
}
}
This snippet shows routing rules only. The full configuration must define direct and block outbounds and include readable domain and IP category data. To send unmatched connections through a proxy, you must also set the actual remote proxy as the default outbound; this snippet does not create one. The first rule sends a documentation-only example domain to the block outbound to show how a precise exception overrides a broader policy below it.
Domain formats and category data
Use full: for an exact domain match, domain: for a domain and its subdomains, and regexp: for a regular expression. A bare string may be interpreted as a keyword, so do not assume it means an exact domain. For a specific site, prefer a prefix that clearly expresses the match you want, then verify it against connection records. Keep regular expressions simple and maintainable.
geosite and geoip refer to different data types: the former provides domain categories, while the latter provides IP ranges. They do not let the core determine a destination’s location in real time. Missing data files, unknown category names, incorrect paths, or outdated resources can all affect loading and matches. Updating a client’s node subscription does not necessarily update these resources; check the relevant resource-management settings separately.
domainStrategy controls when to resolve domains
AsIs typically matches the destination in its current form without resolving the domain to an IP for routing. IPIfNonMatch attempts resolution when a domain does not match an applicable rule, then checks IP rules. IPOnDemand triggers resolution when matching requires an IP condition. Implementation details vary by core and should be checked in its documentation, but all three options affect routing—not every domain lookup on the system.
Resolution can add a query step and make some connections depend on the result. A broad IP rule placed early may change behavior that you expected to be based on domain categories. Troubleshoot in this order: check whether the core received a domain or an IP, review rule order, then check the resolution strategy. Changing only domainStrategy without checking the inbound details often fails to explain why the same site uses different outbounds in different apps.
| Misconception | What actually applies |
|---|---|
| Domain rules recognize every request made to an IP | The inbound must provide a domain, or supported sniffing must recover one |
| Routing mode changes an app's proxy settings | Rules only handle connections that reach the core |
| Category data always covers everything | Categories are maintained datasets and may have gaps or change |
Existing connections may keep using their previous route, and browsers may reuse connections. After changing rules, establish a new test connection and restart the app if needed; refreshing the page alone may not be enough. If the logs differ from expectations, first test the inbound and outbound with one exact domain rule, then restore category rules gradually. For a complete walkthrough, see Practical Routing with geosite and geoip.
6. dns: Resolution sources and connection paths
First, identify who is making the DNS query
Configuring DNS in the core does not make the operating system, browser, or every app use those settings. An app may resolve a name locally and send the resulting IP through the proxy, or send the domain directly to a SOCKS or HTTP inbound for later processing. Browser secure DNS, the system resolver cache, and in-app resolution libraries can all create different paths. Start troubleshooting by checking whether the core receives a domain or an IP.
The top-level dns section provides resolution for core features that need it, such as resolving a domain for an IP-based routing check or for certain outbound policies. It does not declare a DNS listener on the local device. If an app bypasses the core and sends its own queries, adding this object alone will not change the app’s behavior. Use a takeover method supported by the client or change the app settings; do not confuse these separate functions.
Basic servers and query strategy
This snippet uses a standard DNS server address and limits queries to IPv4. The address is an example choice, not a fit for every network. For testing, choose a resolver that is reachable on your current network and meets your needs. Changing domain routing will not make an unreachable DNS server respond, and adding more servers without a clear reason can make troubleshooting harder.
{
"dns": {
"hosts": {
"manual.example": "192.0.2.10"
},
"servers": [
"1.1.1.1",
"8.8.8.8"
],
"queryStrategy": "UseIPv4"
}
}
The address in the hosts example is reserved for documentation and only illustrates a static mapping; it is not a real service. Static mappings are useful for controlled tests but bypass normal address updates. If a destination moves and an old mapping remains, it may keep pointing to an invalid address. When investigating resolution issues, check the core’s mappings separately from the system hosts file; they come from different configuration sources.
UseIPv4, UseIPv6, and UseIP control the address-family preference or scope of core DNS queries. Limiting queries to IPv4 can help test for an IPv6 path issue, but it is not always the best permanent setting. If a destination is IPv6-only, forcing IPv4 will not provide a usable address. On a fully dual-stack network, choose based on actual connectivity and requirements rather than copying the example by default.
Plan domain-specific resolution and fallback together
Traditional configurations can use server objects to select a resolver for specific domains. Domain-based server selection, the default query for unmatched domains, and fallback after a failed query may depend on several fields. A domains list alone does not necessarily prevent other servers from being used. If the resolution path matters, check the current core’s fallback options and test failure cases as well as successful queries.
{
"dns": {
"servers": [
{
"address": "1.1.1.1",
"domains": ["domain:example.com"]
},
"8.8.8.8"
],
"queryStrategy": "UseIP"
}
}
The example shows which server is selected for a domain; it does not guarantee that the query travels through a proxy. Whether a DNS request enters routing, uses a direct connection, or is controlled by a specific outbound depends on the server address format, core implementation, and related settings. Plain UDP, DNS over HTTPS, and addresses with local-processing semantics can take different paths. Check which resolver is selected separately from how the request reaches it.
Encrypted DNS services addressed by domain name also need bootstrap resolution. The core must resolve the DNS service’s own address before it can connect. If that step depends on the same resolution path that has not been established yet, a loop can occur. The same issue is harder to spot when the outbound server is also specified by domain. Keep a clear resolution path for startup and use logs to confirm that the service address resolves first.
| Field location | What it controls |
|---|---|
dns.queryStrategy | Address-family strategy for core DNS queries |
routing.domainStrategy | Whether routing resolves a domain to an IP for matching |
| Resolution strategy within an outbound protocol | How that outbound handles destination domains; exact fields vary by protocol and core |
Consider caching after making changes. Apps, the operating system, and the core may each retain results, and restarting only one layer may not change behavior immediately. During testing, record the query, address family, and response, then establish a new connection. Distinguish between getting no address and getting an address but failing to connect. In the latter case, continue with routing and outbound checks instead of blaming DNS just because a domain is involved.
7. policy and log: Connection policies and visibility
Policy levels are not connection priorities
policy configures connection handling and some statistics options. In common traditional configurations, levels organizes settings by user level. These are not speed tiers and do not promise more bandwidth for a connection. A policy applies only when the protocol and user configuration actually reference the relevant level. A fully configured but unused level is not automatically active at runtime.
Do not treat policy settings as generic performance tweaks to apply in bulk. Handshake timeouts, idle timeouts, waits for one-way traffic, and buffer settings solve different problems. Shorter timeouts may clear dead connections sooner, but can also interrupt slow handshakes or apps that remain idle for long periods. Higher values may retain more connection state, but they will not fix remote authentication failures, DNS resolution errors, or a local inbound protocol mismatch.
{
"policy": {
"levels": {
"0": {
"handshake": 4,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5
}
}
}
}
The example uses level zero to illustrate the structure. Keys are still strings, and each time value uses the units specified for that field, typically seconds. This is an illustrative set of parameters, not a recommendation for every network. If the current core defaults meet your needs, you can omit this section. Without measurements to guide changes, fewer custom fields are easier to maintain than a large set of unexplained settings.
Interpret timeouts in the context of connection state
handshake limits the wait for a connection handshake; connIdle applies to how long a connection remains idle; and uplinkOnly and downlinkOnly control how long processing continues in one direction after the other side has ended. Check the current core documentation for supported protocols and state transitions. These are not simply limits on how long a web page can take to load, and they do not replace an app’s own request timeout.
For example, an app may use heartbeats to keep a long-lived connection active when there is no business data. An idle timeout that is too short can cause periodic reconnects. Check the app heartbeat, server timeout, and intermediate network devices instead of increasing the client value indefinitely. Record the original value and test both idle and sustained-transfer behavior after a change to identify which state is affected.
| Setting | What to observe | What not to infer |
|---|---|---|
| Handshake timeout | Whether the connection is terminated too early during setup | That remote identity settings are correct |
| Idle timeout | Whether a connection reconnects when no data is being transferred | That sustained transfer speeds will change |
| Statistics switches | Whether the required conditions for the relevant statistics are met | That the client UI will show every counter |
Logging and statistics are separate configuration paths
Logs explain what happened; statistics aggregate counts for specific connections or users. They are separate features. Some statistics require a top-level stats object, a supported protocol identifier, and a compatible API, in addition to policy switches. Enabling a statsUserUplink field alone does not guarantee a corresponding screen in the graphical client. The client may use another collection method or not read that statistic at all.
{
"log": {
"loglevel": "warning"
}
}
A moderate log level is usually enough for routine use; increase detail temporarily when troubleshooting, if the core supports it. warning is useful for warnings and errors, but may not include the full route for every connection. To trace routing, you may also need access logs or the client’s connection records. Missing successful-connection details do not mean traffic bypassed the core; first check what the logs record.
If you specify a log file path, check that the directory exists, the running process can write to it, and the client allows that location. Writable directories differ between desktop systems and Android apps, so do not copy an absolute path from another platform. Higher privileges are not necessary just to troubleshoot. Start with the client’s existing log viewer, export only the relevant lines, and configure a separate file only if needed.
Logs may contain destination domains, remote addresses, and authentication context. Before sharing, redact sensitive details while preserving the error type, failure stage, and necessary field path. Restore the usual logging level after testing to avoid accumulating large files. To investigate further, check the Troubleshooting FAQ rather than rebuilding the entire configuration based on a single warning.
8. End-to-end validation, migration, and troubleshooting
Validate each layer; do not rely on a single speed test
A complete check covers whether the file can be read, the core can start, the app can reach the inbound, which outbound a connection selects, whether the remote handshake completes, and whether the destination returns data. Client test tools often cover only part of this chain. A successful test does not prove that browser proxy settings are configured; a failed test does not necessarily mean the node is unusable—it may reflect a test target or method that differs from your actual use case.
First, check the structure and startup only. After saving the file, use the client’s configuration checker or startup logs to confirm the parse result. Look for incorrect object nesting, field types, and unsupported fields. Start with the earliest actionable error; later errors may be consequences of the first failure. Before the core starts successfully, there is no need to change DNS or routing categories, since they are not yet active.
Next, test the inbound. Confirm that the listening port matches the app settings, then have one app make a new request and check for a corresponding record. If no connection appears, check system proxy settings, in-app proxy settings, and whether a plugin overrides them. Desktop terminals usually do not inherit browser settings automatically, and Android traffic may be affected by per-app VPN settings. Fix the traffic path first instead of changing remote identity fields.
Restore a working setup with a minimal end-to-end test
Third, test the outbound. Keep one known remote profile and simple routing, confirm the outbound tag, then check dialing, transport handshake, and authentication. Change one related group of parameters at a time and establish a new connection. If a working setup fails after adding split-routing rules, first roll back those new rules. If it fails only after changing cores, check extended fields and protocol support rather than questioning every network condition at once.
Fourth, test DNS and rule coverage. Choose an exact-domain rule, a destination that requires IP-based matching, and one destination that matches no rule. Confirm the route for each. Use destinations relevant to your needs and record the expected outbound; do not judge only by whether a page loads. A successful connection that takes the wrong route still means the rules need work and may expose later traffic to an unintended path.
| Failure stage | Primary checks | Leave unchanged for now |
|---|---|---|
| File parsing | JSON syntax, object structure, and field support | Server selection |
| Local connection | Listen address, port, and app proxy source | Remote transport path |
| Outbound dialing | Address resolution, port, and network path | Policy timeout settings |
| Security or protocol handshake | Server name, identity, and transport combination | Unrelated domain categories |
| Some destinations fail | Rule order, resolution result, and destination status | The already verified inbound |
For cross-client migration, validate the runtime configuration
Migration is not complete just because a file has been copied to another device. First identify the core the target client actually runs, then check the custom configuration entry point, resource data paths, log path, and local listening address. Start with v2rayN on desktop; on Android, choose v2rayNG or v2flyNG as needed. Find installers on the client downloads page and compare client roles in the client selection guide.
When converting a share link or subscription to native JSON, a successful import only means the client accepted the input; it does not prove that every extended field was preserved. Open the profile editor and check the protocol, transport, security layer, and flow control, then inspect the final runtime configuration. Pay particular attention to path encoding, omitted defaults, and renamed fields. For details, see Subscription Formats vs. Native JSON.
Running multiple clients on the same device can also interfere with testing. They may compete for ports, repeatedly change system proxy settings, or leave a core process running. Before switching test clients, stop the existing connection normally and confirm the new client has taken over the intended inbound. Closing a window does not necessarily stop the service, and you should not end unknown processes in bulk before documenting the current state.
A useful test record includes the configuration source, current core, changed fields, time, test app, and failure stage—never the full credentials. If the source of the issue is unclear, return to the direct-connection baseline from section one, then restore the remote outbound, transport, routing, and DNS one at a time. Keep a working copy at each step so you can return to the latest reliable state instead of rebuilding the configuration from scratch.