V2Ray JSON Config Explained: Inbounds and Outbounds, Plus Routing

Explore the client-generated config from log and DNS settings to inbounds, outbounds, and routing. Learn what each field does and how tags connect to routing rules.

At a glance

This guide is for users who have imported a subscription and want to understand the core config. Start with a JSON example containing a local SOCKS inbound, direct and block outbounds, then review logs, DNS, ports, tags, and rule order. By the end, you’ll know where to look when the core fails to start, a local port won’t connect, or a routing rule doesn’t match.

Client settings vs. core config

v2rayN is a desktop client for managing subscriptions, server entries, routing presets, and core processes. V2Ray or Xray is the program that reads the core config and handles connections. Client preferences and subscription group data aren’t the same as the runtime JSON passed to the core. When troubleshooting, find the file actually loaded at startup—don’t edit a file just because it ends in .json.

On Android, v2rayNG uses the Xray core, while v2flyNG uses the V2Fly core. This guide covers common V2Ray config structures; it doesn’t mean every field works across both cores. For example, REALITY is an Xray-related feature, so don’t assume its config can be used with V2Fly. Even when both support streamSettings, check the current core’s supported transport names and options.

App traffic Local inbound Rule matching Selected outbound Destination

This is the main path to follow when reading the config. DNS comes into play when a domain needs resolving, and logs record the process. The system proxy only affects apps configured to use it; having a SOCKS inbound doesn’t mean all app traffic reaches the core. If traffic isn’t reaching the inbound, changing routing rules usually won’t help.

Complete JSON example: One inbound, two outbounds, two rules

This teaching example has no remote proxy server. It provides SOCKS on 127.0.0.1:10808, blocks requests to the exact domain example.org and TCP traffic on port 25, and sends all other connections direct by default. Don’t use it as a replacement for an existing subscription config, or you’ll lose its proxy outbounds.

{
  "log": {
    "loglevel": "warning"
  },
  "dns": {
    "servers": [
      "1.1.1.1"
    ]
  },
  "inbounds": [
    {
      "tag": "socks-local",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {
        "domainStrategy": "UseIP"
      }
    },
    {
      "tag": "block",
      "protocol": "blackhole",
      "settings": {}
    }
  ],
  "routing": {
    "domainStrategy": "AsIs",
    "rules": [
      {
        "type": "field",
        "inboundTag": [
          "socks-local"
        ],
        "domain": [
          "full:example.org"
        ],
        "outboundTag": "block"
      },
      {
        "type": "field",
        "network": "tcp",
        "port": "25",
        "outboundTag": "block"
      }
    ]
  }
}

The order of top-level object keys usually doesn’t affect processing; they’re arranged here for readability. Array order, however, can affect behavior—especially the first item in outbounds and the order of routing.rules. Don’t reorder these arrays just to tidy up the file.

log: Use logs to tell startup errors from connection errors

log controls log output; it doesn’t select a server. This example sets only loglevel and doesn’t specify a log file. Where logs appear depends on how the core was started; the client may collect them in its own log panel. When troubleshooting, check the first error after startup, then look for new entries when you reproduce the issue.

Field or symptomWhat it meansWhat to check next
loglevel: warningWarnings and errors only, keeping routine output to a minimum.Temporarily switch to info for more connection details.
loglevel: debugProvides more debug detail, but logs grow faster.Reproduce the issue once, then restore the previous level. Remove sensitive information before sharing logs.
address already in useThe listening address or port is already in use by another process.Check whether another core instance is already listening on port 10808.
Config parsing or loading errorThe core hasn’t reached the stage where it can handle connections.Check field names, value types, and whether the current core supports the option.

If the core exits immediately after launch, fix the config or port conflict first—not DNS. If it runs normally but a specific request fails, check the destination, routing result, and connection error. Logs may contain visited domains, server addresses, and local paths; don’t publish the full runtime config or logs publicly.

dns: DNS servers and routing strategy are separate settings

dns.servers specifies the DNS servers used by the core’s built-in resolver. The 1.1.1.1 in this example is a standard DNS server address, typically queried over UDP port 53. No encrypted DNS is configured here, and queries don’t automatically go through a proxy. Verify that the resolver is reachable on your network.

Two fields with the same name are easy to confuse: routing.domainStrategy controls how domains are handled during route matching, while settings.domainStrategy in a freedom outbound controls how the destination is resolved when making a direct connection. They belong to different objects and have different values and effects.

During route matching

Location
routing.domainStrategy
Example value
AsIs
What it does
Doesn’t proactively resolve the destination domain for route matching.
Other common values
IPIfNonMatch、IPOnDemand

AsIs doesn’t mean DNS queries never happen during the connection.

During direct outbound connection

Location
freedom.settings.domainStrategy
Example value
UseIP
What it does
Resolves the destination domain to an IP using built-in DNS, then connects.
Resolver source
This example’s dns.servers

Don’t use the routing value IPIfNonMatch here.

IPIfNonMatch attempts to resolve a domain if no rule matches, then checks IP rules. IPOnDemand triggers a lookup when route matching needs an IP. Neither setting forces all traffic through a proxy. If your config has no IP rules, keep the strategy simple rather than adding extra DNS behavior just to explain a domain rule.

inbounds: Check which inbound the app connects to

inbounds is an array; each item describes an inbound that accepts connections. In this example, listen is set to the loopback address, port is the number 10808, and protocol is socks. Together, these fields specify where an app should connect and which protocol to use.

  1. Check the listening address: 127.0.0.1 is accessible only on the local machine. Changing it to 0.0.0.0 exposes the listener on all interfaces; don’t expose an unauthenticated inbound just to troubleshoot.
  2. Check the protocol: A SOCKS port isn’t an HTTP proxy port. Pointing an HTTP-only app to this example’s port 10808 won’t tell you whether the server is working.
  3. Check the actual port: 10808 is the value used in this guide, not a universal client default. In v2rayN, check the local listener options under Settings → Parameter Settings. The runtime config and logs are the final authority.
  4. Check UDP: udp: true lets this SOCKS inbound handle UDP, but the app must also use SOCKS UDP, and the outbound and network must support that traffic.

