For readers who can import nodes but aren't yet sure what each config field does. Start with a complete config containing only local listeners, direct connections, and blocking rules to map the request flow. Then learn about ports, tags, and rule order, how to add a proxy outbound, and how to avoid losing changes when v2rayN overwrites a generated config file.
1. How requests flow through config.json
config.json is the configuration file read by the core. It is not a subscription link or a client settings file. Subscriptions typically provide node details; the client uses node, routing, and local listener settings to generate the core config. Understanding this distinction helps you decide whether to make a change in node settings, routing settings, or a custom config.
inbounds defines where requests enter, outbounds defines how they leave, and routing selects the route for each request. A browser must send its traffic to a local proxy port before routing rules can handle it. Changing routing alone won't route every app through the proxy.
This article uses the JSON config format commonly found in V2Ray. V2Fly and Xray share some fields, but supported protocols, options, and command-line arguments vary by core. v2rayNG uses the Xray core, while v2flyNG uses the V2Fly core. On desktop, check which core v2rayN is actually using rather than relying on the client name alone.
2. Complete config: two inbounds, direct connection, and blocking
Here’s a complete example config for learning: a SOCKS listener at 127.0.0.1:10808 and an HTTP listener at 127.0.0.1:10809. Requests to example.org and its subdomains are blocked; all other requests go direct by default. There is no remote proxy node, so this config cannot be used as a remote proxy.
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
},
{
"tag": "http-in",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": [
"domain:example.org"
],
"outboundTag": "blocked"
},
{
"type": "field",
"ip": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"127.0.0.0/8",
"::1/128"
],
"outboundTag": "direct"
},
{
"type": "field",
"inboundTag": [
"http-in"
],
"outboundTag": "direct"
}
]
}
}
This is strict JSON: keys and strings use double quotes, with no comments or trailing commas. Arrays can contain multiple objects, but each object must follow the field definitions for its protocol. Don’t treat a “node name” shown in the UI as a core config key, or add a top-level subscription field arbitrarily; subscription management is a client feature.
3. inbounds: listen address, port, and inbound tag
listen is the local bind address, not the remote node address. port is the local port apps connect to, not the server port. Setting the listener to 127.0.0.1 accepts connections only from the local IPv4 loopback interface. Listening on all network interfaces expands access and affects LAN security; it isn't a general fix for connection failures.
SOCKS inbound
- protocol
socks- listen
127.0.0.1- port
10808- tag
socks-in
Apps must use the SOCKS protocol to connect. UDP support also depends on the app and the outbound route.
HTTP inbound
- protocol
http- listen
127.0.0.1- port
10809- tag
http-in
HTTPS destinations are usually accessed through a CONNECT tunnel; there’s no need to change the inbound protocol to https.
tag is an identifier used within the config. A routing rule can reference the inbound tag socks-in with inboundTag. The tag doesn't determine the protocol or create an outbound with the same name. Use a unique, stable tag for each inbound and outbound, and update every reference when renaming one.
In this example, udp: true only allows UDP on the SOCKS inbound. The app must support SOCKS UDP ASSOCIATE, and the outbound route must carry that traffic. Enabling this option doesn't send all system UDP traffic through the core, nor does a regular HTTP system proxy capture game traffic.
4. outbounds: protocol implementation and default route
In this example, freedom means direct connection and blackhole means block; direct and blocked are user-defined tags. Renaming direct to office-direct won't change its behavior, but you must also update every outboundTag that references it.
| Field or location | Example value | What it does |
|---|---|---|
protocol | freedom | Selects the direct outbound implementation |
tag | direct | Identifier referenced by routing rules |
settings | {} | Config object for this outbound protocol |
| First item in the array | direct | Default outbound used when no rule matches |
If no routing rule matches, the core uses the first item in the outbounds array. In this example, direct is first, so traffic that doesn't match a blocking rule or a rule for a specific inbound still goes direct. Move the blocking outbound to the first position, and many unmatched requests will be blocked. That's a change to the default route, not a node that suddenly stopped working.
Check the default route first, then review exceptions
When reviewing a config, start with outbounds[0].tag, then check each routing.rules entry. This shows where unmatched traffic goes before you focus on individual domain rules and miss the overall behavior.
5. routing: rule order, condition combinations, and DNS resolution
routing.rules are checked from top to bottom in array order. The first matching rule determines the outbound; the core doesn't continue looking for a “more specific” match. Put specific exceptions first and broader rules later. If the first rule matches all TCP and UDP traffic, later domain rules will never take effect.
- Check the destination domain first. If a request targets
example.orgor one of its subdomains, the first rule selectsblocked.domain:matches the domain and its subdomains; usefull:to match one exact domain. - Next, check the destination IP. The private and loopback addresses listed in this example go through
direct. This matches IP ranges; it doesn't necessarily apply just because a domain might resolve to a private address. - Then check the inbound. The third rule matches requests from
http-inand routes them direct. Requests already matched by the domain-blocking rule won't reach this one. - Finally, use the default outbound. Requests from
socks-inthat don't match either of the first two rules use the first array entry,direct.
Within a rule, different condition fields generally need to match together. For example, specifying both inboundTag and domain means the domain must come through the specified inbound. Multiple entries in a domain array are alternatives. Mistaking an “or” condition for a rule that combines multiple fields is a common reason traffic doesn't route as expected.
domainStrategy sits inside routing and controls how domains are handled during route matching. AsIs doesn't resolve domains just to match IP rules; IPIfNonMatch tries resolving a domain after it fails to match; IPOnDemand triggers resolution when a rule requires IP information. It isn't a switch that sends all DNS traffic through a proxy.
6. Add a proxy outbound: confirm the upstream, then update references
A remote proxy requires valid server details. The address, port, identity, and transport security settings for VMess, VLESS, and other outbound protocols must match the server; changing only the protocol name doesn't convert a config. For an existing node, import it through the client and inspect the generated config rather than manually combining protocol fields from different cores.
To demonstrate adding an outbound and referencing it, the example below uses a local SOCKS upstream. It assumes another independent service is listening at 127.0.0.1:10810 and requires no authentication. Add this object to the outbounds array. It's a config snippet, not a complete file.
{
"tag": "proxy",
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.1",
"port": 10810
}
]
}
}
- Adding this object alone won't change existing traffic. A rule must reference
proxythroughoutboundTag, or you must move it to the first position in the outbound array to handle unmatched traffic. - To route all unmatched requests through the upstream, put
proxyfirst in the outbound array and keep any necessary direct-connection exceptions. Adjust the example's existing direct rule forhttp-inas needed. - Don't point the upstream port back to your own
10808; that can create a loop. Even on a different port, the upstream must not send the same request back to this instance. - SOCKS doesn't provide transport encryption. This loopback upstream is only for illustrating config structure; when connecting to a remote server, choose a transport appropriate to the protocol and network boundaries.
7. Validate the config and find v2rayN's save location
Validation has three layers: whether the JSON parses, whether the core accepts the fields, and whether real requests follow the intended rules. None can replace the others. Valid JSON doesn't guarantee referenced tags exist, and a successful core startup doesn't mean the system proxy points to the right port.
- Back up the original file. Keep a copy that starts successfully. Change one category at a time, such as ports or routing, so you can roll back easily.
- Check with the matching core. Run the help command for the bundled core first to confirm the config test options for that version. Use the same core that will run the config; don't rely on results from a different core.
- Check the startup logs. If the logs report a listener failure, check the address or port. If config loading fails, check field types and protocol settings. You can temporarily set
logleveltoinfowhile troubleshooting, then restore it afterward. - Run two specific tests. Test the direct outbound first, then the blocking rule. Specify the port explicitly to avoid interference from another app's proxy settings.
curl --proxy socks5h://127.0.0.1:10808 --connect-timeout 5 https://example.com/
curl --proxy socks5h://127.0.0.1:10808 --connect-timeout 5 https://example.org/
curl --proxy http://127.0.0.1:10809 --connect-timeout 5 https://example.com/
socks5h sends the target hostname to the proxy for resolution, making it useful for testing this example's domain rules. If the destination is reachable from the public internet, the first and third requests should attempt to connect through the direct outbound, while the second should be blocked. Exact errors depend on the core and curl version; don't infer which rule matched from an error code alone—check the logs too. On Windows, run curl.exe explicitly if a terminal alias intercepts the command.
In v2rayN, check local listener ports under Settings → Parameter Settings, then review the active routing config under Settings → Routing Settings. Group names and labels may vary by version, so follow the current UI. The config used to run a regular node is often regenerated by the client. Direct edits to a generated file may be overwritten when you switch nodes, update subscriptions, or restart the core.
Where to save changes: keep routine rules in client settings
To keep ports and routing rules long term, change the corresponding client settings. To manage a complete JSON config, use the client's custom config feature and confirm which core it invokes. Don't maintain both UI rules and a temporary file and assume they'll merge automatically.
8. Common config questions
Why does the example still connect directly when a node is selected?
The first outbound in this example is freedom, with no remote proxy details. Selecting a node in the client doesn't automatically rewrite a standalone JSON config. First confirm which file is loaded, then check outbounds[0] and the outboundTag of the matching rule.
Why did routing stop working after I renamed a tag?
Search the file for the old tag and check each outboundTag and inboundTag. References must exactly match the corresponding object's tag, including capitalization. Retest and restart the core after making changes; saving the file alone doesn't update an already running process.
Why do some apps still bypass port 10809 with the system proxy enabled?
The system proxy only works with apps that honor that setting. First, use the HTTP test command above to check the listener, then see whether the app has its own proxy settings. Expanding the listener address won't fix an app that isn't using the proxy; those are separate issues.
Can I paste subscription content directly into config.json?
Don't treat arbitrary subscription text as core JSON. Import and update the subscription in v2rayN's subscription manager, then check that the node protocol is supported by the selected core. To inspect the structure, view the config generated by the client and hide addresses and identity details before sharing it.
When looking up field definitions, use the JSON reference to check the listener, outbound, and routing sections. For troubleshooting, follow the same sequence: is the app using the proxy, is the inbound listening, does a rule match, and can the outbound connect? Test one layer at a time to find the cause more easily than changing DNS, ports, and protocol settings all at once.