JSON / INBOUNDS / OUTBOUNDS / ROUTING / DNS / POLICY

V2Ray Configuration Reference

Understand each field and rule execution order, section by section.

Setting up a subscription for the first time, choosing a server, and enabling the system proxy? Start with the quick-start guide. This page focuses on what each section of the JSON generated by your client does, which fields work together, and where to look when something goes wrong. Once you can connect, use this reference to fine-tune routing and DNS with a clearer picture of what is happening.

JSON structure overview: follow the data flow

A V2Ray config is more than a set of unrelated switches. An app connects to an inbound; the core checks the destination and selects a route, then passes the connection to an outbound. DNS provides answers where needed, while policy and logs govern connection handling and help you investigate problems. Following this data flow is more useful than memorizing fields one by one. The same failed request could mean the app never reached the inbound or the outbound could not complete a handshake—those call for different fixes.

Top-level object and field placement

The top level must be a single JSON object. inbounds and outbounds are arrays and can each define multiple entry points and exits; routing, dns, policy and log are usually objects. Do not put an inbound's listen field in an outbound, or put routing rules directly at the top level. An editor recognizing the brackets only means the structure might parse; it does not confirm that fields are in the right place.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    }
  ],
  "routing": {
    "domainStrategy": "AsIs",
    "rules": []
  }
}

The structure snippet above shows how fields relate, but it has no local listener and cannot accept browser connections as-is. Empty arrays are valid JSON, but what they do at runtime depends on the core's defaults. When reviewing someone else's snippet, first find out which field it replaces. Then decide whether to replace the whole array or add just one object. Appending two complete objects produces invalid JSON.

FieldRoleWhat to check
inboundsAccepts connections from local appsAddress, port and protocol
outboundsEstablishes subsequent connectionsTag, protocol and remote settings
routingRoutes connections to an outboundRule order and tag references
dnsResolves domain names in the coreResolvers and query strategy
policyConnection management and stats togglesLevels and stats dependencies

Valid syntax does not guarantee a valid configuration

For compatibility across tools, this page uses strict JSON throughout: keys and strings use double quotes, the last item in an array has no trailing comma, and explanatory text stays outside code blocks. Some parsers allow comments, but that does not mean subscription processors or other cores will accept them. Use true or false for booleans, not quoted strings. Do not swap numbers and strings based on appearance alone; check what the relevant core expects for each field.

Do not repeat keys within the same object. For example, if you define routing twice, different parsers may overwrite the first entry or reject the config outright. It may look like both sets of rules are present, but only one might take effect. When merging configs, put all rules in the same rules array. Array order matters too: the first outbound may affect the default exit, and an earlier routing rule may catch a connection first. Avoid reordering arrays with an automatic formatter.

Tags connect references across sections. Define an outbound with the direct tag before a routing rule can refer to it with outboundTag; spelling and capitalization must match. Give each inbound and outbound a stable, unique tag so routing and logs can identify it consistently. A tag is just a name: calling one proxy does not give it proxy behavior. That depends on the object's protocol and settings.

Keep the original config before editing

In a GUI client, subscription data, UI settings and runtime configuration are usually stored separately. Keep a recoverable client backup, then check the config actually generated for the current run. Editing a temporary file while the core is running does not mean the change will survive the next launch. Prefer saving changes in the relevant client settings page; use custom config only through the loading method the client supports.

Before each edit, write down the outcome you expect, such as “send this domain through the direct outbound,” rather than “improve the network.” Change only the section relevant to that goal, reload, and test the same action. This helps distinguish syntax errors, unsupported fields and rules that do not match. If you have not established a first connection yet, return to the main tutorial and verify the basics before adding complex rules.

Clients and cores: identify the config dialect first

V2Ray is often used as an umbrella term for an ecosystem, but the client UI is not the core that actually handles connections. v2rayN manages subscriptions, config selection and runtime controls on desktop; the core parses the config, connects and applies routing. Similar-looking menus do not guarantee identical support under the hood. Before looking up a field, check which core is running and whether the client converts its UI settings into a different format.

