V2Ray config.json Explained: Inbounds, Outbounds, and Routing

Understand V2Ray config.json, from inbound listeners and outbound tags to routing order, so you can read the configuration generated by your client.

At a glance

For Mac users who have imported a subscription and want to understand config.json. Start with a complete example containing local inbounds, direct routing, and blocking, then review ports, outbound tags, rule order, and DNS. The example ports are specified for illustration and are not defaults for every client.

How config.json differs from v2rayN settings

config.json is the configuration file read by the core; v2rayN is a desktop client for managing servers, subscriptions, and runtime status. They operate at different levels: the client can translate its settings into a runtime configuration or manage a custom one. A file named config.json is not necessarily the file currently being read by the running process.

This article uses the traditional V2Ray JSON configuration format. Xray and V2Ray share many fields, but their protocol support and specific parameters are not identical. Before inspecting a configuration, check the client’s runtime logs to confirm which core is running, the configuration path, and any startup errors. Don’t paste protocol parameters intended for one core into another.

Application request Local inbound Route matching Target outbound Remote server

This flow also shows what the system proxy does: it directs requests from apps that honor the system proxy settings to a local inbound. Starting the core alone does not route traffic from every app through this path. Apps that ignore the system proxy need their own proxy settings or another explicit method of routing traffic.

Complete example: two inbounds and two outbounds

Here’s a configuration for understanding the structure on your own Mac. It provides SOCKS and HTTP entry points, connects directly by default, and blocks the example domain example.com and its subdomains. It does not configure a remote proxy server, so it is not a subscription-node configuration. Before testing, make sure the ports aren’t already in use by a running client.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "local-socks",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": false
      }
    },
    {
      "tag": "local-http",
      "listen": "127.0.0.1",
      "port": 10809,
      "protocol": "http",
      "settings": {}
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    },
    {
      "tag": "blocked",
      "protocol": "blackhole",
      "settings": {}
    }
  ],
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "domain": [
          "domain:example.com"
        ],
        "outboundTag": "blocked"
      },
      {
        "type": "field",
        "ip": [
          "10.0.0.0/8",
          "172.16.0.0/12",
          "192.168.0.0/16"
        ],
        "outboundTag": "direct"
      }
    ]
  }
}

At the top level, inbounds, outbounds, and rules are arrays. Each array can contain multiple objects inside square brackets, separated by commas; don’t add a comma after the last item. For compatibility with standard JSON tools, use double quotes, omit comments, and enter ports as numbers rather than quoted strings.

10808
Example SOCKS inbound port
10809
Example HTTP inbound port
2 rules
Routing rules checked in order

inbounds: Listening address, port, and inbound tag

listen specifies the network interface that accepts connections. The example uses 127.0.0.1, which allows connections only from the same Mac. Changing it to 0.0.0.0 listens on all IPv4 interfaces, but doesn’t guarantee access from other devices; firewall rules, network isolation, and authentication still matter. For local use, keeping the listener on loopback makes access easier to control.

SOCKS inbound

Tag
local-socks
Listen address
127.0.0.1:10808
Protocol
socks
Authentication
noauth; local use only
UDP
false; disabled in this example

Set the app to use a SOCKS proxy; don’t enter this port as an HTTP proxy port.

HTTP inbound

Tag
local-http
Listen address
127.0.0.1:10809
Protocol
http
Setting
settings: {}

Accepts HTTP proxy requests and CONNECT tunnels; the inbound itself does not provide TLS-encrypted listening.

On macOS, go to System Settings → Network → your current connection → Details → Proxies to check the address and port. For the HTTP inbound in this example, set Web Proxy and Secure Web Proxy to 127.0.0.1:10809 as needed. For the SOCKS inbound, use port 10808. During troubleshooting, enable only the method you need to avoid conflicts between settings.

tag is an internal identifier in the core configuration, not a server address or a name that any app can recognize. In routing rules, inboundTag can distinguish where a request came from. For example, one rule can handle local-http and another local-socks, letting you set different policies for each entry point.

outbounds: Protocol determines behavior; tag links to routing

In this example, freedom is a direct outbound that connects to the destination from the current device; blackhole blocks requests. A remote proxy outbound also needs the server address, port, and identity parameters required by its protocol, as well as any transport settings. Changing only tag to proxy won’t turn a direct connection into a proxy.

Field or protocolMeaningWhat to check
tagInternal name of the outboundMust exactly match the rule’s outboundTag
protocolSelects the outbound implementationConfirm the running core supports the protocol
settingsParameters specific to the protocolDifferent protocols don’t share the same configuration structure
streamSettingsTransport and transport security settingsMatch each setting to the server’s requirements
freedomConnect directly to the destinationUses the device’s actual network connection
blackholeBlocks matching requestsMake sure legitimate traffic isn’t matched by mistake

