This guide is for users who can already import nodes and are ready to read native Xray configuration. Start with a complete JSON example featuring local inbounds, direct and blocked outbounds, then learn how logging, ports, tags, routing order, and DNS fit together. By the end, you’ll be able to pinpoint where traffic stops and distinguish client settings, share links, and runtime configuration.
Configuration Structure and Traffic Flow
Xray is the core that handles connections; v2rayN is the desktop client that manages nodes, generates configuration, and launches the core. A node share link usually describes just one remote connection, while native JSON can define local listeners, multiple outbounds, routing, and DNS. Saving a share link as config.json does not turn it into a runnable core configuration.
This is the main path to follow when reading a configuration. log records status, while dns comes into play when a domain needs resolving; neither is a separate forwarding hop every request must pass through. The order of top-level objects in the JSON does not determine traffic flow.
inbounds: where traffic enters and which local protocol it uses.outbounds: how connections leave the core—direct, proxied, or blocked.routing: which outbound tag to use based on the source and destination.dns: configures built-in DNS; whether it is used also depends on related resolution settings.
Complete JSON Example: Local Inbounds and Direct Outbound
Here’s a standalone configuration for understanding the structure. The SOCKS inbound listens on 127.0.0.1:10808, and the HTTP inbound listens on 127.0.0.1:10809. There’s no remote proxy node: ordinary requests connect directly, while requests to the reserved test domain blocked.test are blocked. This configuration cannot be used to verify 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": {
"domainStrategy": "UseIP"
}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": [
"full:blocked.test"
],
"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"
}
]
},
"dns": {
"servers": [
"localhost"
]
}
}
This example uses strict JSON: keys and strings are double-quoted, comments aren’t allowed, and there’s no trailing comma after the last array element. The field notes below explain this configuration; you don’t need to assemble them into a second configuration.
log: Pinpoint Where Errors Occur
loglevel controls how much detail error logs include. Common values are debug, info, warning, error, and none. For troubleshooting, temporarily change warning in the example to info, then switch it back to avoid keeping excessive connection details in your logs.
| Field | Purpose | What to check when troubleshooting |
|---|---|---|
loglevel | Sets the error log level | Specific messages about configuration loading, listener failures, and connection failures |
access | Specifies the access log file | Request destinations and the outbound actually used |
error | Specifies the error log file | Context for startup and runtime errors |
This example doesn’t specify log file paths, so start by checking the console when running from the command line. If you add access or error, choose a path the current process can write to, and account for relative paths and the working directory. Logs may include destinations, so remove sensitive information before sharing them for troubleshooting.
- JSON parsing error before startup: check brackets, commas, and field types first.
address already in useat startup: check the local port before changing the remote protocol.- The inbound accepts the request, but the connection later times out: check the actual outbound, DNS resolution, and destination reachability.
inbounds: Listen Address, Port, and Inbound Tag
Each inbound object defines a listener that receives traffic. listen is the bind address, port is the listening port, and protocol determines which protocol the app uses to connect. tag is an identifier within the configuration—not a password or hostname to enter in a browser.
SOCKS Inbound
- Tag
socks-in- Address
127.0.0.1- Port
10808- Protocol
socks- UDP
true
The app must connect using SOCKS. UDP also requires support from both the app and the rest of the connection path.
HTTP Inbound
- Tag
http-in- Address
127.0.0.1- Port
10809- Protocol
http- Protocol Settings
{}
HTTPS sites typically establish a tunnel with HTTP CONNECT; this doesn’t mean you should change the inbound protocol to HTTPS.
127.0.0.1 accepts connections only from the local machine. Changing it to 0.0.0.0 broadens the IPv4 listening scope; whether other devices can connect still depends on the firewall and network environment. This example uses noauth for SOCKS, so don’t expose the listener more widely just to try sharing it on a local network.
- When a browser uses the system proxy settings, point it to the actual HTTP inbound. Don’t mix up the two ports.
- The local port isn’t the remote server port; changing
10808won’t change the node’s server-side configuration. udp: trueenables UDP on the SOCKS inbound only; it won’t make the HTTP inbound carry regular UDP traffic.
outbounds: Outbound Protocols and the Default Outbound
outbounds is an array of outbounds available to routing. In this example, freedom connects directly to the destination, while blackhole blocks the connection. direct and block are user-defined tags here; behavior is determined by protocol, not by what the tag name means in English.
| Configuration Layer | Example Field | What It Does |
|---|---|---|
| Outbound identifier | tag | Referenced by routing through outboundTag |
| Proxy protocol | protocol | Protocol implementations such as freedom, blackhole, VMess, VLESS, Trojan, or Shadowsocks |
| Protocol settings | settings | Destination, user, or other protocol-specific settings for the outbound |
| Transport settings | streamSettings | Transport method and transport security for supported proxy outbounds |
When adding a remote proxy outbound, the protocol, server address, port, identity parameters, and transport settings must all match. Simply changing freedom to vless does not configure a working node. TLS or REALITY fields belong in the relevant transport security settings; don’t put them in the inbound SOCKS settings.
Takeaway: Add a Fallback Rule Before Reordering Outbounds
When no routing rule matches, the first outbound is usually used. In this example, direct comes first, so unmatched requests connect directly. If you want proxying by default, configure a fallback outbound explicitly; don’t just check whether a particular rule names a proxy tag.
routing: Rule Order, Tag References, and Resolution Strategy
Rules in routing.rules are checked in array order, and the first matching rule determines the outbound. The first rule in this example sends the exact domain blocked.test to block; the second sends the listed IPv4 ranges to direct. These ranges are examples, not a comprehensive set of special-use addresses, and they don’t include IPv6.
- After a request arrives, the core gets the available information, such as the inbound tag, destination domain or IP, and port.
- Check rules in order, starting with the first. Different condition types within the same rule generally all need to match.
- Items in the same list are generally matched with an “any” condition—for example, any range in the
iparray can match. - When a rule matches, its
outboundTagis used; if none match, the default outbound is used.
full: matches an exact domain, while domain: matches a domain and its subdomains. To scope a rule to a particular inbound, add an inboundTag array. Referenced tags must match the tag on the corresponding object. Inbound and outbound tags serve different purposes; you can’t reference socks-in as an outbound.
| routing.domainStrategy | When Domain Resolution Happens | What to Watch for When Reading Rules |
|---|---|---|
AsIs | Does not resolve domains specifically for routing matches | IP rules alone can’t reliably route requests that use domain names |
IPIfNonMatch | Resolve the domain and try matching again if no rule matches it | A broad rule that matches too early prevents later resolution and matching |
IPOnDemand | Resolution is triggered when matching reaches a rule that requires an IP | The position of an IP rule affects when resolution occurs |
This example uses AsIs, so requests submitted as domains aren’t resolved just to match the second IP rule. However, the default outbound is still direct, so seeing a request connect directly doesn’t prove it matched the private IP range rule. When troubleshooting, check the actual rule match and outbound—not just whether the page loads.
dns: Distinguish Routing Resolution from Connection Resolution
Configuring dns doesn’t mean Xray takes over DNS for every app. An app may resolve a domain itself and then pass the IP to the proxy, or send the domain to a SOCKS or HTTP inbound. What information routing can use depends first on what the inbound actually receives.
In this example, dns.servers uses the special value localhost, which means resolution goes through the local system resolver—not a DNS server running on the local machine. It also doesn’t open a UDP port 53 listener. To provide DNS service to apps, you need to design a separate inbound for DNS requests and configure how they’re handled.
routing.domainStrategy: determines whether routing needs to resolve domains to IP addresses for rule matching.freedom.settings.domainStrategy: determines how the direct outbound handles destination domains.dns.servers: provides resolvers for built-in DNS; related features determine when they’re used.
This example sets UseIP on the freedom outbound, so direct connections resolve domains through the built-in DNS process. This doesn’t conflict with AsIs for routing: the former applies when the outbound prepares to connect, while the latter governs rule matching. Even if the outbound later gets an IP, routing isn’t automatically run again after its decision has been made.
Takeaway: First Check Whether the App Sends a Domain or an IP
When a domain rule doesn’t match, first check whether the app resolved the domain locally. Don’t change the routing strategy, DNS server, and outbound protocol all at once. Change one layer at a time so you can tell what affected the result.
Testing and Troubleshooting: From Config Checks to Live Connections
First validate the configuration with the core’s built-in check command, then start the listeners, and finally connect an app. Run the commands below in a terminal where the xray executable is available. If it isn’t on your command search path, use its actual path. Check the version and help output first to confirm which options the current binary supports.
xray version
xray run -help
xray run -test -config ./config.json
xray run -config ./config.json
A successful config check only means the core accepts the configuration; it doesn’t mean a remote server is reachable or that an app is using the proxy. For a repeatable local test, start a test server in another terminal, then access it through each inbound:
python -m http.server 8000 --bind 127.0.0.1
curl --noproxy "" --proxy socks5h://127.0.0.1:10808 http://127.0.0.1:8000/
curl --noproxy "" --proxy http://127.0.0.1:10809 http://127.0.0.1:8000/
This test requires Python and curl installed on the local machine. The response comes from a local test directory, verifying the chain from app to inbound, routing, and direct outbound. --noproxy "" prevents existing proxy bypass settings from skipping this inbound test. Stop the temporary server when you’re done; a directory listing in the terminal isn’t a proxy speed test.
Why Does the Core Exit Right After Startup Even Though the Config Check Passed?
Check the console for listener errors. Make sure 10808 and 10809 aren’t in use. If necessary, switch to 11808 and 11809, and update the app’s proxy ports to match.
Why Does Traffic Still Use the Default Outbound After I Add a Domain Rule?
First confirm the request includes a domain, then check whether an earlier, broader rule is catching it. Verify the exact matching scope of full: and make sure outboundTag references the correct outbound.
Why Did My Changes to the Runtime Config in v2rayN Disappear After Restart?
The client may regenerate the runtime configuration. Keep a separate copy first, then load it through the custom configuration option available in your version, or edit the corresponding routing and DNS settings in the client. Don’t rely on a temporary runtime file as a permanent configuration source.
Can I Import This JSON Directly into Any Client?
First check whether the import option accepts native configuration or node share content. v2rayNG uses the Xray core, while v2flyNG uses the v2fly core. Compatibility of a complete configuration still depends on the core version, supported fields, and how the client loads it.
Once the baseline test works, add a remote proxy outbound and configure an explicit route for it. Change one thing at a time and keep the last working configuration. If something breaks, you can narrow the cause to the inbound, rules, resolution, or remote connection instead of repeatedly changing unrelated settings.