Oct 15, 2026 · Configuration · 12 min read

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

Learn how V2Ray config.json is structured, from inbound listeners and outbound proxies to routing rules. Includes JSON examples to help you understand client-generated configurations.

At a glance

For readers who have imported nodes but aren’t sure how to read the configuration generated by their client. Start by testing the structure with a local port, 10808, and a direct outbound, then compare the VMess outbound and domain-routing examples to see how the fields fit together. You’ll learn to distinguish local listeners, remote connections, rule matching, and client settings—and why a subscription link isn’t a complete config.json.

1. config.json Structure and Request Flow

config.json is one possible filename for a core configuration file, not a fixed storage location. Clients such as v2rayN may generate temporary runtime configurations, and manual changes to those files can be overwritten the next time you switch nodes. First check whether you’re running Xray or V2Fly, then find the configuration file actually loaded and review the startup logs.

App request Inbound listener Route match Outbound connection Destination service

inbounds and outbounds are arrays, so each can contain multiple entries. routing is an object; its rules property is the rules array. A tag is an internal reference name, not a server address. Rules use outboundTag to point to an outbound.

Here’s a complete starter configuration for local direct-connection testing only. It exposes a SOCKS service on the loopback address, and sends requests not matched by a rule to the first outbound, direct. This configuration has no remote proxy node and does not change your system proxy settings.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "local-socks",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    },
    {
      "tag": "block",
      "protocol": "blackhole",
      "settings": {}
    }
  ],
  "routing": {
    "domainStrategy": "AsIs",
    "rules": []
  }
}

2. inbounds: Listen Address, Port, and Local Protocol

An inbound determines how an application passes traffic to the core. In the example above, 127.0.0.1:10808 is a local SOCKS listener, not the port of a remote node. Browsers, command-line tools, and other SOCKS-compatible apps must be configured to use this listener before their traffic reaches the core.

Local SOCKS Inbound

tag
local-socks
listen
127.0.0.1
port
10808
protocol
socks

Accepts connections from this machine only; the app must use the SOCKS protocol.

Optional HTTP Inbound

tag
local-http
listen
127.0.0.1
port
10809
protocol
http

Must be added separately under inbounds; it isn’t provided automatically by the SOCKS inbound.

10808 and 10809 are example values used in this article, not fixed defaults across all versions. Pointing an app’s HTTP proxy setting at a SOCKS port causes a protocol mismatch. If your client offers a mixed-protocol listener, check which protocols it actually supports rather than guessing from the port number.

Use the command below to check whether an app can make an HTTPS request through the example listener. Use curl.exe on Windows and curl on macOS or Linux. socks5h sends the destination hostname to the proxy for handling; where it’s ultimately resolved depends on the configuration.

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

3. outbounds: Remote Nodes and Transport Settings

An outbound determines where the core connects to reach a destination. freedom means a direct connection, blackhole blocks traffic, and vmess requires a compatible remote server. An inbound can use SOCKS while an outbound uses VMess: they serve different points in the connection path, so their names don’t need to match.

Here’s an example to replace the top-level outbounds section. 192.0.2.10 is a documentation-only address, and the UUID and domain are illustrative too, so none of these values can be used to connect. For real use, get the address, port, user ID, transport, and TLS settings from an authorized server configuration. Replacing just one field won’t work.