What V2Fly and Xray share—and where they differ

V2Fly and Xray in the Project V ecosystem share some history and many familiar JSON structures. However, protocol implementations, transport settings, extra features and field changes do not always move in sync. The examples on this page explain concepts using a common legacy JSON format; they do not promise that every extension field can be copied between cores. For VLESS, XTLS, REALITY and similar settings, verify support for the exact core and protocol combination you use.

On Android, v2rayNG is primarily used with the Xray core, while v2flyNG is an alternative client for the V2Fly core. On desktop, start with v2rayN, but choose a core that matches your configuration. Successfully importing a link does not mean the client can run every parameter in it. Import parsing, config generation and core startup are three separate steps to verify.

ClientTypical useWhat to check
v2rayNDesktop subscription and runtime managementActive core, generated config and system proxy
v2rayNGAndroid connection managementXray settings, system connection permission and app scope
v2flyNGV2Fly option for AndroidV2Fly fields and protocol compatibility

GUI settings are not a mirror of the raw JSON

A client may combine several fields behind one dropdown, or hide tags and ports it manages automatically. For example, a routing mode in the UI may represent a whole set of rules, not a top-level key called “mode.” Switching modes after editing the generated file may overwrite your changes. When reading a config, distinguish values from the current server, global settings, and fields the client fills in at startup.

Custom configs may not support every feature managed by a client. Some import methods run a complete config; others treat it as a single server entry. The local port used by the system proxy, the listener the client uses for status checks, and tags needed for stats may all follow additional conventions. If the UI says the client is running but apps cannot connect, check the actual listening port before assuming the remote server is down.

{
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    }
  ]
}

This outbound defines a direct connection. It is useful for explaining common fields, but it is not a complete remote connection config. It also illustrates how to check compatibility: identify the protocol, inspect its settings, then check the transport fields. Changing only the protocol name while keeping the old protocol's settings will usually fail. Check each protocol's server, authentication and transport fields together.

Check feature support before migrating configs

Before switching cores, list the protocols, transports, routing databases, DNS policies and inbound features actually in use. Confirm that the new core supports them, then test a single config on a small scale. Do not replace the whole config and judge success solely by whether the UI shows the core as running. The core may start while a less common route, UDP use case or transport behavior still differs from the original.

Routing databases are another compatibility boundary. A config that references geosite:cn still requires the active core to find the relevant resource and the file to contain that category. The directory a client uses to update rule data may differ from the directory another core searches. When migrating manually, check the resource location as well as the JSON; do not rename and mix data files in different formats.

When troubleshooting, note four things: client name, active core, config source and field named in the error. These are usually more useful than a screenshot of the home screen. To choose an installer, visit the client downloads page. For help deciding between the two core branches, see Xray vs. V2Fly core differences. Switching clients is not a universal fix; first identify the specific compatibility issue it would solve.

Inbounds: define how apps connect

An inbound determines how an app hands a connection to the core. Common local entry points are SOCKS and HTTP proxies, which apps must reach using the correct protocol and port. An inbound port is not a remote server port, and listening locally does not mean the remote connection succeeded. First confirm the app reaches the inbound. If the request never enters the core, changing outbound authentication will not help.

Check local address, port and protocol together

{
  "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": {}
    }
  ]
}

This config creates two listeners bound only to the local loopback address. SOCKS apps use 10808; HTTP proxy apps use 10809. These ports are not interchangeable aliases. They are example values, not a promise that your client uses the same ports. If the UI shows different values, follow the active runtime config and update the app's proxy address to match.

With listen set to 127.0.0.1, other devices cannot reach the listener over your LAN. Binding to a wildcard address exposes it beyond the local machine, so account for authentication, the system firewall and your network's trust boundary. The SOCKS example above does not require authentication and is suitable only when access is explicitly limited to the local machine. Do not expose it as a public shared proxy without changes.

