Start by following the direction of traffic, then look at the individual fields. This guide uses a local-only example to explain inbounds, outbounds, routing, and logs. It’s for users who have imported a server and need to track down port conflicts or unexpected routing. The example includes no remote proxy server. By the end, you’ll know how saved server details differ from the runtime config and how to verify changes step by step.
1. V2Ray Config Structure: Client Settings vs. Core Config
In short: inbounds determine where requests enter, outbounds determine how the next connection is made, and routing determines which outbound handles each request. These aren’t three independent switches; they’re consecutive steps in a processing chain.
v2rayN is a desktop client that stores server profiles, manages subscriptions, generates runtime configs, and launches the selected core. On Android, v2rayNG uses the Xray core, while v2flyNG uses the V2Fly core. Similar client names don’t mean they accept exactly the same config fields. Before troubleshooting, check the logs or About screen to identify the core and version in use.
This guide uses the traditional JSON config format. V2Ray and Xray share parts of this structure, but supported features depend on the version of each core. For example, don’t copy settings such as REALITY or xtls-rprx-vision into a V2Fly config just because they appear in an Xray example. Share links and subscription data may not be complete runtime configs, either; you can’t just paste a server address into a JSON file and expect the core to use it.
2. Inbounds: Addresses, Ports, and Protocols
An inbound is where the core accepts traffic—not a remote server. When a browser connects to a local SOCKS port, the request reaches the inbound first, then the core selects an outbound. In this example, SOCKS listens on 10808 and HTTP on 10809. These are example values, not defaults for every client.
SOCKS inbound
- listen
127.0.0.1- port
10808- protocol
socks- tag
socks-in
Set the app to SOCKS5 and use the same address and port.
HTTP inbound
- listen
127.0.0.1- port
10809- protocol
http- tag
http-in
An HTTP proxy can also forward HTTPS connections using CONNECT; it isn’t limited to HTTP websites.
listen sets the listening address. 127.0.0.1 accepts connections only from the local machine. Changing it to 0.0.0.0 binds to all IPv4 interfaces, potentially allowing other devices on the same network to access the proxy. If the proxy is for local use only, don’t widen its listening scope just to troubleshoot.
- Port in use: If you see
address already in useor a bind error, first check whether another core process is using the same address and port. - Protocol mismatch: Sending an HTTP proxy request to a SOCKS inbound can fail during the handshake, even if the port is reachable.
- What tags do:
tagis a reference name within the config. It isn’t a password, but any routing rule that refers to it must use the exact same name. - UDP support: Allowing UDP in SOCKS settings doesn’t mean all UDP traffic from every app will automatically use that inbound.
3. Outbounds: Server Settings and the Default Route
Outbounds define how the core connects out. A remote proxy usually needs a server address, port, credentials, and transport security settings. A direct outbound connects straight to the destination. In the traditional config format used here, requests that don’t match a routing rule use the first item in the outbounds array by default. Array order can therefore affect where traffic goes.
| Field or protocol | What it does | What to check |
|---|---|---|
tag | Makes this outbound available to routing rules | Must exactly match outboundTag |
freedom | Connects directly to the destination | Means a direct connection, not a remote proxy |
blackhole | Discards traffic sent to it | Check whether a rule is matching it unexpectedly |
settings | Stores settings for the selected protocol | The structure varies by outbound protocol |
streamSettings | Sets the transport and transport security | Must match the server configuration |
VMess, VLESS, Trojan, and Shadowsocks each use their own settings structure. Also, don’t confuse a local listening port with a remote server port, even though both fields are called “port.” The local port is where apps connect on your device; the remote port is where the core connects to the server. Changing one won’t fix the other.
With VMess, for example, user credentials go in the protocol’s user array, while TLS belongs in the transport security settings. The WebSocket path, TLS server name, and remote address may all be different. If a subscription import won’t connect, compare each field with the details from your provider instead of changing every value to the server address.
Bottom line: Check the default outbound before routing rules
If unmatched requests go direct, check whether the first array item is freedom. Don’t just change the proxy tag in a rule while overlooking the fact that the default outbound is still direct.
4. Routing: Rule Order and When Domains Are Resolved
Routing doesn’t automatically pick the rule that “looks most specific.” It checks rules in config order, and the first match determines the outbound. A broad rule placed first can prevent narrower rules later in the list from ever taking effect. When a rule includes multiple conditions—such as an inbound, domain, and port—they generally all need to match.
{
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["full:example.invalid"],
"outboundTag": "block"
},
{
"type": "field",
"inboundTag": ["socks-in", "http-in"],
"outboundTag": "direct"
}
]
}
This example first routes an exact match for example.invalid to block, then sends other requests from the two local inbounds to direct. The domain is only an example for illustrating match syntax; it isn’t a working remote server address. full: matches the complete domain, while domain: can match the specified domain and its subdomains. They aren’t interchangeable.
- AsIs: Keeps the original destination form during routing and doesn’t resolve domains specifically for IP rules. The direct outbound may still resolve the domain later to establish a connection.
- IPIfNonMatch: If the domain doesn’t match a rule, attempts to resolve it and continues checking IP rules.
- IPOnDemand: Resolves the domain when the routing process needs to check IP conditions.
Domain rules also depend on what information the inbound receives. If an app resolves the destination to an IP and passes that IP to the proxy, the core may not know the original domain. Sniffing can extract domain clues from some protocol traffic, but it won’t work for every request and isn’t a substitute for configuring DNS routing.
5. Complete JSON Example: Two Inbounds, Direct, and Block
This complete example uses the traditional format to demonstrate the structure only; it doesn’t provide a remote proxy. It listens on two local ports, defaults to direct connections, and blocks an example domain. If a client is already using either port, stop the relevant process or choose an unused port first. Otherwise, a port conflict may look like a config syntax error.
{
"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": "block",
"protocol": "blackhole",
"settings": {}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["full:example.invalid"],
"outboundTag": "block"
},
{
"type": "field",
"inboundTag": ["socks-in", "http-in"],
"outboundTag": "direct"
}
]
}
}
You can trace references backward from anywhere in the config. If you see outboundTag: "block", find tag: "block" in the outbound array. If you see inboundTag, check the inbound array for its name. Thinking of tags as connecting lines within the config makes issues easier to trace than memorizing every field.
- Use a plain-text editor to save the file as
config.json, and make sure it hasn’t been saved as rich text. - Use double quotes for object keys and strings. Write port numbers as numbers, not text with units.
- Don’t add a comma after the last item in an array or object. This example uses standard JSON and contains no comments.
- Test the config with the core before starting the listeners. Passing the syntax check means the core accepts the config; it doesn’t guarantee that a remote server is reachable.
6. Logs and Client Settings: Trace Issues to the Right Layer
log controls log output. Common loglevel values include debug, info, warning, error, and none. For everyday use, leave it at warning. Temporarily switch to info or debug when troubleshooting, then set it back to avoid accumulating details about your activity.
| Config section | Where to check in the client | What to verify |
|---|---|---|
inbounds | Local listening settings under v2rayN “Settings” → “Parameter settings” | Port, listening scope, and inbound protocol |
outbounds | Edit the current server in the server list | Remote address, port, user credentials, transport, and security settings |
routing | Current routing profile and rule editor | Rule order, selected outbound, and whether the profile is enabled |
log | Log level settings and runtime log panel | Startup error and when it occurred |
Menu names can vary between client versions. This table maps concepts; not every version uses the same menu structure. On Android, distinguish VPN mode from a local proxy listener: v2rayNG and v2flyNG can receive traffic through the system VPN service, so desktop system proxy checks don’t apply directly.
On desktop, enabling the system proxy tells apps that respect system proxy settings to use the selected inbound. It doesn’t guarantee that every app’s traffic passes through the core. If there are no connection records for an app in the logs, first check whether its traffic reached the inbound; don’t change the remote server straight away. For more on traffic capture, see the usage guide. For connection issues, see the connection troubleshooting guide.
- Exits immediately on startup
- First check for JSON parsing errors, unsupported fields, port bind failures, and missing data resources.
- Local port is reachable, but connections fail
- Next, check routing, the remote connection, authentication, the TLS handshake, and DNS resolution. An open port doesn’t necessarily mean the proxy is working.
- Only some websites have issues
- Note the domain and time, check whether a block or direct rule matched, then compare the result using the same server.
7. Validation: Change One Thing at a Time
Command-line tests can confirm whether the core accepts a config. The commands below show common forms for V2Ray 5.x and Xray; run only the one that matches your core. Check the executable path and current working directory. For other versions, consult the program’s help output first.
v2ray test -c config.json
xray run -test -config config.json
These tests don’t replace a real request or prove that your account, server, and network path are working. After a successful test, start the core, confirm it’s listening, send a request, and check the outbound route. Keep the destination and connection method the same during testing so you can tell which change made a difference.
- Check the actual file: Confirm which config file the startup command points to. Make sure you haven’t edited a backup while the core is running another file.
- Check syntax and fields: Test the config with the core you’re running. If it fails, start with the first clear error; later messages may be follow-on errors.
- Check the inbound: Verify the app’s proxy type and port so its requests reach the intended inbound.
- Check the rules: Verify tag references and rule order. If needed, temporarily simplify routing to a single outbound for comparison.
- Check the remote server: Restore the actual proxy settings, then verify the server address, authentication, and transport security settings.
- Restore normal settings: Undo any temporary changes that widened the listening scope, restore the log level, and save a record of the changes that produced a reproducible result.
Bottom line: Confirm requests reach the inbound before troubleshooting the remote server
If there are no inbound records, fix the app connection and listening settings first. If requests reach the inbound but use the wrong outbound, start with routing. Troubleshooting one layer at a time makes it easier to pinpoint the issue than changing ports, protocols, and servers all at once.