Start with the inbound, outbound, and the tags that link them, then look at routing and DNS. This guide walks through a traditional V2Ray JSON config with no remote server credentials, explaining its fields, rule order, and troubleshooting checks. It’s for users who have imported a subscription and want to understand files exported by v2rayN. The example covers local SOCKS access, direct connections, and blocking only; it does not provide remote proxy access.
1. Client settings vs. runtime config
v2rayN is a management interface; V2Ray and Xray are the cores that handle connections. A subscription stores server details or a config source, while the interface may also save groups, the current selection, and routing presets. The JSON read by the core at startup describes listening ports, outbound protocols, and routing rules. These files are not interchangeable just because they share a file extension.
This guide uses the traditional V2Ray JSON config format and does not cover every core’s proprietary fields. v2rayNG uses the Xray core, while v2flyNG uses the v2fly core. Seeing inbounds and outbounds in both does not mean every protocol option is compatible. For fields related to REALITY, flow control, and transports, check the documentation for the core actually in use.
Follow this chain to trace references in the config. log helps you inspect what happens, while dns provides name resolution; neither is a network hop that must run in the order shown. The order of the five modules in the JSON object does not determine processing order, but the order of rules in the routing array can affect the result.
- Objects:
{}hold key-value pairs, such as an inbound’s listening address and protocol. - Arrays:
[]hold multiple entries, such as several outbounds or routing rules. - Types:
10808is a number,"10808"is a string, andfalseis a Boolean. They are not interchangeable. - Syntax: This guide uses standard JSON, with no comments or trailing commas. Keys and strings use double quotes.
2. Full example: local SOCKS inbound and two outbounds
Here’s a complete config to illustrate the structure. It listens only on the local loopback address and accepts SOCKS requests. It blocks TCP connections to destination port 25; connections not matched by a rule go direct by default. It does not make apps use a proxy automatically or connect to a remote proxy server.
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "local-socks",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": false
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"inboundTag": ["local-socks"],
"network": "tcp",
"port": "25",
"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"
],
"outboundTag": "direct"
},
{
"type": "field",
"domain": ["full:localhost"],
"outboundTag": "direct"
}
]
},
"dns": {
"servers": ["localhost"],
"queryStrategy": "UseIP"
}
}
These are fixed values for this sample config, not defaults for every client version. If another program is using 10808, choose a free port and update the app’s SOCKS address to match. The private-network direct rule demonstrates the syntax; it is not a complete list of local network ranges.
3. log and inbounds: confirm requests reach the core
log.loglevel sets the amount of log detail. The example uses warning to focus on warnings and errors. For troubleshooting, you can temporarily switch to info, or use debug if needed. Leaving out access or error file paths does not mean logs disappear; the client that launched the core may still capture and display them.
inbounds is an array of entry points. This example accepts only SOCKS connections sent to 127.0.0.1:10808, so the app must be set to use that address. Pointing a browser’s HTTP proxy setting at a SOCKS-only port causes a protocol mismatch; simply starting the core does not change the system proxy settings.
What to check in the logs
- loglevel
- warning
- Temporary troubleshooting
- info or debug
- Output location
- Depends on the configured path and how the core was started
Once you’ve found the issue, lower the log level to avoid recording excessive connection details over time.
Local inbound
- listen
- 127.0.0.1
- port
- 10808
- protocol
- socks
- tag
- local-socks
tag is a routing reference, not a listening address or remote node name.
auth: "noauth" means this SOCKS inbound requires no authentication, so the example limits listening to the local machine. Don’t expose it on all interfaces just to let other devices connect without also setting up access controls. udp: false means SOCKS UDP forwarding is disabled on this inbound, so it can’t be used to verify whether UDP apps work.
A port conflict prevents the inbound from starting; a connection timeout occurs later in the chain. Check the logs for a listening error first, then investigate routing, DNS, or the remote server to avoid unnecessary changes.
4. outbounds: protocols connect; tags link to them
The outbounds array defines how connections leave the core. In this example, freedom connects directly to the destination, while blackhole drops matching traffic. direct and block are arbitrary tags. Renaming a tag does not change what the protocol does, but every rule that references it must be updated too.
| Field | Example value | What to know |
|---|---|---|
| outbounds[0].tag | direct | Routing reference; also the default outbound here |
| outbounds[0].protocol | freedom | Connect directly to the destination, without a remote proxy |
| outbounds[1].protocol | blackhole | Block connections selected by routing |
| settings | Empty object | Uses the default behavior in this example; proxy protocols usually need additional parameters |
In this traditional config model, connections that match no rules go to the first outbound. Changing the array order can therefore change the default behavior. If block is first, requests that previously went direct may be blocked. Don’t treat the order as cosmetic.
A real VMess or VLESS outbound also includes the server address, port, authentication details, and possibly streamSettings. protocol specifies the proxy protocol; transport and security parameters go elsewhere. Simply changing freedom to vless will not establish a connection, and settings from different protocols cannot be swapped as-is.
To check the default behavior, inspect the first outbound and then its tag references
Before editing the config, note the tag of the first outbound and search for every outboundTag. Check both the default path and explicit routes to determine whether a connection will go direct, through a proxy, or be blocked.
5. routing: rules match in order, not by presumed specificity
routing.rules are checked from top to bottom, and the matching rule’s outbound is used. Longer domains and narrower IP ranges do not automatically get priority. Conditions within a rule generally all need to match; multiple values in a condition array usually match if any one of them does. Before configuring rules, distinguish between “all conditions” and “any condition.”
- Rule 1: The inbound tag must be
local-socks, the network must be TCP, and the destination port must be25. All three conditions must match for the connection to go toblock. - Rule 2: Send the connection to
directif the destination IP falls within any listed range. Even a private IP is blocked if it first matches the TCP port 25 rule above. - Rule 3:
full:localhostmatches that exact domain, not every name containing the word localhost. - No rule matches: Use the first outbound, which is
directin this example. Rules 2 and 3 produce the same result as the default here; they are included to demonstrate the syntax.
inboundTag must reference an inbound’s tag, and outboundTag must reference an outbound’s tag. Match spelling and capitalization exactly. These are not group names the core recognizes automatically, and they won’t follow changes to node display names in the client.
domainStrategy: "AsIs" means routing does not resolve domain names to IP addresses for IP-based rules. If the app passes a domain name, the second IP rule may not match; if it passes an IP address, the range can be checked directly. Switching to IPIfNonMatch attempts to resolve the domain after it fails to match at the domain stage. This does not mean all DNS requests are sent through a remote server.
full:example.com: matches only this exact domain, useful for precise tests.domain:example.com: matches this domain and its subdomains, useful for routing by domain.port: "25": this is the destination port, not the local listening port10808.ip: matches the destination address; it cannot express a condition like “from a device on the local network.”
6. dns: distinguish name resolution from traffic routing
The example’s dns.servers contains only "localhost", meaning it uses the local system’s name resolution. It does not listen on a local DNS port. queryStrategy: "UseIP" allows queries for both IPv4 and IPv6 addresses; the results still depend on DNS records, the resolver, and network conditions.
Including a dns module does not mean every app’s DNS requests pass through the core. An app may resolve a name itself and send the resulting IP over SOCKS, or pass the domain name to the core. In the first case, the domain information needed for domain-based routing may be lost; in the second, the destination domain is preserved. How the inbound receives requests and how the DNS module resolves names are separate concerns.
| Config setting | What it controls | Don’t confuse it with |
|---|---|---|
| dns.servers | Resolvers available to the built-in DNS module | DNS settings for every app on the operating system |
| dns.queryStrategy | Address families to query | Routing rules that choose direct or proxied connections |
| routing.domainStrategy | Whether routing uses resolved addresses when matching | Forcing all DNS queries through the same outbound |
| Resolution behavior in an outbound | How that outbound handles destination domains | A universal field shared by every core |
To choose a resolver based on the domain, you can explore fields such as domains in DNS server objects, but also check how query traffic reaches the resolver. Changing the DNS address alone does not prove that queries use the intended proxy route. When troubleshooting, check whether the request carries a domain or an IP address, and which outbound is ultimately selected.
7. Checking an exported config: from syntax to real connections
Make a copy of the config before editing rather than overwriting a runtime file generated by the client. v2rayN may regenerate the config when you switch nodes, update a subscription, or restart the core. Changes made only to a temporary runtime file may disappear on the next start. For lasting changes, use the client’s routing, DNS, or custom config settings; the exact location depends on the version.
- Check JSON syntax: Make sure quotation marks, brackets, braces, and commas are paired correctly. If Python is installed, use the command below to check basic syntax; passing this check does not mean the core supports every field.
- Check which core is running: Verify its name, version, and config file path in the client’s core selection and startup logs. Don’t rely on the client’s name alone.
- Check references: Search for each
inboundTagandoutboundTagto confirm its definition exists, and check the first outbound. - Check the listener and app: Confirm
127.0.0.1:10808starts successfully and that the app uses SOCKS on the same port. - Test one change at a time: Change one module at a time. First test a basic TCP request, then test domain and blocking rules to avoid mixing variables.
python -m json.tool config.json
If the syntax is valid but the core won’t start, check the logs for the field path and error type. Unknown fields, incompatible protocol options, and port conflicts are different problems; changing DNS will not solve them all. Before sharing logs or a config, remove server credentials, subscription links, and any unnecessary personal file paths.
Why does the core still fail to start when the JSON check passes?
A syntax check only confirms that the file can be parsed. Open the client’s startup log, find the first error, and verify the protocol name, field types, and core in use. If the address is already in use, stop the program using port 10808 or change the listening port in both places.
Why did my original node stop working after I changed outboundTag?
First check that the new label exactly matches the tag of an outbound, then review the rule order. Don’t change only the routing reference and forget the outbound definition; also check whether an earlier rule is matching the request first.
Why doesn’t this copied config connect to a server in my subscription?
This example has only two outbounds: freedom and blackhole. To use a remote proxy, have the client generate the appropriate outbound from a valid subscription, then check how routing references it. Don’t guess at authentication parameters in this sample.
Why did my manual changes disappear after restarting v2rayN?
The client probably regenerated the runtime config. Keep copies from before and after your changes, save any routing or DNS changes you want to keep in the client’s persistent settings, then restart and check the newly generated file instead of repeatedly editing a temporary one.