A UDP toggle does not guarantee end-to-end UDP

The udp setting in SOCKS allows the inbound to handle applicable UDP requests, but successful communication also depends on whether the app uses SOCKS UDP, the outbound protocol supports it and the remote network allows it. An HTTP proxy listener usually handles HTTP requests and tunnels; it is not a general-purpose UDP entry point. If a browser works but a real-time app does not, first check which transport each one uses.

Likewise, the system proxy advertises a proxy entry point to apps that honor system settings; it does not capture all traffic automatically. Some programs use their own proxy settings, while others connect directly. TUN is a different capture method involving a virtual network interface, permissions, routing and DNS. Adding a SOCKS inbound alone cannot replace it. To find out what is covered, trace how the specific app's traffic enters instead of relying on the tray icon.

Sniffing can reveal domain clues, but it is not universal

{
  "inbounds": [
    {
      "tag": "socks-in",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      },
      "sniffing": {
        "enabled": true,
        "destOverride": ["http", "tls"]
      }
    }
  ]
}

Sniffing attempts to extract protocol or domain information from incoming traffic, helping the core handle connections that provide only a destination IP. It is not decryption and cannot recover a domain from every connection. Some handshakes, encrypted metadata and non-HTTP or non-TLS traffic limit what can be identified. After enabling it, check how the active core treats overridden targets. A domain clue for routing is not necessarily the same as the actual connection destination.

Stable inbound tags also make routing by entry point easier. For example, a separate test inbound can use a routing rule's inboundTag to affect only connections arriving through it. Rules still run in order: an earlier, broader match may prevent the inbound-specific rule from being reached. Choose a tag when you add the inbound to avoid renaming it later and missing a routing reference.

To verify an inbound, use an app with explicit manual proxy support, set its local address and port, and check whether a request appears in the logs. If there is no entry, check the app settings, listener address and firewall. If a request appears and then errors, move on to the outbound and name resolution. If only LAN devices cannot connect, check the bind address and access scope separately. Keeping these cases distinct avoids unrelated config changes.

Outbounds: distinguish connection type from tag

An outbound determines how the core reaches the next destination after receiving a connection. It can connect directly, use a remote protocol, or explicitly block the request. Inbounds handle how apps get in; outbounds handle how the core gets out. They do not need to use the same protocol. An app can connect through a SOCKS inbound and then be routed to different outbound types, so a local SOCKS port does not imply SOCKS is used at the remote end.

Start with direct and block outbounds

{
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    },
    {
      "tag": "block",
      "protocol": "blackhole",
      "settings": {}
    }
  ]
}

freedom tells the core to connect directly to the destination; blackhole discards traffic routed to it. direct and block are just tags in this example. You can rename them, but update every routing reference too. A block outbound is for an explicit deny policy, not an automatic fallback when a connection fails. If a rule sends ordinary requests there by mistake, apps may simply time out or lose the connection.

In traditional config behavior, traffic that matches no rule usually goes to the first outbound in the array. Here the first outbound is direct, so direct connection is also the default. Adding a remote outbound at the end does not automatically send all requests through it. Before changing the default, check whether the client generates fallback rules or uses features such as load balancing. Array order alone does not tell you what happens in every setup.

Keep all remote settings together

VMess, VLESS, Trojan and other protocols use different authentication and transport fields. The remote address, port, user details, transport type and TLS settings must match the server's actual configuration. Do not move authentication details from one link into a different protocol object, or delete unfamiliar fields just to make the config parse. Parsing only confirms that the format was accepted; it does not prove both sides agree on the handshake.

With a remote server using TLS, the connection address, server name used in the handshake and host field in the application-layer request may each serve a different purpose. They may be the same, but do not assume so just because they all look like domain names. The server may also specify the transport path. Use the complete config you received and check each value against it. Randomly changing a name or path usually turns one clear error into several mismatches.