In a typical configuration, requests that don’t match a routing rule use the first outbound in the outbounds array. Here, direct comes first, so unmatched requests connect directly. This order is especially important when reading a client-generated configuration: having a proxy outbound in the file doesn’t mean unmatched traffic will use it.

Takeaway: Check the default outbound before reviewing specific rules

If you’re troubleshooting why traffic still connects directly, check the first outbound, then verify the matching rule’s outboundTag. Don’t infer the actual route from a node name or a color in the interface.

routing: First-match order, combined conditions, and domain handling

routing.rules are checked from top to bottom. When a rule matches, its specified outbound is used and no later rules are checked, even if they’re more specific. Put explicit exceptions before broader rules. Array order is part of the configuration’s behavior; changing it can change the route without causing a syntax error.

  1. Request to www.example.com: The first rule’s domain:example.com matches the root domain and its subdomains, so the request goes to blocked.
  2. Request to 192.168.1.20: The first rule doesn’t match; the second matches the private IP range and sends the request to direct.
  3. Destination doesn’t meet either condition: No rule matches, so the first outbound, direct, is used.

Within a rule, different condition fields generally all need to match. Multiple values for the same field are treated as alternatives. For example, the rule below blocks only TCP requests from the HTTP inbound to port 25. A request to the same port from the SOCKS inbound doesn’t meet all the conditions.

{
  "type": "field",
  "inboundTag": ["local-http"],
  "network": "tcp",
  "port": "25",
  "outboundTag": "blocked"
}

Add this object to the existing routing.rules array and place it where needed; it isn’t a complete configuration. Note that field types differ: inbound port is a number, while a routing rule’s port is a string representing a port or range, such as "80,443".

For conditions such as geosite: or geoip:, make sure the core can read the required data files and that the requested entries exist. This article uses explicit domain names and CIDR ranges to distinguish rule behavior from external data dependencies. Don’t confuse routing.domainStrategy with similarly named DNS strategy options in the direct outbound.

DNS and sniffing: What destinations can rules see?

Whether a domain rule matches depends on the destination information received by the inbound. HTTP proxy requests and CONNECT usually include a domain name; SOCKS requests can include either a domain name or an IP address already resolved by the app. If the app resolves the name locally, the core may receive only the IP address, so don’t assume it can always see the original domain.

curl --proxy socks5h://127.0.0.1:10808 --connect-timeout 5 https://example.com/
curl --proxy http://127.0.0.1:10809 --connect-timeout 5 https://example.com/

The first command uses socks5h so curl passes the domain to the SOCKS proxy for resolution. With socks5, curl resolves the destination locally. The domain request is expected to be blocked by the rules in this example, so a failed command is the intended result; the exact error depends on where the connection fails. Here, 5 is the connection timeout in seconds, not a performance measurement.

A safe editing sequence and common troubleshooting checks

Back up the file first, confirm which file is active, and only then make changes. v2rayN may regenerate its runtime configuration when you switch nodes, update a subscription, or restart the core. Editing a generated file temporarily can help test an issue, but it’s not a reliable way to keep rules long term. Save persistent changes in the relevant client settings or a managed custom configuration.

  1. Confirm the core and file path. Check the startup logs for the core name, loaded file, and error lines. If the path contains spaces, put it in quotes in terminal commands.
  2. Check the JSON structure. Run the command below in macOS Terminal. It parses the JSON but doesn’t check whether the core supports every field.
  3. Check the listening ports. Make sure 10808 and 10809 aren’t in use by another process, and confirm they match the app settings.
  4. Check tag references. Verify that each outboundTag points to an existing outbound and each inboundTag matches an inbound tag.
  5. Reload and test. Restart or reload the core from the client, confirm it starts successfully, then test destinations known to match and not match.
plutil -lint -s -r -- config.json
lsof -nP -iTCP:10808 -sTCP:LISTEN
lsof -nP -iTCP:10809 -sTCP:LISTEN

plutil is used here to check JSON syntax. Even if the syntax is valid, a misspelled protocol, missing required parameters, or an incompatible core can still prevent startup. If the listener check returns no output, that alone doesn’t prove the configuration has a syntax error; also check whether the process started and whether it loaded a different file.

SymptomCheck firstNext step
Core exits immediately on startupJSON syntax, field compatibility, required parametersRead the first actionable error in the startup log
Address already in useInbound port and duplicate processesIdentify the process listening on the port, then stop it or choose another port
Rule doesn’t matchRule order, destination format, inbound tagReproduce with a fixed proxy command and compare the logs
Changes disappear after restartCheck whether you edited a generated fileMove the settings into the client’s managed configuration
Works in the browser but not in other appsCheck whether the app honors the system proxyCheck the app’s proxy type and port settings

Takeaway: Change one layer at a time

First confirm the inbound can accept requests, then make sure the outbound works, and only then adjust routing. Temporarily increasing log verbosity can help with troubleshooting; restore the original level afterward. Before sharing logs or configuration, remove subscription URLs, credentials, and private destination details.

Download v2rayNChoose a client for your platform