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

Explore a complete config.json, with a field-by-field guide to inbounds, outbounds, routing rules, and how they reference each other.

At a glance

This guide is for users who have imported a configuration into a client and want to understand what the core actually runs. Start with a complete JSON example containing only local inbounds, direct and reject outbounds, then follow the tag references, routing order, and DNS behavior. The example has no remote proxy server. By the end, you’ll be able to trace where requests enter, which rule handles them, and what to check after making changes.

1. Client Settings vs. Core Configuration

v2rayN, v2rayNG, and v2flyNG are clients, not the core itself. Clients manage subscriptions, nodes, and the user interface, then translate relevant settings into a configuration the core can read. v2rayNG uses the Xray core, while v2flyNG uses the v2fly core. For v2rayN, check which core is currently selected to understand its runtime configuration. Supported fields can vary across cores and versions.

This guide covers common fields in the traditional V2Ray JSON configuration format. When inspecting a real setup, first determine whether you’re looking at a core config or a client settings file. Subscription groups, window options, and update preferences saved in the interface may not appear in the core’s config.json. Conversely, local ports in the runtime config may be generated automatically by the client.

App sends a proxy request Local inbound accepts it Routing rules are checked Selected outbound handles it Connection to the destination is made

This flow describes apps that explicitly use a local proxy. Starting the core alone does not route every program through a local port. System proxy settings, in-app proxy settings, and TUN mode work differently. This guide focuses on SOCKS or HTTP inbounds to avoid confusing proxy access issues with routing errors.

2. A Complete config.json Example

Here’s a complete config for exploring the structure. It listens on two loopback ports, rejects requests for the exact domain example.org, and sends all other traffic directly. example.org is a documentation example domain, not a working node. There are no remote VMess or VLESS outbounds, so this config does not forward traffic to a proxy server.

10808
SOCKS inbound port in this example
10809
HTTP inbound port in this example
3 rules
Routing rules checked in order
{
  "log": {
    "loglevel": "warning"
  },
  "dns": {
    "servers": [
      "1.1.1.1"
    ]
  },
  "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.org"
        ],
        "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"
      },
      {
        "type": "field",
        "inboundTag": [
          "socks-in",
          "http-in"
        ],
        "network": "tcp,udp",
        "outboundTag": "direct"
      }
    ]
  }
}

In standard JSON, keys and strings must use double quotes, and the final item in an array or object must not have a trailing comma. This code block has no comments and can be saved as a standalone example. To confirm that a specific core can load it, run that core’s config check command.

3. inbounds: Listen Address, Port, and Inbound Tags

inbounds is an array because a single core process can accept connections through multiple inbounds. Each inbound’s protocol determines what it accepts, and the fields inside settings vary by protocol. In this example, udp: true on the SOCKS inbound enables SOCKS UDP forwarding; it does not automatically capture UDP traffic from every app.

SOCKS inbound

tag
socks-in
listen
127.0.0.1
port
10808
protocol
socks
auth
noauth

Apps must explicitly connect to this SOCKS inbound; listening on a loopback address limits access to this device.

HTTP inbound

tag
http-in
listen
127.0.0.1
port
10809
protocol
http
settings
Empty object

Handles HTTP proxy requests and CONNECT tunnels; it does not host a website on your device.

listen and port together define where the inbound accepts connections. Changing 127.0.0.1 to 0.0.0.0 expands access, potentially letting other devices connect to the proxy port. This example also uses SOCKS without authentication, so don’t widen the listening address just for convenience. If you need LAN access, plan for authentication and firewall rules too.

tag is an identifier used for references within the config. It is not a server address and does not create a new port. The inboundTag in the third routing rule refers to the two strings defined here. If you rename an inbound tag, update the corresponding rule as well, keeping the capitalization and hyphens identical.

4. outbounds: Protocols and Default Route

outbounds define how connections are handled when they leave the core. freedom connects directly to the destination from the local machine, while blackhole rejects traffic. Here, direct and block are arbitrary tag names. The protocol determines what an outbound does—not what its tag happens to be called.

PositionField or valueWhat it does
First outboundtag: directAvailable for routing rules and serves as the default outbound when no rule matches in this example.
Direct protocolprotocol: freedomConnects directly to the destination address.
Second outboundtag: blockReferenced by the exact-domain reject rule.
Reject protocolprotocol: blackholeDoes not forward the request to the destination server.

With the standard routing behavior used here, the first outbound is used when no rule matches. So changing the order of the outbounds array is not just a cosmetic rearrangement—it can change the default route. This example also adds a final direct rule for the two known inbounds, making the intended default behavior easier to follow.

Key takeaway: Check tag references before changing outbounds

Search for every outboundTag and make sure it matches the tag of an existing outbound. Then check that the first outbound is the intended fallback for unmatched traffic. A misspelled tag won’t automatically fall back to a protocol with a similar name.

To use a remote node, add a proxy outbound with valid settings and a protocol supported by the current core, then point the relevant rules to it. VMess and VLESS use different user fields, transport settings, and security options; changing only the protocol string does not convert one into the other. Use a config generated by importing a trusted subscription as a reference, and verify the server, port, user ID, and transport parameters individually.

5. routing: Rule Order and Match Conditions

