V2Ray config.json Explained: inbounds, outbounds, and routing

A field-by-field guide to log, inbounds, outbounds, routing, and DNS, including how outboundTag connects routing rules to their chosen outbounds.

At a glance

This guide is for anyone who needs to read a client-generated config or troubleshoot ports and routing. Start with a complete configuration containing local inbounds, direct and block outbounds, then learn how remote proxies, rule order, and DNS fit in. The example helps verify the configuration structure; it does not include a usable remote proxy. By the end, you’ll know where a request enters, which rule it matches, and which outbound handles it.

1. config.json Structure and Request Flow

config.json is a common configuration filename, not a fixed requirement. The kernel’s startup arguments determine which file it loads. This guide uses V2Ray’s traditional JSON configuration format. V2Ray and Xray share many core fields, but their extensions and version support differ; don’t assume a field added in one kernel can be copied directly to the other.

App request Local inbound Rule matching Outbound handling Destination connection

inbounds accept app traffic, outbounds define how connections are made, and routing selects an outbound. log records runtime information, while dns configures the built-in resolver. The order of these top-level keys in a JSON object usually doesn’t affect execution, but the order of rules in the routing array does.

Top-level fieldData typeWhat to check first
logObjectCan you see startup errors and request logs?
inboundsArrayDo the app’s connection protocol, address, and port match the inbound?
outboundsArrayAre the outbound protocol and its settings complete?
routingObjectDo the rule order and outbound tags line up?
dnsObjectWhich steps actually use the built-in resolver?

Use the configuration below as a standalone kernel structure test. It listens on two local ports, blocks example.org and its subdomains, and sends all other requests directly by default. It does not send traffic to a remote proxy or enable the system proxy automatically.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "socks-in",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": false
      }
    },
    {
      "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": ["domain:example.org"],
        "outboundTag": "block"
      },
      {
        "type": "field",
        "ip": ["geoip:private"],
        "outboundTag": "direct"
      }
    ]
  },
  "dns": {
    "servers": ["1.1.1.1"]
  }
}

2. log: Make Configuration Errors Easier to Find

Common loglevel values are debug, info, warning, error, and none. This example uses warning to keep routine output manageable. For startup or connection issues, temporarily switch to info; use debug when you need more detailed internal logs.

{
  "log": {
    "access": "access.log",
    "error": "error.log",
    "loglevel": "info"
  }
}

This is a top-level snippet: use it to replace log in the full configuration, not to add a duplicate key. Relative log paths are resolved from the process’s working directory, which may not be next to the config file. When a client launches the kernel, its working directory, file permissions, and log display may differ; start with the runtime logs provided by the client.

3. inbounds: Listen Addresses, Ports, and Proxy Protocols

inbounds is an array, so you can define multiple entry points. Each inbound’s protocol determines how it interprets data from apps. SOCKS and HTTP proxy are different protocols; even when both listen locally, an app configured for the wrong type may fail to connect.

10808
SOCKS inbound port in this example
10809
HTTP inbound port in this example
2
Local inbounds in this configuration

These are explicit example values, not defaults for every client version. For an HTTP proxy, configure the app to use address 127.0.0.1 and port 10809. For a SOCKS5 proxy, use 127.0.0.1:10808.

Local SOCKS inbound

Tag
socks-in
Listen
127.0.0.1:10808
Protocol
socks
Authentication
noauth
UDP
false

This example handles local TCP requests only; SOCKS UDP forwarding is disabled.

Local HTTP inbound

Tag
http-in
Listen
127.0.0.1:10809
Protocol
http
Settings
{}

For apps that support HTTP proxies; HTTPS requests usually establish a tunnel with CONNECT.

Setting listen to a loopback address limits access to the local machine. Changing it to 0.0.0.0 listens on all IPv4 interfaces and may expose the inbound to other devices on your LAN. If you need to share it, plan authentication, firewall rules, and access scope together—don’t just change the listen address.

4. outbounds: Matching Protocols to Tags

outbounds define how traffic leaves the kernel. In this example, freedom is the direct outbound and blackhole blocks traffic. direct and block are user-defined tags; a tag alone doesn’t determine what an outbound does. Its behavior comes from protocol and the related settings.

Outbound tagProtocolBehavior in this example
directfreedomConnects directly to the destination and handles unmatched requests by default
blockblackholeBlocks matching requests without connecting to a remote proxy
proxyDepends on the actual proxy nodeNot defined here; add a complete proxy outbound before referencing it

In this type of configuration, requests that don’t match a routing rule use the first outbound in the outbounds array. This example puts direct first, so unmatched requests go direct. If you move a real proxy outbound to the first position, unmatched traffic will use it instead.

A VMess or VLESS outbound requires a real server address, port, user ID, and transport and security settings that match the server. You can’t infer settings such as a WebSocket path or TLS server name from the protocol name alone. For configurations that use Xray extensions, check which features the kernel supports; not every VLESS configuration is compatible with V2Ray.

Bottom line: Check the tags before changing node settings

Compare each rule’s outboundTag with the outbound’s tag, character for character, and make sure the first outbound matches your default traffic policy. Only after both checks pass should you troubleshoot the server and transport settings.