LayerWhat to checkCommon pitfall
Identify the objecttag、protocolTreating a tag as a protocol feature
Protocol settingsConnection parameters inside settingsMixing authentication structures from different protocols
Transport settingsApplicable fields such as streamSettingsChanging the transport name without its related settings
Routing referenceoutboundTagOutbound exists but is never selected

Which layer failed?

Once a connection is assigned to an outbound, it may pass through name resolution, an underlying connection, a TLS handshake, protocol authentication and data transfer. A failure at one step is not automatically a “server unavailable” problem. For example, a local DNS failure means no destination address was obtained; an open remote port does not prove protocol authentication succeeded. Look for the earliest specific error in the logs, not just the last summary line.

Test direct and remote outbounds against different, clearly chosen targets. First send the same local inbound to a known reachable direct-connection target to confirm the app-to-core path. Then select an existing remote outbound and check routing and handshake results. If both tests fail, start with the shared inbound or client runtime. If only the remote path fails, investigate remote fields, network conditions and protocol compatibility.

Do not make disabling certificate verification your default troubleshooting step. The certificate name, system clock, trust chain and server settings can all affect TLS; turning verification off can hide the real mismatch. Check where the connection details came from and confirm the device time before following the specific error in the logs. Never post a full JSON config with authentication details in a public forum. Troubleshooting usually needs only the protocol type, field names and a sanitized error.

A client may rewrite remote outbounds when updating a subscription, so use a client-supported method to manage local routing that needs to persist. To install a client, visit the downloads page. For post-import checks, see the v2rayN first-connection checklist. Keep the outbound list small until you have verified each path individually.

Routing: evaluate rules in order

Routing priority depends on rule order, not how many rules there are. Rules are usually checked from top to bottom; the first match selects an outbound, and later rules do not apply to that connection. Put specific exceptions before broad categories. If a domain explicitly set to direct still goes remote, first check for an earlier, broader match rather than repeatedly restarting the client.

Rule order and tag references

{
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "ip": ["geoip:private"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "domain": ["domain:example.com"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "domain": ["geosite:cn"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "network": "tcp,udp",
        "outboundTag": "block"
      }
    ]
  }
}

This example depends on existing direct and block outbounds and loadable GeoIP and GeoSite data. The last rule sends all remaining TCP and UDP connections to the block outbound. That is an explicitly restrictive policy, not a recommended default for everyone. To send remaining traffic through an existing remote outbound, use its actual tag and confirm it exists. Do not invent a tag called “proxy.”

Within a rule, different condition fields usually all have to match; multiple candidates in the same list usually match any one of them. For example, a rule with both inboundTag and domain applies to a connection from the specified inbound that matches the specified domain—not one or the other. Unrelated conditions in a single rule can make it impossible to match. Use separate rule objects for independent paths.

Choose the right domain-matching semantics

domain:example.com matches the domain and its subdomains; full:example.com matches the exact domain only. Plain strings and regular expressions have different semantics, so one successful match does not prove the boundaries are right. For ordinary domain routing, use an explicit match type to avoid a short keyword matching unrelated domains. If you need a regex, test its boundaries and escaping separately.

JSON strings and regular expressions both use escaping, so take care when copying rules from another tool. First check the actual string represented in JSON, then how the core interprets it. There is no need to use a complex expression just to match a domain and its subdomains. Readable rules make it easier to trace false matches without guesswork.

How domainStrategy uses IP addresses

AsIs does not resolve domain names to IP addresses for routing, though connections with a known IP can still match IP rules. IPIfNonMatch tries to resolve the domain and check IP conditions if no rule matches at the domain stage. IPOnDemand triggers resolution when IP information is needed during matching. These settings control when resolution happens during routing. They do not switch the device's DNS globally or specify a remote outbound.

Pay special attention to fallback rules. If a broad rule catches a connection at the domain stage, IPIfNonMatch may never reach the post-resolution matching step. You might expect a private-IP rule to handle a domain, while a fallback network rule actually selects the outbound first. Make clear which destinations should be classified by domain and which need an address check after resolution; do not infer execution order from field names alone.