routing.rules is an ordered array. For each request, the core checks rules from top to bottom and uses the outbound specified by the first matching rule; it does not run every rule in sequence. A broad rule near the top can prevent more specific rules later in the list from ever matching.

  1. Match the exact domain first. full:example.org matches only that exact domain, not its subdomains. This domain rule can only match when the request includes the domain or the core otherwise has access to it.
  2. Next, match the specified IP ranges. This example lists the loopback range and three common private IPv4 ranges; matching traffic goes to direct. This is not a complete list of special-purpose addresses and does not cover private or link-local IPv6 ranges.
  3. Finally, match the two local inbounds. Requests from either socks-in or http-in that use TCP or UDP are sent to direct.

Within a rule, the two inbounds in the inboundTag array mean “match either one.” Different condition fields in the same rule generally all need to match. So the third rule means “from either of these inbounds and using a matching network type”—not “from either inbound, or using TCP.”

domainStrategy: "AsIs" means routing does not proactively resolve domains to IPs for IP-based rules. If an app passes a domain to the core, the second rule for private IP ranges will not match just because that domain might resolve to a private address. If the app passes an IP, the range can be matched directly. This setting controls processing during routing; it does not mean DNS queries never happen during a connection.

6. dns and log: Keep DNS Resolution Separate from Routing

The example’s dns.servers setting uses 1.1.1.1. This configures the DNS server for the core’s built-in DNS; it does not automatically replace DNS settings in the operating system or browser. Adding this field alone also does not guarantee that every DNS request will go through a proxy. Which components use built-in DNS, and how resolution traffic is routed, depends on the core and its policies.

log.loglevel is set to warning, which is a good starting point for monitoring warnings and errors. To troubleshoot routing, you can temporarily switch to info; use debug only when you need more detail. The log level is not a log file path, and output formats can vary by version. Before sharing logs, remove sensitive information such as subscription URLs, user IDs, server addresses, and destination details.

7. Change, Validate, and Roll Back in Order

A client may rebuild its runtime config when you switch nodes, update a subscription, or restart it. Direct edits to an automatically generated file may be overwritten the next time one of those actions occurs. First decide whether you’re inspecting the generated config or using a custom one, then choose whether to make changes in the client interface or through its custom config option.

  1. Keep a rollback copy. Save the original config and note the current core, config file location, and listening ports. Don’t put subscription URLs in public notes.
  2. Change one thing at a time. For example, add a single domain rule without also changing DNS, ports, or the remote node. This makes it easier to identify what caused a change.
  3. Check the syntax and validate the config first. A standard JSON check catches issues with commas and quotes. A core-specific check can also catch unsupported protocols, incorrect field types, and some reference problems.
  4. Then start the core and check its listeners. If an address is already in use, check whether a client is using ports 10808 or 10809. Don’t run two copies of the same config on the same ports.
  5. Finally, verify the actual traffic path. Confirm that the app reaches the local inbound, then check whether the request uses the expected outbound. If something fails, restore the last working config.

Here are common ways to check a config from the command line. Choose one; don’t run commands for different cores interchangeably. Adjust the executable name and command syntax for your system. If your version doesn’t recognize an option, check its help output. A successful check only means the config was accepted; it doesn’t prove that a remote server is reachable or that routing works as intended.

v2ray test -c config.json

xray run -test -config config.json

On a desktop system with curl installed, you can test this example using explicit proxy requests. The first command passes the domain through the SOCKS proxy, which is useful for checking the full:example.org reject rule. The second uses the HTTP inbound to request another example domain and should go direct. Run both commands only after the core has started successfully and the ports match this example.

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

curl --proxy http://127.0.0.1:10809 --connect-timeout 10 https://example.com/

The first request is expected to fail, but the error alone can’t tell you whether the reject rule matched, the inbound failed to start, or the network is down. The second request’s success depends on your network. Check command output alongside the core logs; a page failing to load does not, by itself, prove that a particular rule matched.

8. Common Questions About Reading the Config

Why can’t I save an imported subscription directly as config.json?

A subscription provides node data for the client to read and update; it is not necessarily a complete config that the core can load directly. It may contain share links or an encoded list, and typically does not define local inbounds. Import it into the client first, then inspect the runtime config the client generates. Changing the file extension alone won’t make it usable.

Can I swap the SOCKS and HTTP ports?

Port numbers are not tied to a protocol and can be changed in the config, but you must update the app too. Entering this guide’s port 10808 as an HTTP proxy won’t make it an HTTP inbound just because the port accepts a connection. Check protocol first, then verify the port and the proxy type used by the app.

Why is traffic still going direct when a reject rule is in place?

Check these four things in order: Is the app actually using the proxy inbound? Did the core receive a domain or an IP? Did an earlier rule already match? Is the core running the config you just edited? Also, an exact-domain rule does not cover subdomains. Don’t start by switching nodes to work around what may be a routing rule issue.

Can I replace v2rayNG’s runtime config with this example?

No. The Android client generates runtime settings that also depend on VPN integration, DNS, and per-app routing. This guide demonstrates only the basic structure for local SOCKS, HTTP, and routing. Make changes through the relevant client settings and keep a recoverable copy. For full proxy functionality, you’ll also need a valid remote outbound.

Download the clientChoose your OS and architecture