For readers who have imported nodes but aren’t sure how to read the configuration generated by their client. Start by testing the structure with a local port, 10808, and a direct outbound, then compare the VMess outbound and domain-routing examples to see how the fields fit together. You’ll learn to distinguish local listeners, remote connections, rule matching, and client settings—and why a subscription link isn’t a complete config.json.
1. config.json Structure and Request Flow
config.json is one possible filename for a core configuration file, not a fixed storage location. Clients such as v2rayN may generate temporary runtime configurations, and manual changes to those files can be overwritten the next time you switch nodes. First check whether you’re running Xray or V2Fly, then find the configuration file actually loaded and review the startup logs.
inbounds and outbounds are arrays, so each can contain multiple entries. routing is an object; its rules property is the rules array. A tag is an internal reference name, not a server address. Rules use outboundTag to point to an outbound.
Here’s a complete starter configuration for local direct-connection testing only. It exposes a SOCKS service on the loopback address, and sends requests not matched by a rule to the first outbound, direct. This configuration has no remote proxy node and does not change your system proxy settings.
{
"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": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": []
}
}
2. inbounds: Listen Address, Port, and Local Protocol
An inbound determines how an application passes traffic to the core. In the example above, 127.0.0.1:10808 is a local SOCKS listener, not the port of a remote node. Browsers, command-line tools, and other SOCKS-compatible apps must be configured to use this listener before their traffic reaches the core.
Local SOCKS Inbound
- tag
- local-socks
- listen
- 127.0.0.1
- port
- 10808
- protocol
- socks
Accepts connections from this machine only; the app must use the SOCKS protocol.
Optional HTTP Inbound
- tag
- local-http
- listen
- 127.0.0.1
- port
- 10809
- protocol
- http
Must be added separately under inbounds; it isn’t provided automatically by the SOCKS inbound.
10808 and 10809 are example values used in this article, not fixed defaults across all versions. Pointing an app’s HTTP proxy setting at a SOCKS port causes a protocol mismatch. If your client offers a mixed-protocol listener, check which protocols it actually supports rather than guessing from the port number.
listen: "127.0.0.1"restricts access to the local machine, making it suitable for personal desktop testing.listen: "0.0.0.0"listens on all IPv4 interfaces. Sharing access on a local network also requires authentication and firewall configuration; changing the address alone is not enough to secure it.settings.udp: trueenables SOCKS UDP forwarding, but the app must support SOCKS5 UDP, and the outbound and server must also support the traffic.- System proxy settings provide an entry point at the operating-system level, while TUN routes traffic through a virtual network interface. Neither is the same as a regular SOCKS inbound.
Use the command below to check whether an app can make an HTTPS request through the example listener. Use curl.exe on Windows and curl on macOS or Linux. socks5h sends the destination hostname to the proxy for handling; where it’s ultimately resolved depends on the configuration.
curl --proxy socks5h://127.0.0.1:10808 --connect-timeout 10 -I https://example.com
3. outbounds: Remote Nodes and Transport Settings
An outbound determines where the core connects to reach a destination. freedom means a direct connection, blackhole blocks traffic, and vmess requires a compatible remote server. An inbound can use SOCKS while an outbound uses VMess: they serve different points in the connection path, so their names don’t need to match.
Here’s an example to replace the top-level outbounds section. 192.0.2.10 is a documentation-only address, and the UUID and domain are illustrative too, so none of these values can be used to connect. For real use, get the address, port, user ID, transport, and TLS settings from an authorized server configuration. Replacing just one field won’t work.
{
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "192.0.2.10",
"port": 443,
"users": [
{
"id": "b43b4b90-a710-4ee4-883d-b8c574099d11",
"alterId": 0,
"security": "auto"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "proxy.example.net"
}
}
},
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
}
| Field | Example value | What to check |
|---|---|---|
settings.vnext[].address | 192.0.2.10 | Remote server address, not the local listen address. |
settings.vnext[].port | 443 | Port that accepts connections on the server, not 10808. |
users[].security | auto | VMess user encryption setting, not a TLS switch. |
streamSettings.network | tcp | This example uses TCP; other transports require their corresponding settings. |
streamSettings.security | tls | Transport-layer security method; distinct from the protocol’s security setting. |
tlsSettings.serverName | proxy.example.net | TLS server name, which must match the server deployment and certificate. |
Takeaway: Check the Protocol, Then the Transport
Changing VMess to VLESS takes more than changing protocol. Check settings against your core’s configuration spec, then review streamSettings. For TLS failures, check the domain, certificate, and system time; disabling certificate verification is not a routine fix.
v2rayNG uses the Xray core, while v2flyNG uses the V2Fly core. Which cores are available in v2rayN depends on what’s installed and selected. Similar top-level structures don’t guarantee full compatibility with extension fields, especially for specific VLESS security combinations. The TCP fields in this article illustrate common configuration patterns; check field names and compatibility against your installed core version.
4. routing: Rule Order, Tag References, and the Default Outbound
Routing rules don’t take effect just because you’ve grouped a few domains; the core checks rules in order. Usually, the first matching rule determines the outbound. Different conditions within one rule must all be met, while multiple values in an array for the same condition are alternatives. outboundTag must exactly match the tag of an existing outbound.
This example depends on the proxy, direct, and block outbounds from the previous section. It blocks a sample domain first, sends private IP addresses directly, and routes everything else through the proxy. Both domains are examples for illustrating rules, not recommendations for where to connect.
{
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["full:blocked.example.org"],
"outboundTag": "block"
},
{
"type": "field",
"ip": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
"outboundTag": "direct"
},
{
"type": "field",
"domain": ["domain:example.org"],
"outboundTag": "direct"
},
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
}
}
full:blocked.example.orgmatches only that exact domain. Although it’s also a subdomain ofexample.org, the earlier blocking rule takes precedence.- A private-network rule applies to requests whose destination IP falls within the specified ranges. It isn’t a complete list of special-use addresses and doesn’t automatically cover local IPv6 addresses.
domain:example.orgmatches that domain and its subdomains; it doesn’t match arbitrary strings that happen to contain it.- The final
network: "tcp,udp"rule is the fallback in this example. Put it after specific rules, or it may catch traffic before those rules can take effect.
If no rule matches, the core typically uses the first outbound in the outbounds array. The first outbound in section one is direct; in section three, it’s changed to proxy. Simply reordering the array can therefore change the default behavior. Having a proxy node in the array doesn’t mean all requests use it.
5. dns, Logging, and the Limits of Client-Generated Configurations
routing.domainStrategy controls how routing handles domains during rule matching. It isn’t a DNS server address or a complete DNS-routing policy. AsIs tries to match using the original destination; IPIfNonMatch tries resolving the domain and then checks IP rules if no domain rule matches; IPOnDemand may trigger resolution when checking IP rules.
This example only adds built-in DNS settings. It won’t automatically change the operating system’s DNS settings or guarantee that every app’s queries pass through the core. If an app resolves a domain locally before connecting, the core may receive only an IP address. If the proxy protocol passes the domain through, the outbound and server may determine where it’s resolved.
{
"dns": {
"servers": ["localhost"]
}
}
- Subscription: A way for a client to retrieve node details, not a generic core field you can put directly in
outbounds. - System proxy: Tells apps that honor system proxy settings to use a local listener; it doesn’t route traffic from every process.
- TUN: Managed by the client and its networking components. A TUN switch in the interface doesn’t map directly to a generic JSON field.
- Logs: For troubleshooting, temporarily set
log.logleveltoinfo, reload, and reproduce the issue once. Restorewarningwhen you’re done.
When reading a configuration exported by a client, start with inbounds[].port, outbounds[].tag, and routing.rules, then trace DNS and transport settings. Don’t delete unfamiliar management listeners or extra fields without checking: they may support client statistics, control interfaces, or runtime features. Before sharing logs, remove user IDs, subscription URLs, and server details.
6. Configuration Checks and Common Issues
Save a restorable copy before making changes, and change one area at a time. First validate the JSON and core fields, then check the listening port, and finally test the remote connection and rule matching. The commands below are for newer cores that provide the relevant command-line interface. Check your installed program’s help output for supported options, and use the path to the file the client actually loads when it manages the configuration.
xray run -test -config config.json
v2ray test -c config.json
A successful test command doesn’t mean the node is usable. It mainly checks whether the current core accepts the configuration; it doesn’t replace tests for remote authentication, the TLS handshake, or access to the destination. If the core starts but an app still can’t connect, narrow down the issue in this order: local listener, remote outbound, then routing result.
Getting “address already in use”? Which port should you change?
First check which inbound port is already in use. If the log points to 10808, stop the duplicate core process or change the port under inbounds to 10810, then update the app’s proxy address to match. Don’t change the remote node’s port 443 for this.
The core starts, but curl still can’t connect?
First, use the direct-connection configuration and socks5h test command from section one to check the listener. If the connection to the local port fails, check the listen address, port, and proxy protocol. If traffic reaches the core but then times out, check the remote address, authentication, and TLS logs.
Why is traffic still using the proxy after I added a direct rule?
Check rule order, the spelling of outboundTag, and whether the core receives a domain or an IP address. Make sure the catch-all rule isn’t placed first, then confirm the running process loaded the file you edited rather than a newly generated client config.
Why did my manual JSON changes disappear after a subscription update?
The client may regenerate its runtime configuration from the node and global settings. For persistent changes, use the client’s routing, inbound, or custom configuration options. If you choose to manage a complete JSON file yourself, make sure you know its path and how the core is started so the two configurations don’t overwrite each other.
Takeaway: Test One Change with One Request
Keep the local listener at 127.0.0.1:10808 and use the same test destination, then check direct connections, proxy connections, and routing separately. Check the logs after each reload; this makes it easier to locate the problem than changing the port, DNS, and transport settings all at once.