Keep routing rules readable by separating local addresses, explicit exceptions, service categories and the final fallback. Change one category at a time. After updating rule data, retest a few representative destinations because category contents may change. If a resource is missing or a category cannot be found, address the startup log rather than hiding the problem by deleting all routing rules. For resource maintenance, see how to update GeoIP and GeoSite databases.

Finally, use the logs to check the destination domain, inbound tag and selected outbound. A page loading successfully does not prove the intended outbound was selected: both direct and remote connections may work. If the routing result is unexpected, check the rules in the config actually loaded, then whether the app supplied a domain, and finally the databases. This order helps distinguish missing domain information from a classification error.

DNS configuration: find out who resolves the domain

A single request can involve several resolvers: the app, the operating system, the core's built-in DNS, or the remote side of the connection. Seeing a dns section does not mean every device query goes through it. First determine whether the app gives the inbound a domain or an IP address, then check whether this config participates in the request. Otherwise, changing core DNS while observing system-level results can lead to the wrong conclusion.

What servers and queryStrategy do

{
  "dns": {
    "servers": [
      "1.1.1.1",
      "localhost"
    ],
    "queryStrategy": "UseIPv4"
  }
}

This config specifies a resolver at an explicit address and a local system resolver, while limiting the address families queried by the core to IPv4. Here, localhost means using the local system's resolver, not a remote hostname. Do not assume the list is a fixed primary/fallback sequence where the second server is used only if the first fails. Selection, concurrency and fallback behavior depend on the server objects, matching domains and active core implementation.

UseIPv4 is an address-family choice, not a general performance tip. If a destination is reachable only over IPv6, forcing IPv4 may make it unreachable. If IPv6 is broken on your network, allowing both families may expose a different connection failure. Check what your local network and remote path support before setting the query strategy. This field does not disable IPv6 on the system and cannot replace network settings.

Select a resolver by domain

{
  "dns": {
    "servers": [
      {
        "address": "1.1.1.1",
        "port": 53,
        "domains": ["domain:example.com"]
      },
      "localhost"
    ],
    "queryStrategy": "UseIP"
  }
}

Server objects can route DNS queries for specific domains to a chosen resolver. This example sends example.com and its subdomains to the specified server; other queries depend on the complete fallback logic. domains is not the same as routing's domain field, even if the values look similar. More complex server objects may include expected-address and fallback settings. Confirm that the active core supports their exact semantics before adding them.

Check that DNS servers are reachable. Plain DNS, encrypted DNS, and system resolvers may use different connection paths; some follow normal routing, while others are handled locally. A server listed under `servers` does not necessarily use a proxy. If the resolver address is a hostname, consider how it is resolved at startup to avoid a circular dependency.

SettingMain effectDoes not imply
System network settingsAffects apps using the system resolverAll core queries use this resolver
Top-level dnsProvides DNS resolution inside the coreAll device DNS traffic is intercepted
Routing resolution strategyDetermines whether matching needs an addressSelects an outbound for a connection
App-specific DNS settingsAffects that app's resolution pathOther apps get the same result

Limits of caching, sniffing and FakeDNS

After changing resolution settings, apps, the system and the core may still have old cached results, and existing connections may be reused. Test with a new connection and, when needed, clear the cache using the relevant app's method. Do not rely on refreshing the same page once. A browser using its own encrypted DNS may see different results from the system. Identify the resolver path used by your test before comparing results to determine whether the change actually affected the request.

FakeDNS uses virtual addresses to store domain mappings, helping restore domain information for subsequent traffic. It does not assign real destination addresses on the public internet. It must work with traffic interception, mapping cleanup and sniffing; enabling one field alone will not make every app work. If a program bypasses the expected entry point and tries to connect directly to a virtual address, it may fail. If the system proxy already works as needed, there is no need to enable FakeDNS just to add another setting.

