JSON Configuration Guide
Trace the configuration from local inbound to outbound selection, with field references, processing order, and client-managed settings.
For your first setup, start with the import, mode selection, and connection checks in the Getting Started Guide. This page is a reference: it explains why settings take effect, which fields depend on one another, and how to track down errors after a change. Desktop instructions use v2rayN where applicable; see Clients and platform downloads for installation options.
JSON Structure and Request Flow
First, distinguish the client from the core
v2rayN, v2rayNG, and v2flyNG provide configuration management interfaces; the core handles listening ports, protocols, and connection forwarding. A node entry in the interface is usually not a complete core configuration. The client combines node details, routing templates, local listeners, and runtime options to generate a configuration for the current run. Editing a temporary JSON file therefore does not mean the change will persist after the next launch. Before reading a file, identify whether it contains node details, custom settings, or the runtime configuration generated by the client.
V2Fly and Xray have a shared lineage, but their configurations are not fully interchangeable. Protocol support, transport fields, DNS extensions, and default behavior can differ. This guide focuses on structures common to both, and calls out limitations where protocol-specific details matter. Avoid piecing together snippets from different cores and repeatedly deleting fields until the configuration starts. A successful launch only means the configuration was accepted; it does not prove that traffic follows the intended path.
Minimal Local Test Configuration
This configuration listens on the local loopback address, provides a SOCKS inbound, and sends received connections directly to their destinations. Use it to test configuration structure, port availability, and application proxy settings—not remote nodes. Before running it, make sure the port is free. If the client has already created a listener on the same port, test this separately through the client's supported custom configuration option to avoid a port conflict between processes.
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "socks-local",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
}
]
}
The connection flow can be summarized as follows: an application connects to an inbound, the inbound identifies the destination, routing selects an outbound, and the outbound establishes the connection. DNS is involved in name resolution, but not every request necessarily uses the built-in DNS configuration. Policy controls connection behavior, while logs help record and diagnose issues. Keep these roles separate to determine whether a failure is at the entry point, decision, or exit—rather than blaming every access problem on the node.
Objects, Arrays, and Data Types
The top level is an object. inbounds and outbounds are arrays because a configuration can define multiple entry and exit points; routing, dns, and policy are usually objects. Array order can matter—for example, routing rules are evaluated in sequence. Object key order usually does not determine processing order. Moving top-level sections around will not make DNS run before or after routing.
Strict JSON requires double quotes around field names and strings, and does not allow trailing commas or comments. Do not arbitrarily change numbers such as ports into strings; boolean values must be written as true or false. Some loaders tolerate relaxed syntax, but portable configurations should follow strict JSON. In particular, avoid duplicate fields in the same object: some parsers keep only the last value, so a file may load while silently discarding earlier settings.
tag is an internal reference name, not a protocol type. An outbound can be named direct or anything else; routing rules must use the exact same string. Differences in capitalization, spacing, or spelling can break the reference. List your inbound and outbound tags before writing rules. Change one section at a time and keep a recoverable copy—it's easier to identify the cause than after replacing the entire runtime file at once.
inbounds Configuration
The Listen Address Defines the Inbound's Reach
An inbound defines how the core receives connections; it does not determine which server they ultimately use. listen is the local address to bind to, port is the listening port, and protocol specifies the inbound protocol. For use on the current device only, prefer binding to 127.0.0.1. This address has nothing to do with a remote server. Do not enter a server domain from your subscription here: it is not a local address, and binding will usually fail.
Binding to 0.0.0.0 listens on every local IPv4 interface, which may allow other devices on the same network to reach the inbound. Actual access also depends on the firewall and network isolation, but a firewall is not a substitute for authentication. If you need to share the inbound on a local network, restrict allowed sources, set appropriate authentication, and verify the listening interfaces. Do not expose every inbound on every network interface just to troubleshoot a connection failure; that creates a new access risk.
| Field | Purpose | What to Check |
|---|---|---|
tag | Identifies the inbound | Must match the routing inboundTag |
listen | Binds to a local address | Use the loopback address for local access |
port | Listening port | Must match the application setting and be available |
settings | Protocol-specific parameters | Cannot be reused across protocols without adjustment |
sniffing | Attempts to identify the traffic destination | Distinguish the detected name from the actual connection target |
SOCKS and HTTP Inbounds
A SOCKS inbound works for applications that support SOCKS proxies; an HTTP inbound is for applications configured to use an HTTP proxy. Both may use a local address, but their protocols are not interchangeable. If an application is set to use an HTTP proxy but points to a SOCKS listening port, the TCP connection may succeed while the subsequent handshake fails. Switching remote nodes usually won't help. First check that the application's proxy type matches the inbound protocol.
{
"inbounds": [
{
"tag": "http-local",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
]
}
This HTTP inbound can coexist with the SOCKS inbound in the first section, but both must be added to the same inbounds array. Do not add another top-level field with the same name. The two inbounds need different ports. If the client manages these ports automatically, make the change in its interface rather than only editing a temporary file. The application's port, client settings, and actual listening address in the runtime logs should all match.
udp: true in SOCKS settings only allows the inbound to handle the corresponding UDP forwarding capability; it does not mean that every application's UDP traffic will automatically reach the core. The application must support the relevant proxy method, and the outbound and remote service must support it too. Traditional system HTTP proxy settings do not capture all UDP traffic. When troubleshooting voice or other UDP applications, first confirm that the traffic enters the core instead of checking only for this boolean field in the inbound.
Sniffing and Routing: Where the Boundary Lies
sniffing can attempt to identify domain names from supported protocol handshakes, commonly HTTP and TLS. It is not a general-purpose parser for all traffic, and it cannot guarantee a domain name for every connection. If an application connects directly to an IP and the handshake contains no recognizable hostname, domain rules may not match. Enabling sniffing therefore does not guarantee that domain-based routing will work. Check the access logs to confirm what was actually detected.
Some cores can use sniffed results for routing without replacing the actual connection target. Check the current core's documentation for the relevant fields and behavior; these extensions are not included in the general examples here. First decide what you need: additional information for routing, or a change to the connection destination. Confusing the two can make logs appear to match the right domain while resolution or the actual connection takes a different path, especially for local services and specific IP addresses.
outbounds Configuration
Outbounds and the Default Route
An outbound defines how the core handles a connection it has received. It can connect directly to the destination, forward traffic through a remote proxy, or discard it. When no rule matches, the first outbound in the outbounds array is usually used. That means the first object is part of the default behavior, not just a matter of formatting. Moving a direct outbound to the top can cause previously unmatched connections to go directly to their destinations.
tag gives each outbound a name that routing rules can reference; protocol selects the outbound protocol, and the structure of settings depends on that protocol. In routing, outboundTag refers to the tag, not the protocol name. A configuration can have multiple outbounds using the same protocol, but each should have a unique tag. Otherwise, rule references become ambiguous, and the client may overwrite existing objects when generating its configuration.
{
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
}
freedom means the current device connects directly to the destination; it does not tell you which DNS resolver the request will use. blackhole discards matching connections, which may appear to the application as a timeout or connection failure. Used on its own, the snippet above defaults to direct connections; the blocking outbound is used only when a rule selects it. Defining an outbound does not automatically send traffic through it—you must also check the default order or routing references.
Remote Node Parameters Cannot Be Inferred from One Another
Proxy outbounds such as VMess, VLESS, and Trojan must match the server configuration. Server address, port, credentials, transport, and security are separate settings; do not infer one from another. A correct address with the wrong transport, or valid credentials with a mismatched security layer, will still fail to connect. For a real node, import a valid link or subscription through the client rather than manually filling in unknown values from a webpage snippet.
Common proxy outbounds also use streamSettings to describe transport and security. The protocol determines how proxy requests are encapsulated, the transport determines how the byte stream is carried, and the security layer determines how the connection is protected or verified. These are not three switches that can be combined at will. The client settings must match the combinations accepted by the server. Turning on every security option does not improve compatibility; it may create a handshake the server cannot understand.
The connection address and the server name used for TLS should also be treated separately. The former establishes the network connection; the latter is used in the handshake and server identity verification. They can differ, but both must satisfy the service configuration and certificate requirements. If certificate verification fails, check the server name, device clock, and server certificate chain. Disabling verification is not a routine fix: it changes the identity checks and often hides a mistyped server name.
Core Support and Outbound Capabilities
v2rayNG uses the Xray core, while v2flyNG is a V2Fly-core client. For v2rayN, check the current configuration and runtime logs to identify the core in use. A link being recognized by the interface does not mean the selected core supports every field. If you encounter an unknown protocol or unsupported transport parameter, confirm the core family first and then decide whether to use a compatible configuration. Simply changing the protocol name cannot convert a node.
Outbounds may also involve UDP forwarding, multiplexing, connection reuse, and socket options. Support depends on the combination of core, protocol, and server, so avoid enabling all of these in a minimal configuration. Troubleshoot with the basic outbound generated by the client first and verify that a single connection works, then restore extensions one at a time. Otherwise, a failed request could involve inbound UDP, the remote protocol, or the underlying network, making it difficult to identify which layer failed first.
When managing multiple nodes, distinguish the client's “current node” from “multiple outbounds” in the core configuration. Changing the selection in the interface may regenerate a single proxy outbound or update a referenced outbound; the exact behavior depends on the client. After a subscription update, check that the tags and rule references are still present. For background on the core families, read Project V, V2Fly, and the Xray branch before choosing a client.
routing Rules
Rule Order and Matching Conditions
routing.rules is an ordered array. The core checks rules in sequence and selects the corresponding outbound when it finds a match; if none matches, it falls back to the default outbound. Put specific, narrow conditions first and catch-all conditions later. If the first rule already covers all TCP and UDP traffic for an inbound, a later domain rule for the same inbound will never get a chance to match, no matter how specific it is.
Different condition fields in one rule usually must all match; multiple entries in an array for the same field usually mean any one of them can match. For example, specifying both inboundTag and domain means the connection must arrive through that inbound and match the target domain—not either condition on its own. To express two independent cases, use two rules instead of combining all the conditions in one object.
{
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"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",
"domain": ["full:example.com"],
"outboundTag": "block"
}
]
}
}
This snippet depends on the direct and block outbounds declared in section three. The first rule handles the listed IPv4 address ranges; the second blocks only an exact domain. This is not a complete list of private networks, and it does not cover every IPv6 case. The example uses AsIs, so an unresolved private-network domain will not necessarily match the first rule just because its eventual IP falls within one of those ranges. The form of the target is part of the matching conditions.
Domain Rules and Resource Files
full:example.com matches the exact domain; domain:example.com matches the domain and its subdomains. Do not put a full URL in a domain field: the scheme, path, and query parameters are not part of the domain. Choose the matching syntax deliberately when you need to cover both the root domain and subdomains. Regular expressions are useful when a pattern is genuinely needed; for ordinary domains, explicit prefixes are easier to review and less likely to match a broader set of traffic by mistake.
geosite: and geoip: refer to categorized resources; they are not arbitrary category names that the core understands automatically. The resource file must exist, and the category must be present in that file. Clients may also use different sources for rule resources. If a resource fails to load, first check the file location and category spelling. Changing the outbound address will not fix this: resource loading may fail before any remote connection is made.
What domainStrategy Does—and Does Not Do
AsIs uses the original target for routing where possible. IPIfNonMatch can try to resolve the domain if no domain rule matches, then check IP conditions. IPOnDemand may trigger a lookup when routing needs to evaluate an IP condition. The latter two affect DNS requirements during routing. They do not mean “global proxy” or “direct mode,” and they do not directly determine where a remote proxy server resolves the final destination.
A routing mode in the client interface often corresponds to a complete set of rule templates, not just one domainStrategy value. Switching modes may replace rule order, the default outbound, or category resources. To check how traffic is routed, inspect the generated routing section and note the target domain, inbound tag, selected outbound, and matching rule position. A website loading in your browser alone cannot tell you whether a specific rule matched or the default outbound was used.
Build a small test list: verify an exact domain, a subdomain, a direct IP, and a local service, changing only one rule at a time. If the access log shows only an IP, check whether the application resolved it before the request reached the core and whether the inbound provides domain information. DNS and routing work together, but neither replaces the other. For more on split DNS, see V2Ray DNS Split Routing: A Configuration Guide.
dns Configuration
First Identify Which Layer Sends the Query
A single visit may involve several DNS lookups: the system resolving the application's target, the core resolving a domain for routing, the outbound resolving the remote server address, and the remote service resolving the final destination. The dns section defines resolver behavior inside the core; it does not automatically take over system lookups or every application's queries. When troubleshooting, first ask which process looked up which name. Otherwise, it is easy to mistake the browser's result for a result from the core's DNS.
If an inbound receives a domain name, the core may pass it directly to the proxy outbound. If it receives only an IP address, domain-based DNS rules usually have no original name to work with. Routing policy, resolver options for direct outbounds, and the client's traffic capture mode all affect the actual lookup path. If changing the DNS server does not change how a request is routed, the setting may not be broken—the request may simply not use the built-in DNS at all.
servers, hosts, and Query Types
{
"dns": {
"hosts": {
"full:example.com": "192.0.2.1"
},
"servers": [
"localhost"
],
"queryStrategy": "UseIPv4"
}
}
This snippet demonstrates static mappings and the system resolver. 192.0.2.1 is a documentation-only address, not a usable website address; with this mapping in place, a connection relying on the built-in DNS cannot be used to verify normal access. localhost means to use the system resolver; it does not send a query to an arbitrary UDP port on the local machine. For actual use, remove the demonstration mapping and choose a resolver suited to your environment.
hosts can pin a result for a particular name, which is useful for troubleshooting or known internal hostnames, but it bypasses normal dynamic resolution for that name. If the service address changes, an old mapping may keep pointing to the wrong place. If a domain always resolves to an unexpected address, check the core's mappings, the system hosts file, and the application's cache—not just the subscription. These are separate layers; changing one does not clear the others.
queryStrategy controls which address families the built-in resolver uses, such as UseIPv4, UseIPv6, or UseIP. It does not change the system's network protocol or guarantee that every connection uses only one address family. If the device has an IPv6 address but the route is unusable, test resolution and connectivity separately. An AAAA record only proves that a result was returned; it does not prove that traffic can reach that address.
Choose a Resolver by Domain
The servers array can contain server objects with common fields such as address, domains, and expectIPs. domains identifies the domains that should preferentially use that server; expectIPs constrains the acceptable result range. The latter does not assign a static address to a domain or define an outbound rule. Whether another server is tried after a result is rejected depends on the other servers and the fallback behavior of the current core.
Avoid applying an overly narrow expected IP range to a broad set of domains. Content delivery networks, dual-stack deployments, and service migrations can all change legitimate results. Strict filtering can turn a valid response into a resolution failure that looks like an unavailable remote node. Before configuring it, confirm the category rules and actual address distribution. Afterward, record which results were accepted or rejected. If you lack that information, start by testing a narrow, targeted rule.
Plain DNS, encrypted DNS, and system resolution can use different connection paths. Some address formats bypass ordinary outbound handling, while others may be routed through the core. Check the current core's documentation for the exact server address syntax; the word “encrypted” does not guarantee that a query goes through a proxy. If resolver connections need a proxy, but the proxy server's hostname in turn depends on that resolver, you have a startup dependency loop. Set up an independent resolution path for the server name first.
policy: Connection and Statistics Settings
A Level Is Not Routing Priority
policy controls connection lifetimes and some statistics behavior; it does not select a node. Keys under levels are user-level IDs, and JSON object keys must still be strings. Protocol users or the relevant inbound settings can refer to a user level, but the exact field depends on the protocol. If no level is explicitly set, the default level is usually used. A higher number does not mean higher speed, and a level cannot replace routing rule order.
Before setting policy, confirm which user it applies to. If the inbound protocol has no corresponding user settings, or the client-generated file does not include the expected level, a custom level may never be referenced. Look for the associated field in the final runtime configuration; the presence of a levels object alone does not mean it is active. For a single-user local client, clearly defined default behavior is usually easier to maintain than several unreferenced levels.
{
"policy": {
"levels": {
"0": {
"handshake": 4,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5
}
},
"system": {
"statsInboundUplink": false,
"statsInboundDownlink": false
}
}
}
These time values illustrate common policy settings in seconds; they are not universal optimization recommendations. Check the defaults and supported options for each core. When a connection drops, do not start by copying a set of larger timeout values. Determine whether the handshake failed, the connection was idle for too long, or one direction had already closed. A policy setting only applies at its relevant stage; it cannot fix authentication or certificate errors, or a server that is not listening.
What the Four Timeout Settings Cover
handshake limits how long the connection handshake can wait. If the network is slow to establish a connection, a limit that is too short may terminate a handshake that would otherwise succeed; making it unlimited only prolongs the wait when it fails. connIdle concerns how long a connection stays idle and is often used to clean up inactive connections. It is not the same timer as the application's request timeout or a remote reverse proxy timeout. Any layer may close the connection first.
uplinkOnly and downlinkOnly concern how long a connection is kept open when only one direction remains active. Downloads, long-lived connections, and half-closed connections may be affected. Interpret “up” and “down” in terms of the core's traffic directions, not as “faster uploads” or “faster downloads.” If an application waits for a long response after sending its request, an overly aggressive one-way timeout may close the connection early. Check the application protocol and logs.
To decide whether a policy change is warranted, check whether the issue consistently appears after a similar idle period and whether renewed activity prevents the disconnect. If the connection fails immediately, check the protocol and authentication first. If failures happen only after switching networks, check connection recovery and the system's network state. Treating every problem as a timeout can hide the real cause and keep unusable connections open longer.
Statistics Switches Are Not a Statistics API
The inbound and outbound traffic-statistics switches in policy.system control the corresponding counters; user-level settings may also enable per-user statistics. Turning these fields on does not automatically add a traffic graph to the interface, nor does it expose a query API. The client must also configure a statistics module, query service, and tag associations. If you only want traffic displayed in the client, check its built-in options first rather than creating a duplicate interface by hand.
If a statistics service or management interface needs a listening address, restrict it to an appropriate local scope instead of exposing it along with LAN inbounds. Statistics tags should also match the actual inbounds and outbounds; otherwise, counts may be empty or attributed incorrectly. Adding unknown statistics fields will not make an unsupported core support the feature. If a field or service fails to load, restore the basic policy first and check extension support one item at a time.
Test policy changes with a reproducible single-connection scenario, not by judging whether a webpage seems to load faster. Record the stage at which it waits, the reason it closes, and the application's behavior before and after the change. Core restarts typically affect existing connections and in-memory counters, so keep the starting conditions consistent. For everyday client use, keep the generator's reasonable defaults and change only fields supported by evidence; this makes upgrades and troubleshooting easier.
Client-Managed Configuration
Find the Configuration Source That Persists
A graphical client usually stores node details, subscription groups, routing templates, and interface settings separately, then generates a runtime file for the core based on the current selection. These layers are not the same data. Updating a subscription mainly changes remote node details, changing a routing mode mainly changes rules, and system proxy settings affect the application entry point. Identify which layer your change belongs to before editing. Don't modify a remote outbound just to change the browser's entry point, or re-import every subscription just to adjust routing.
In v2rayN, make routine changes through the relevant local listener, routing, and core settings. Use the client's custom configuration feature when you need a complete custom JSON file. Menu names can vary between interface versions, so identify the right function rather than copying a file path from an unrelated screenshot. In particular, avoid editing only the temporary configuration currently in use: the generator may overwrite it as soon as you select another node or restart.
| Source | What It Contains | Commonly Overwritten When |
|---|---|---|
| Node entry | Remote protocol, address, authentication, and transport | Editing a node or updating a subscription |
| Routing template | Rule order and outbound selection | Switching routing modes |
| Client settings | Local inbounds and runtime options | Saving settings or restarting |
| Custom configuration | Core behavior defined by a file | Reloading the file or switching configurations |
Importing, Saving, and Running Are Separate Steps
If the client accepts a file, that may only mean it recognized the format or link. You still need to select the configuration, start the core, and check the runtime logs. If you save without reloading, the existing process may continue using the old contents. During verification, check the startup logs and actual listening information; a changed name in the list is not enough. If the client can preview the generated configuration, compare key fields before and after startup to confirm that the expected inbound tags and outbound references are in use.
On Android, v2rayNG and v2flyNG typically use system authorization to create a virtual network interface for traffic capture. App selection and background settings also affect which traffic enters. Desktop system proxy behavior does not translate directly to this setup. Adding a SOCKS inbound by hand does not replace the client's traffic capture workflow. Conversely, enabling system capture does not prove that every inbound in a custom file is being used.
When moving between the three clients, assess node details and complete configuration separately. A share link usually contains node information, but may not include local ports, DNS, routing resources, or policy. Even if the target core supports every field in a full JSON file, the client may require specific tags, inbounds, or runtime settings. After migration, verify the inbound, default outbound, DNS, and rule resources—not just whether the node appears in the list.
A Safe Order for Merging Configuration
When merging snippets, start with the current working configuration, make a backup, and replace one complete section at a time. Merge array elements into arrays and fields into objects; do not paste two top-level objects together. Resolve tag conflicts first, then update every reference. If a routing rule points to a new tag, confirm that the outbound exists before enabling the rule. Otherwise, the core may refuse to start while loading the configuration.
Subscription URLs and node credentials are sensitive. Before saving configuration copies, sharing screenshots, or asking for help, remove access parameters from addresses, identity fields, and subscription links. Logs may also contain server addresses or destination domains, so review them before sharing. The examples here contain only local inbounds and documentation addresses; they are not remote service details. Get real authentication values from the service configuration, and never put them in public documentation.
Test the system proxy and core separately. If the core is running but the system proxy still points to an old port, applications will fail to connect. If you exit the client before the system proxy is restored, applications may keep trying to reach an inbound that no longer exists. Conversely, some applications ignore the system proxy and need to be configured separately. If only one application cannot connect, check its proxy settings before replacing the entire core configuration.
After updating the client or switching cores, keep a copy of the original configuration and recheck extension fields and generation behavior. An old option remaining in the interface does not mean the new core interprets it the same way. For the basic setup, return to the Getting Started guide. For subscription issues, see Subscription Refresh and Automatic Updates so you can distinguish a subscription download problem from a core forwarding problem.
Configuration Validation and Log Troubleshooting
Check in Order: Load, Listen, Connect
Start troubleshooting at the earliest stage that fails. First, can the JSON be parsed? Second, does the core accept the fields and tag references? Third, can the inbound listen? Fourth, does application traffic reach it? Only then check routing, DNS, and remote connections. If the logs already show a port binding failure, changing DNS is pointless. If the core never starts, a browser connection failure cannot tell you whether the outbound protocol is correct.
First confirm the selected core and the result of the latest launch in the client's logs, then inspect the earliest error. Later messages such as “process exited” or “connection refused” may only be consequences of the first error. Read logs in timestamp order and distinguish startup errors from access errors. A log window may include records from a previous run; mark the start of each new run so an old error is not mistaken for evidence that a fix failed.
{
"log": {
"loglevel": "info"
}
}
To inspect connection behavior, you can temporarily set the log level to info; enable a more detailed debug level only briefly when necessary. Merge logging settings into the existing log object rather than declaring it twice. The client may manage access and error log output, so do not enter a path you cannot write to. Restore an appropriate level after troubleshooting to limit the recording of destination details and avoid generating excessive files.
Use the Error Type to Find the Relevant Section
Parsing errors usually involve quotes, commas, brackets, or data types. An editor's highlighted position may be where the error was detected, not where it began; check the previous item for a missing separator. Unknown protocols, unknown fields, or invalid enum values are more likely to indicate a core compatibility issue. Confirm which core is in use first. Do not change a field name to a similar-looking alternative unless the specification confirms they are equivalent.
If listening fails, check whether another process has taken the port, whether the bind address belongs to the local machine, and whether the client has already created the same inbound. If routing fails to load, check that the target tag exists, the resource file is readable, and the category name is valid. For DNS failures, confirm the query target, server reachability, and fallback conditions. These issues occur at different stages, so addressing them separately preserves more troubleshooting evidence than reinstalling the client immediately.
| Symptom | Check First | Don't Change Yet |
|---|---|---|
| Exits while loading configuration | JSON, field support, and tag references | Application proxy settings |
| Connection to the local inbound is refused | Process, listening address, and port | Remote DNS routing |
| Only domain-based access fails | Resolution path and returned address | All inbound parameters |
| A rule does not match as expected | Target format, rule order, and tags | Unrelated connection timeouts |
| Remote handshake fails | Protocol, transport, security, and clock | Local rule resources |
Build a Reproducible, Reversible Test Loop
Keep a known-good, reliably starting configuration as your baseline. Add inbound extensions, proxy outbounds, rules, DNS, and policy one at a time. After each addition, confirm that the core starts, then run the relevant test. A direct-connection baseline only verifies the local inbound and basic network; it does not prove that a remote node works. A successful proxy connection does not prove that routing rules are correct either—the request may have used the default outbound. Draw conclusions only about what the test actually covered.
Record the fields changed, expected result, actual logs, and rollback steps each time. For routing tests, include both a domain name and a direct IP. For DNS tests, distinguish address families and caches. For policy tests, distinguish connection setup from idle behavior. If the result is unexpected, restore the previous baseline first and confirm that the problem appeared with the change. Then narrow down the cause instead of piling more patches onto a broken configuration.
A configuration file combines objects with distinct responsibilities; it is not a feature checklist that becomes better by growing longer. Keep fields whose purpose is clear, remove test settings you have confirmed are no longer needed, and manage client-generated sections through the appropriate client settings. For common issues, see the FAQ. For startup errors, see How to Read Core Startup Errors and Logs. Once troubleshooting is complete, save a final working copy and return to normal use.