{
  "outbounds": [
    {
      "tag": "proxy",
      "protocol": "vmess",
      "settings": {
        "vnext": [
          {
            "address": "192.0.2.10",
            "port": 443,
            "users": [
              {
                "id": "b43b4b90-a710-4ee4-883d-b8c574099d11",
                "alterId": 0,
                "security": "auto"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "tls",
        "tlsSettings": {
          "serverName": "proxy.example.net"
        }
      }
    },
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    },
    {
      "tag": "block",
      "protocol": "blackhole",
      "settings": {}
    }
  ]
}
FieldExample valueWhat to check
settings.vnext[].address192.0.2.10Remote server address, not the local listen address.
settings.vnext[].port443Port that accepts connections on the server, not 10808.
users[].securityautoVMess user encryption setting, not a TLS switch.
streamSettings.networktcpThis example uses TCP; other transports require their corresponding settings.
streamSettings.securitytlsTransport-layer security method; distinct from the protocol’s security setting.
tlsSettings.serverNameproxy.example.netTLS server name, which must match the server deployment and certificate.

Takeaway: Check the Protocol, Then the Transport

Changing VMess to VLESS takes more than changing protocol. Check settings against your core’s configuration spec, then review streamSettings. For TLS failures, check the domain, certificate, and system time; disabling certificate verification is not a routine fix.

v2rayNG uses the Xray core, while v2flyNG uses the V2Fly core. Which cores are available in v2rayN depends on what’s installed and selected. Similar top-level structures don’t guarantee full compatibility with extension fields, especially for specific VLESS security combinations. The TCP fields in this article illustrate common configuration patterns; check field names and compatibility against your installed core version.

4. routing: Rule Order, Tag References, and the Default Outbound

Routing rules don’t take effect just because you’ve grouped a few domains; the core checks rules in order. Usually, the first matching rule determines the outbound. Different conditions within one rule must all be met, while multiple values in an array for the same condition are alternatives. outboundTag must exactly match the tag of an existing outbound.

This example depends on the proxy, direct, and block outbounds from the previous section. It blocks a sample domain first, sends private IP addresses directly, and routes everything else through the proxy. Both domains are examples for illustrating rules, not recommendations for where to connect.

{
  "routing": {
    "domainStrategy": "AsIs",
    "rules": [
      {
        "type": "field",
        "domain": ["full:blocked.example.org"],
        "outboundTag": "block"
      },
      {
        "type": "field",
        "ip": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "domain": ["domain:example.org"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "network": "tcp,udp",
        "outboundTag": "proxy"
      }
    ]
  }
}
  1. full:blocked.example.org matches only that exact domain. Although it’s also a subdomain of example.org, the earlier blocking rule takes precedence.
  2. A private-network rule applies to requests whose destination IP falls within the specified ranges. It isn’t a complete list of special-use addresses and doesn’t automatically cover local IPv6 addresses.
  3. domain:example.org matches that domain and its subdomains; it doesn’t match arbitrary strings that happen to contain it.
  4. The final network: "tcp,udp" rule is the fallback in this example. Put it after specific rules, or it may catch traffic before those rules can take effect.

If no rule matches, the core typically uses the first outbound in the outbounds array. The first outbound in section one is direct; in section three, it’s changed to proxy. Simply reordering the array can therefore change the default behavior. Having a proxy node in the array doesn’t mean all requests use it.

5. dns, Logging, and the Limits of Client-Generated Configurations

routing.domainStrategy controls how routing handles domains during rule matching. It isn’t a DNS server address or a complete DNS-routing policy. AsIs tries to match using the original destination; IPIfNonMatch tries resolving the domain and then checks IP rules if no domain rule matches; IPOnDemand may trigger resolution when checking IP rules.

This example only adds built-in DNS settings. It won’t automatically change the operating system’s DNS settings or guarantee that every app’s queries pass through the core. If an app resolves a domain locally before connecting, the core may receive only an IP address. If the proxy protocol passes the domain through, the outbound and server may determine where it’s resolved.

{
  "dns": {
    "servers": ["localhost"]
  }
}

When reading a configuration exported by a client, start with inbounds[].port, outbounds[].tag, and routing.rules, then trace DNS and transport settings. Don’t delete unfamiliar management listeners or extra fields without checking: they may support client statistics, control interfaces, or runtime features. Before sharing logs, remove user IDs, subscription URLs, and server details.

6. Configuration Checks and Common Issues

Save a restorable copy before making changes, and change one area at a time. First validate the JSON and core fields, then check the listening port, and finally test the remote connection and rule matching. The commands below are for newer cores that provide the relevant command-line interface. Check your installed program’s help output for supported options, and use the path to the file the client actually loads when it manages the configuration.

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

A successful test command doesn’t mean the node is usable. It mainly checks whether the current core accepts the configuration; it doesn’t replace tests for remote authentication, the TLS handshake, or access to the destination. If the core starts but an app still can’t connect, narrow down the issue in this order: local listener, remote outbound, then routing result.

Getting “address already in use”? Which port should you change?

First check which inbound port is already in use. If the log points to 10808, stop the duplicate core process or change the port under inbounds to 10810, then update the app’s proxy address to match. Don’t change the remote node’s port 443 for this.

The core starts, but curl still can’t connect?

First, use the direct-connection configuration and socks5h test command from section one to check the listener. If the connection to the local port fails, check the listen address, port, and proxy protocol. If traffic reaches the core but then times out, check the remote address, authentication, and TLS logs.

Why is traffic still using the proxy after I added a direct rule?

Check rule order, the spelling of outboundTag, and whether the core receives a domain or an IP address. Make sure the catch-all rule isn’t placed first, then confirm the running process loaded the file you edited rather than a newly generated client config.

Why did my manual JSON changes disappear after a subscription update?

The client may regenerate its runtime configuration from the node and global settings. For persistent changes, use the client’s routing, inbound, or custom configuration options. If you choose to manage a complete JSON file yourself, make sure you know its path and how the core is started so the two configurations don’t overwrite each other.

Takeaway: Test One Change with One Request

Keep the local listener at 127.0.0.1:10808 and use the same test destination, then check direct connections, proxy connections, and routing separately. Check the logs after each reload; this makes it easier to locate the problem than changing the port, DNS, and transport settings all at once.

Download clients