Troubleshoot DNS in three steps: check whether you got an answer, whether it suits the network, then which answer the connection used. A DNS timeout in the logs points first to resolver reachability. If an address is returned but the connection fails, check the address family, outbound and remote path next. For background, see how FakeDNS works and when to use it. Not every connection failure is a DNS problem.

Once the config is stable, keep a small set of destinations that represent different resolution paths for regression tests. Repeat the same tests after changing a server, query strategy or interception mode. DNS answers can vary with service routing, so they do not need to stay identical. What matters is that resolution completes, the returned address works and the connection follows the expected routing boundaries.

Policy and logs: manage connections and keep evidence

policy covers connection management and stats-related behavior; it does not decide which outbound a domain uses. For a routing issue, check routing; for an authentication issue, check the outbound. Do not blame every connection failure on insufficient time just because the policy has a timeout field. Most users can keep the defaults until logs show that a connection lifecycle needs a specific adjustment.

How level policies apply to connections

{
  "policy": {
    "levels": {
      "0": {
        "handshake": 4,
        "connIdle": 300,
        "uplinkOnly": 2,
        "downlinkOnly": 5,
        "statsUserUplink": false,
        "statsUserDownlink": false
      }
    },
    "system": {
      "statsInboundUplink": false,
      "statsInboundDownlink": false,
      "statsOutboundUplink": false,
      "statsOutboundDownlink": false
    }
  }
}

The "0" level is an object key, so it must be quoted. Which level a connection uses depends on the protocol settings: some inbounds link it through userLevel, while some protocols store it in the user object. Adding a level object does not automatically move every connection to it. The values here illustrate the fields; they are not universal recommendations for every network.

handshake controls how long to wait for a connection handshake; connIdle sets the idle connection duration; uplinkOnly and downlinkOnly concern how long a connection is kept when only one direction is active. These values are usually in seconds, but their exact effects depend on the protocol and connection stage. Setting every value very high may just keep dead connections around longer; it will not fix a wrong server address or authentication details.

Stats toggles are not a complete monitoring service

Boolean stats settings in the policy only allow certain data to be recorded. Reading it may also require a stats module, user identifiers, API configuration and client support. Turning on a few toggles does not mean the UI will immediately show every metric. In a GUI client, keep the stats settings it manages unless you know what you are changing; altering tags or removing an API may prevent the UI from communicating with the core.

Do not expose a management API casually just to view stats. Whether it listens, which address it binds to and who can access it all need separate consideration; it is not an ordinary app proxy entry point. If the client already shows traffic data, you usually do not need to create an externally accessible API. The baseline config on this page has no management API: it keeps the connection path clear rather than providing a complete monitoring setup.

Use warning logs by default; increase detail briefly when needed

{
  "log": {
    "loglevel": "warning"
  }
}

Use warning during normal operation to keep routine output manageable. When troubleshooting, switch briefly to a more detailed log level to see where a request goes, then reproduce the issue once. Avoid leaving verbose debug logs enabled: they may record destination addresses and connection details while using extra storage. Restore the usual level when done and remove unneeded logs using the method provided by your client.

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

This snippet writes access and error logs to relative file paths. Relative paths usually depend on the process's working directory, which may not be the directory containing the JSON file; the client may also manage output separately. Before using it, check that the directory is writable, the files are easy to locate and logs will not grow unchecked. If an invalid path prevents startup, restore the client's default output settings instead of repeatedly changing connection parameters without evidence.

Read logs in timestamp order to reconstruct a request: did the core start, did the inbound receive a connection, which outbound did routing select, did resolution succeed, and where did the handshake fail? If the first clear error says a config field is unrecognized, later connection statuses are not useful yet. If startup succeeded but there are no request entries, go back to the app's entry point. Find the earliest confirmed failure before deciding what to change.

On mobile, distinguish process lifecycle issues from connection policy. The OS may stop the app, restrict background activity or switch networks; extending connIdle alone will not fix that. If connections drop when the screen turns off, use v2rayNG background and battery settings to check system-level conditions. Policy settings matter only while the core is running and managing the connection.