5. routing: Rule Order and outboundTag

routing.rules are checked in array order, and the first matching rule selects its specified outbound. A broad rule placed first may prevent more specific rules later in the list from running. Different condition fields in the same rule generally all need to match; multiple values in an array for one field generally match if any value does.

{
  "type": "field",
  "inboundTag": ["http-in"],
  "domain": ["domain:example.org"],
  "outboundTag": "block"
}

The snippet above shows combined conditions: a request must come from http-in and target example.org or one of its subdomains to use block. To match only the exact domain, use full:example.org instead of a domain suffix match.

  1. First, list the actual outbound tags, such as direct and block in this example.
  2. Put specific rules that need priority first, followed by broader rules.
  3. Check every outboundTag: spelling, capitalization, and hyphens must match the outbound tag exactly.
  4. Check whether the inbound receives a domain or an IP address. Different destination information can lead to different rule matches.
  5. Be clear that unmatched traffic uses the first outbound; “no match” does not mean traffic is blocked automatically.
domainStrategyRouting strategyWhat to keep in mind
AsIsUse the destination information already available; don’t resolve domains just to match routing rulesIf the destination is already an IP address, IP rules can still match it
IPIfNonMatchIf a domain doesn’t match a rule, try resolving it before checking IP rulesMay add a DNS lookup; results depend on the addresses returned by DNS
IPOnDemandResolve the domain when a rule needs to check the IP addressUnderstand when resolution happens in the context of rule order

This example uses AsIs. That means an app-provided private IP can match geoip:private, but a domain that ultimately resolves to a private address may not match this IP rule. Not resolving at the routing layer doesn’t mean resolution is never needed to establish the connection.

6. dns: The Built-in Resolver Is Not the System DNS

dns.servers specifies servers for the kernel’s built-in resolver. The 1.1.1.1 address here is an example of traditional DNS; it doesn’t enable encrypted DNS or guarantee that the server is reachable on your network. Choose a resolution method for your actual network and confirm that queries can reach the resolver.

For example, to have the direct outbound actively use the built-in DNS to obtain an IPv4 address, update its settings with the snippet below, if your kernel supports it. This is an outbound setting, not a routing setting. Both use domainStrategy, but the scope is different.

{
  "tag": "direct",
  "protocol": "freedom",
  "settings": {
    "domainStrategy": "UseIPv4"
  }
}

7. Validate the Configuration and Understand Client Limitations

Validate the syntax first, then start the listeners, and finally test the request path. Standard JSON does not allow comments or trailing commas. When copying a snippet, replace the relevant object rather than leaving two top-level keys with the same name. A loader accepting extensions doesn’t mean other kernels or clients will support them.

  1. Back up the original configuration and confirm you’re editing the file that is actually loaded.
  2. Use the configuration test command for your current kernel, and fix field and resource-file errors first.
  3. Stop the processes using 10808 and 10809, then start the test kernel.
  4. Send a request through an explicitly specified proxy port instead of relying on the system proxy being enabled.
  5. Check the results for the blocked domain and a normal destination, then add a real proxy outbound and more complex rules one at a time.

V2Ray 5 commonly uses the following command to test traditional JSON configurations. Executable paths may vary by distribution; run v2ray help first to confirm the available subcommands. Don’t mix the older -test -config argument format with the newer subcommands.

v2ray test -c config.json
v2ray run -c config.json

Once the kernel starts successfully, use a proxy-aware request tool to test the route. Both commands below request the domain blocked in this example, so the expected result is a failed request or a closed connection. This confirms the rule is working; it is not a connectivity test. The socks5h scheme in the second command sends the domain to the proxy to resolve, helping preserve domain-based matching.

curl --max-time 10 --proxy http://127.0.0.1:10809 http://example.org/
curl --max-time 10 --proxy socks5h://127.0.0.1:10808 http://example.org/

v2rayN is a desktop client that generates the kernel’s runtime configuration based on the selected node, routing, and settings. Generated files may be overwritten when you switch nodes or restart. v2rayNG uses the Xray kernel, while v2flyNG uses the V2Fly kernel. Reviewing their runtime configurations can help explain similar fields, but whether custom JSON imports work and how they interact with UI settings depends on the specific client version.

Why doesn’t changing config.json affect the traffic in my client?

First confirm you edited the file loaded by the current process, not an old exported copy. Then check whether the client regenerated the configuration, restarted the kernel, and routed the app through the relevant inbound. Subscription node settings, client routing presets, and full kernel configurations are separate layers; changing one doesn’t necessarily affect the others.

Does a successful configuration test mean the proxy node will work?

A configuration test mainly checks whether the config can be loaded. It doesn’t prove the remote server is online or confirm that the user ID, transport path, and TLS settings match the server. This example has no remote proxy outbound. To test a real node, add a complete outbound and check the connection with an actual request.

If only domain-based routing fails, where should I start?

First check whether the app sends the inbound a domain or an already-resolved IP. Then review the matching scope of domain: and full:, followed by rule order and domainStrategy. Don’t change DNS, sniffing, and every outbound setting at once before confirming the destination information.

Download v2rayN