tag: socks-local is an internal inbound name, not a domain or subscription server name. It doesn’t change the listening protocol; it lets other config sections refer to this inbound. The first rule uses an array for inboundTag because a rule can match multiple inbound tags.

"inboundTag": [
  "socks-local"
]

If you change an inbound tag, update every rule that references it. Changing socks-local to local-socks on the inbound but leaving the original rule unchanged means that rule won’t match its traffic.

outbounds: Tags, protocols, and the default outbound

outbounds defines how a connection is sent onward. In this example, freedom means direct and blackhole means blocked; neither is a remote proxy protocol. A real subscription-generated config may include VMess or VLESS outbounds with server addresses, ports, user IDs, and transport settings.

ItemIn this exampleWhat to know
tagdirect、blockA name referenced by rules; spelling and capitalization must match exactly.
protocol: freedomDirect outboundConnects to the destination directly from the device running the core.
protocol: blackholeBlock outboundRejects matching traffic; it doesn’t automatically try another outbound.
VMess、VLESSNot configured in this exampleRequires valid server and authentication settings; changing only the protocol name won’t work.
streamSettingsNot needed in this exampleDescribes the transport and security layers; settings must match the server.

In this example, with no other default dispatch mechanism configured, connections that don’t match a routing rule use the first item in outbounds, so they go direct by default. Moving block to the first position changes the fallback behavior; renaming a tag doesn’t change the array order.

Takeaway: Check the default outbound before analyzing exceptions

Open the runtime config and note the tag on the first item in outbounds. Then check each routing.rules entry’s outboundTag. This is more reliable than searching for “proxy,” since the config generator determines tag names.

If the real config uses proxy as the proxy outbound tag, each routing outboundTag: proxy must point to a matching outbound. Adding a rule that targets proxy to this example won’t create a proxy connection. Keep and verify the valid proxy outbound generated by the client.

routing: Rules run top to bottom; understand AND vs. OR

routing.rules are generally checked from top to bottom. When a rule matches, its specified outbound is used, and later standard rules aren’t checked. Put specific rules before broader ones. A rule being present in the config doesn’t mean it will get a chance to run.

Different condition fields in a rule generally work as AND conditions, while multiple values in an array for the same field generally work as OR conditions. The first rule in this example requires both the inbound tag socks-local and the destination domain example.org to match before sending the request to block.

Requests sent to the local SOCKS inboundHow it matchesResult
Domain example.org, TCP 443Both the inbound and exact-domain conditions in the first rule match.block
Domain www.example.org, TCP 443Doesn’t match the exact domain or destination port 25.Default direct
Other domain, TCP 25The first rule doesn’t match; the second does.block
Other domain, TCP 443Neither rule matches.Default direct

This walkthrough assumes the app sends the domain name to the SOCKS inbound. If the app resolves it first and sends only the IP, the first domain rule may not work. Sniffing can identify domain information in some protocols, but it can’t always recover the original domain. Sniffing isn’t enabled here; test the rule with a request that explicitly uses the domain name.

Config checks and common questions

Change one variable at a time and note the logs before and after each change. First confirm the core starts, then check that the inbound is reachable, and finally verify rules and outbounds. Changing ports, DNS, tags, and transport settings all at once makes it hard to tell what caused a success or failure.

  1. Check syntax and types: Port 10808 is a number, udp is the boolean true, and inbounds and outbounds are arrays.
  2. Check every reference: Find each inboundTag and outboundTag, and confirm that a matching tag exists in the corresponding inbound or outbound.
  3. Confirm the app uses the inbound: When testing this example, specify the SOCKS address explicitly so browser system proxy settings don’t affect the result.
  4. Trace the routing: Start with the first rule and check the destination domain, destination port, network type, and inbound tag.
  5. Return to client-managed settings: Once you understand the behavior, make persistent changes using routing or config options supported by the client, then regenerate and test the runtime config.

If curl with SOCKS support is installed, run the command below while the core is using this example and the port is available. socks5h sends the destination domain to the SOCKS server for resolution. This example blocks that request with its exact-domain rule, so a failed request is the expected test result—not evidence of a remote proxy failure.

curl --proxy socks5h://127.0.0.1:10808 --connect-timeout 10 https://example.org/

Why does my JSON change revert after restarting v2rayN?

The client may regenerate the runtime file. Keep a record of your changes, then check the corresponding rule under Settings → Routing Settings, or use a custom config option explicitly supported by your current version. Don’t treat a temporary runtime file as a permanent source of settings.

Why does startup fail when outboundTag is set to proxy?

Find the item with the tag proxy in the outbounds array and check for spelling or capitalization differences. If it’s missing, restore a valid proxy outbound config rather than simply renaming freedom.

Why don’t my domain rules match after changing DNS to 1.1.1.1?

First check whether the app sends the domain name, then verify rule order and the full or domain prefix. Changing the DNS server won’t restore domain information the app has already discarded or change rule priority.

Can I replace the VMess protocol field with VLESS for an outbound?

No. The two protocols use different authentication fields and parameters, and their transport and security settings must also match the server. Reimport the correct subscription or connection details, then check which fields the running core supports.

For more on routing, see the routing reference. To check DNS settings in the client, follow the DNS setup steps in the guide. Once you understand the core config, compare it with the client interface to avoid mistaking subscriptions, inbounds, and routing for the same setting.

Download a clientv2rayN · v2rayNG