Validation and rollback: isolate issues with a minimal config

When validating a config, separate “the core starts” from “the destination is reachable.” Startup checks include JSON syntax, field compatibility, resources and listener settings. Successful access also depends on the app, resolution, routing and the rest of the connection. The local direct-connection baseline below checks whether an app can reach a destination that is already directly accessible through the core. It does not include a remote proxy or require GeoIP or GeoSite resources.

Complete local direct-connection baseline

{
  "log": {
    "loglevel": "info"
  },
  "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": {}
    }
  ],
  "routing": {
    "domainStrategy": "AsIs",
    "rules": [
      {
        "type": "field",
        "network": "tcp,udp",
        "outboundTag": "direct"
      }
    ]
  }
}

Back up your current client settings, then load this baseline through a client-supported entry point for a complete custom config. Do not paste it into a field that accepts only a single server's settings. If the client manages ports or generates additional rules, follow its actual configuration method and avoid running two cores that compete for the same port. After loading it, point an app with manual proxy support to the relevant local entry point and visit a destination known to be directly reachable.

Validate one layer at a time; change one variable at a time

If the baseline will not start, check the earliest error first. For syntax errors, check brackets, commas and double quotes; for a port conflict, check other processes; for an unrecognized field, confirm the core and config format. Do not add DNS, sniffing or more outbounds yet—they create more possible causes. A minimal config helps break intertwined problems into steps you can verify independently.

If the core starts but an app cannot connect, first check whether the request appears in the logs. If not, check the app's proxy protocol, address and port. If it appears but name resolution fails, check the system resolver and destination domain. If an address is returned but the connection fails, check the local network and destination service. There is no remote outbound in this test, so remote authentication and transport settings are not relevant yet. Confirm the baseline works before using it as a reference.

Once the baseline works, restore your existing remote outbound and add one explicit route to it. Verify a single remote connection, then add domain categories, DNS objects and other policies gradually. Run the same test after each group of changes and record the result. If a new section causes a failure, roll back that section to narrow down the cause. There is no need to reconfigure an inbound that has already passed testing.

Observed symptomCheck firstNot the first priority
Core exits immediatelySyntax, fields, resources and portsBrowser cache
Core starts, but no requests appearApp entry point and proxy scopeRemote authentication settings
Request arrives but the wrong outbound is selectedLoaded config, rule order and tagsIncreasing connection timeouts
Correct outbound, but handshake failsProtocol, transport, clock and remote detailsReordering domain categories

Reloading config and existing connections

After changing the config, reload it or restart the core using a method supported by your client. Existing long-lived connections, browser connection pools and app caches may retain the old state, so a page that is still playing does not prove the new rules had no effect. Test with a new connection; if needed, close and reopen the app. You do not need to restart the whole device after every field change. A small, targeted test is a faster way to confirm the config was reloaded.

Test the system proxy and TUN separately too. Verify one access method before switching to the other; overlapping interception can make it hard to tell how traffic entered. Desktop and Android integrate with the system differently, but the checks are the same: inbound, resolution, routing and outbound. If only one app is unaffected, check its proxy scope before concluding that the entire core is broken.

When rolling back, restore the complete set of related fields

A useful backup is more than a JSON file: it includes the config source, active core, required resources and client settings. When rolling back routing, restore the full rule order; for an outbound, restore the protocol and its transport fields; for DNS, check any app-specific resolver settings too. Do not undo only the last visible value while leaving a rule that points to a deleted tag. Incomplete references can cause new errors.

Keep a short change log with the goal, fields changed, test method and result. It does not need account details or a copy of every full log. For a similar issue next time, start with the proven baseline and compare what changed. New users can start with the tutorial; for common connection issues, see the connection troubleshooting FAQ. Use this page as a reference for fields and execution flow, and return to the relevant section for the issue at hand.