For users who have imported a server profile and want to understand the core config. Follow a request through the four top-level fields, then examine a VMess + WebSocket + TLS example and check tag references, rule order, and DNS. By the end, you’ll know whether to adjust an inbound, server settings, or routing—instead of rewriting the entire JSON every time a connection fails.
1. Config files vs. client settings: know the difference
v2rayN and v2rayNG are clients for managing servers, updating subscriptions, and generating runtime configs for the core. v2rayNG uses the Xray core; v2flyNG uses the v2fly core. v2rayN can manage different cores, but this guide covers the traditional V2Ray/Xray JSON format—not a universal field reference for every core.
Here, config.json means the config actually read by the core—not a client settings file that stores things like window position or subscription groups. The filename may vary: check which file the launch command specifies and whether its contents include fields such as inbounds and outbounds. A client may overwrite a generated file when it updates servers or restarts the core.
inbounds: Define where the core accepts requests, such as a local SOCKS port.outbounds: Define how requests leave the core—for example, through a proxy server, a direct connection, or a block.routing: Select an outbound based on conditions such as the destination domain, IP, or inbound tag.dns: Configure the built-in resolver; this does not automatically take over system DNS.
2. Complete JSON example: two inbounds, three outbounds
Here’s a complete config to explore. node.example.invalid is a deliberately fake domain, and the UUID is for demonstration only, so this config can’t connect to a real server. The example uses the traditional VMess vnext structure. Use the server’s actual address, port, UUID, transport path, and TLS name in a real config.
{
"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"],
"routeOnly": true
}
},
{
"tag": "http-in",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "node.example.invalid",
"port": 443,
"users": [
{
"id": "00000000-0000-4000-8000-000000000001",
"alterId": 0,
"security": "auto"
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"serverName": "node.example.invalid"
},
"wsSettings": {
"path": "/ws",
"headers": {
"Host": "node.example.invalid"
}
}
}
},
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["full:blocked.example.invalid"],
"outboundTag": "block"
},
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "direct"
},
{
"type": "field",
"domain": ["full:localhost"],
"outboundTag": "direct"
}
]
},
"dns": {
"servers": ["223.5.5.5"]
}
}
These are example ports, not fixed defaults across all versions. In JSON, a port is a number and true is a Boolean—neither should be quoted as a string. Don’t add a trailing comma after the last item. Comments are omitted so the example can be validated as-is.
3. inbounds: listen address, protocol, and sniffing
listen sets the local address that accepts connections, port sets the port, and protocol tells the app which proxy protocol to use. A SOCKS client can’t connect directly to an HTTP inbound; an open port doesn’t mean the protocol is right. For manual app setup, this example uses SOCKS5 at 127.0.0.1:10808 or an HTTP proxy at 127.0.0.1:10809.
SOCKS inbound
- tag
- socks-in
- listen
- 127.0.0.1
- port
- 10808
- protocol
- socks
- settings.udp
- true
Listens on localhost only. For UDP, the app must also use SOCKS5 UDP relay.
HTTP inbound
- tag
- http-in
- listen
- 127.0.0.1
- port
- 10809
- protocol
- http
- settings
- Empty object
For apps that support HTTP proxies; HTTPS requests typically use a CONNECT tunnel.
127.0.0.1 restricts the listener to the local machine. Changing it to 0.0.0.0 makes it listen on all IPv4 interfaces. The example SOCKS inbound also uses auth: "noauth", so don’t expose it to a network without changes. If you need to share the proxy, review authentication, firewall rules, and allowed source addresses together.
sniffing can identify a destination domain from an HTTP Host header or the server name in a TLS handshake, helping domain-based routing rules match. routeOnly: true means the sniffed name is used only for routing; it does not replace the original connection target. Sniffing doesn’t decrypt HTTPS or guarantee a domain will be available for every connection. In this example, it’s enabled only on the SOCKS inbound.
tagis an internal config reference, not a username shown to the server.settings.udp: truedoes not automatically route all UDP traffic through the core, and it does not enable TUN mode.- Android clients’ VPN-based traffic capture can’t be explained by these two local listening ports alone. This example is mainly for understanding manual proxy endpoints.
4. outbounds: separate server authentication from transport
For a proxy outbound, protocol: "vmess" specifies the server protocol. settings.vnext holds the server list, while users contains authentication parameters. id is the user’s UUID; alterId: 0 is the common setting for modern VMess—don’t try nonzero values from outdated guides. users[].security: "auto" is a VMess encryption option, not a TLS toggle.
| Field | Example value | What it does |
|---|---|---|
vnext[].address | node.example.invalid | Server address the core connects to |
vnext[].port | 443 | Server listening port, not the local inbound port |
streamSettings.network | ws | Uses WebSocket transport |
streamSettings.security | tls | Enables TLS at the transport layer |
tlsSettings.serverName | node.example.invalid | TLS server name, typically used for SNI and certificate name validation |
wsSettings.path | /ws | WebSocket request path; must match the server |
wsSettings.headers.Host | node.example.invalid | HTTP Host header for the WebSocket handshake |
The example uses the same value for the address, SNI, and Host to keep things simple; these can differ in a real config. Even when connecting to an IP address, TLS may require a domain name. The path must also match exactly: /ws and /proxy aren’t interchangeable labels.
freedom is the direct outbound; blackhole is the blocking outbound. direct, block, and proxy are tags chosen for this example and can be renamed, but every outboundTag reference must be updated to match. VLESS user fields differ from VMess fields—you can’t migrate by changing protocol to vless alone.
Quick check: verify both layers when a connection fails
First compare the address, port, and UUID. Then check the network, TLS name, Host, and path. Don’t disable certificate verification to work around an incorrect serverName; skipping validation won’t make a bad config correct.
5. routing: rule order and the default outbound
Rules in rules are checked in array order, and the first match selects the outbound. This example blocks a demonstration domain first, then sends private addresses and localhost direct. If no rule matches, traditional V2Ray/Xray configs use the first outbound in the outbounds array, so this example defaults to proxy. The default doesn’t depend on the tag being named “proxy.”
- Check the tag:
outboundTag: "direct"must match an outbound withtag: "direct". - Check the order: Put specific exceptions before broad rules. A catch-all rule placed too early will shadow the rules after it.
- Check the conditions: Different fields in a rule generally all need to match. Multiple options in an array for one field generally match if any one of them does.
- Check the data:
geoip:privaterequires the GeoIP data file to be available. If you usegeositeentries, check that the corresponding domain data is available too.
Domain matching syntax varies: full:example.com matches only the exact domain, while domain:example.com matches that domain and its subdomains. A string without a prefix is generally treated as a keyword and may match more targets than expected. For precise exceptions, make the match scope explicit.
| domainStrategy | When resolution happens | What to check |
|---|---|---|
AsIs | Routing does not resolve domains to IP addresses on its own | Don’t expect a domain request to match an IP rule automatically |
IPIfNonMatch | If no domain rule matches, resolve the domain and try matching its IP | The resolved address can change the selected outbound |
IPOnDemand | Resolution is triggered when matching requires an IP condition | Check the cost of resolution and the DNS response |
AsIs keeps the matching process simple in this example. A destination that is already a private IP can match a private-address rule. But a domain that resolves to a private IP isn’t guaranteed to match that rule. For internal domains, add an explicit direct-routing domain rule, or adjust the policy once you understand how resolution works.
6. dns: the built-in resolver is not system DNS
dns.servers specifies which servers the core’s built-in resolver uses. This example has a single upstream, 223.5.5.5, to illustrate the structure; it is not a complete DNS-routing setup. An app may resolve names itself before sending a request. Check the resolution paths used by the system, browser, and core separately.
- App sends a domain name: The core may be able to apply domain rules. If the routing strategy requires an IP, it then queries the built-in DNS resolver.
- App resolves the name first and sends an IP: Routing may see only the IP. Sniffing can recover some domain clues, but it won’t identify every request.
- Direct outbound resolution: The domain strategy for
freedomalso affects resolution. The empty settings in this example don’t mean every lookup uses the built-in upstream above. - Remote proxy resolution: When a domain is sent to the server through the proxy protocol, the server may resolve it. The exact behavior depends on the request and config.
So, adding dns alone doesn’t prove that all lookups are routed as intended. Check whether the app uses its own resolver, the routing domainStrategy, the outbound’s resolution strategy, and whether DNS queries can reach their server. For a server that requires a domain name, make sure its lookup doesn’t depend on connecting through that same server first.
7. From exported config to troubleshooting: change one variable at a time
Before troubleshooting, keep a working copy of the config, then check the core version and the file it actually reads. In v2rayN, go to “Settings” → “Parameter Settings” to view or adjust local listener options; page layouts and labels vary by version. Make sure the client’s server form, routing settings, and runtime JSON agree—don’t focus only on a file the client may regenerate.
- Check the syntax: Use a local editor to check brackets, commas, and value types. Don’t submit UUIDs, subscription URLs, or server details to a public online formatter.
- Check core compatibility: Confirm whether V2Ray or Xray is reading the config. Check that new fields and transport types are supported by your current core version.
- Check the listener: Confirm that the process is listening on the configured port. If you see
address already in use, resolve the port conflict before changing the server UUID. - Check the outbound: Temporarily reduce the number of routing variables and verify the address, authentication, and transport. Troubleshoot timeouts, authentication failures, and TLS errors separately.
- Check routing: Add one narrowly scoped test rule, confirm that the request selects the intended outbound, then restore the full rule set.
- Turn debug logging back down: If needed, temporarily set
logleveltodebug. When you’re done, restorewarningto avoid logging excessive connection details.
When running the core independently, use its config test command. The command below is for V2Ray/Xray executables that use the current subcommand format; replace the path with the actual executable location. Older command-line syntax may differ, so check the local help output first.
v2ray test -c config.json
xray run -test -config config.json
A successful test mainly confirms that the current core can parse and load the config. It doesn’t confirm that the server is online, the certificate is valid, or every rule behaves as intended. Finish by making a real request from an app known to support proxies. In logs, check the inbound tag, destination, and selected outbound. Remove authentication details and personal browsing records before sharing logs.
Key takeaway: verify the inbound, outbound, routing, and DNS in order
First confirm that requests reach the right port, then verify the proxy server, and only then add routing and DNS policies. Keep a restorable copy at each step. Otherwise, after changing all four layers at once, all you’re left with is “it still doesn’t work.”