Xray Configuration File Explained: Inbounds, Outbounds, and Routing

A section-by-section guide to Xray JSON configuration, covering log, inbounds, outbounds, routing, and DNS—and how each field handles traffic.

At a glance

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.

App request Inbound listener Routing match Outbound connection Destination server

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.

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.

FieldPurposeWhat to check when troubleshooting
loglevelSets the error log levelSpecific messages about configuration loading, listener failures, and connection failures
accessSpecifies the access log fileRequest destinations and the outbound actually used
errorSpecifies the error log fileContext 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.

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.

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 LayerExample FieldWhat It Does
Outbound identifiertagReferenced by routing through outboundTag
Proxy protocolprotocolProtocol implementations such as freedom, blackhole, VMess, VLESS, Trojan, or Shadowsocks
Protocol settingssettingsDestination, user, or other protocol-specific settings for the outbound
Transport settingsstreamSettingsTransport 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.

  1. After a request arrives, the core gets the available information, such as the inbound tag, destination domain or IP, and port.
  2. Check rules in order, starting with the first. Different condition types within the same rule generally all need to match.
  3. Items in the same list are generally matched with an “any” condition—for example, any range in the ip array can match.
  4. When a rule matches, its outboundTag is 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.domainStrategyWhen Domain Resolution HappensWhat to Watch for When Reading Rules
AsIsDoes not resolve domains specifically for routing matchesIP rules alone can’t reliably route requests that use domain names
IPIfNonMatchResolve the domain and try matching again if no rule matches itA broad rule that matches too early prevents later resolution and matching
IPOnDemandResolution is triggered when matching reaches a rule that requires an IPThe 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.

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.

Download v2rayN