Config Structure and Request Flow
A V2Ray config file has a JSON object at its root. Start by locating inbounds, outbounds and routing: inbounds accept app requests, routing decides which outbound handles them, and outbounds establish the next connection. dns provides name resolution the core can use, while policy controls some connection timeouts and statistics. These sections reference one another, but they are not executed in file order. The order of top-level keys usually does not change how the config works.
Keep three easily confused types of data separate. A subscription contains remote connection details; client settings store interface options; and the runtime core config is what the client builds from those inputs and passes to the core. Subscription content is not necessarily complete JSON, and a client's own settings file may not be loadable by the core. A file containing a server list, window state or subscription update time is not necessarily a runtime config just because of its extension. Check the client logs, config viewer or custom-config documentation to confirm what the file is for.
A Complete Skeleton: Local Proxy Inbounds and a Direct Outbound
Here is a basic V2Ray-style config. It provides SOCKS and HTTP inbounds on the loopback address, with all ordinary requests going direct and no remote proxy server configured. Use it to see how inbounds, outbounds and routing fit together—not as a replacement for a proxy config generated from a subscription. The example ports are for illustration; check that no other process is using them before you start. Your client may instead generate a mixed inbound with a different port layout.
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
},
{
"tag": "http-in",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "direct"
}
]
}
}
Here, direct and block are custom tags, not reserved keywords. A routing rule's outboundTag must exactly match an outbound's tag. Requests that match no rule go to the first outbound by default, so this example still sends them direct. Private-address rules depend on the relevant geodata resources. If the runtime environment does not provide them, fix the resource-loading issue or, once you understand the implications, use explicit address ranges instead of ignoring startup errors.
Check JSON Syntax Separately from Core Behavior
In standard JSON, keys and strings use double quotes; numbers and booleans do not. Do not add a trailing comma after the last item in an array or object. The code samples here use standard JSON and contain no comments inside the config. Some loading methods accept extended syntax, but a format accepted by one client is not necessarily accepted by every core. When copying, take only the code—not the heading, explanatory text or code-block markers.
Valid syntax does not guarantee valid fields. "port": 10808 uses a numeric port, and "udp": true uses a boolean; writing either as a string may cause a parse error or unexpected behavior. Duplicate keys are worth checking too: parsers may handle them differently. Remove duplicate definitions rather than relying on the later value to override the earlier one. After editing, check the structure, then the references, then the runtime behavior. These checks are not interchangeable.
The short examples in the following sections are snippets for understanding or replacing the relevant section; none is a complete runtime config on its own. If you already have a client-generated config, edit a copy and change one field at a time, preserving the rest of its valid content. For a more detailed walkthrough of how the sections fit together, see A Section-by-Section Guide to V2Ray JSON Configs.
inbounds: Listen Address, Port and Protocol
inbounds is an array. Each item defines an entry point that accepts requests, specifying the listen address, port, protocol and tag. Apps must use the proxy protocol and port that match the inbound. In a browser, connect to an HTTP inbound when configuring an HTTP proxy; apps that support SOCKS can connect to a SOCKS inbound. Entering a port number in system proxy settings does not make that port speak the protocol the app expects.
listen sets the address to accept connections on. For a single-device setup, prefer 127.0.0.1 so the inbound is available only on the local machine. Listening on all interfaces exposes it more broadly and raises firewall, authentication and LAN access-control concerns; it is not a general fix for a website that will not load. Loopback addresses also differ by IP family: an app connecting to IPv6 loopback may fail if the service listens only on IPv4 loopback. When troubleshooting, explicitly use the address the config actually listens on.
SOCKS Inbound: UDP and Domain Forwarding
This snippet enables a local SOCKS inbound with UDP support. Setting auth to noauth means the inbound does not require a SOCKS username and password; it does not mean the remote node needs no credentials. Bound to loopback, it is suitable for apps on a single-user desktop. If you make it available to other devices, reassess the access boundary rather than just changing the listen address.
{
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
]
}
Enabling udp allows the inbound to accept relevant UDP forwarding requests. It does not mean all app traffic will reach the core, or that every outbound can carry UDP. The app must use a compatible forwarding method, and both the outbound protocol and remote service must support it. A webpage loading successfully only confirms that the current web request path works; it does not prove that real-time communication or other UDP apps will work.
Where a domain is resolved depends on how the app makes its proxy request. The app may resolve the name locally and pass an IP address to SOCKS, or pass the domain to the proxy for resolution. In the first case, routing may have less domain information to work with; in the second, the core may be able to match rules against the original domain. Judge options such as “resolve domain through proxy” by what the app actually does. The SOCKS label alone does not tell you where resolution happens.
HTTP Inbound and Inbound Tags
An HTTP proxy inbound is often used for system proxy settings or manual browser configuration. For an HTTPS site, the app typically creates a tunnel through the HTTP proxy; this does not mean the local HTTP inbound needs a website certificate. Keep the local proxy protocol, destination site protocol and remote proxy transport separate. If you use a dedicated HTTP inbound, the port in system proxy settings must match it. If the client uses a mixed inbound, use the port currently shown by the client.
| Field | Purpose | What to Check |
|---|---|---|
listen | Local Listen Address | Must use the same address family as the app connection |
port | Local Listen Port | Must not conflict with an existing listener |
protocol | Inbound Protocol | Must match the app's proxy type |
tag | Inbound Tag | Referenced by routing rules via inboundTag |
An inbound tag does not determine the port's protocol, but it can be used as a routing condition. For example, two inbounds on one computer can have different tags and use inboundTag to route through different outbounds. Keep tags unique, concise and meaningful, and check all references when renaming one. Inbound and outbound tags are not automatically paired: matching names do not create a connection. Routing and the default outbound determine what happens.
When troubleshooting a port conflict, first close duplicate client instances, then check the logs for bind errors. If you change the port, update both app settings and the system proxy; editing only the JSON leaves the old inbound address in place. After restarting the core, confirm that the listener was actually created. An inbound being present, an app connecting to it and a remote site being reachable are three separate stages. Verify each in turn instead of switching servers repeatedly to mask a local inbound issue.
outbounds: Protocol, Tags and the Default Route
outbounds defines how the core connects after receiving a request. A direct outbound usually uses freedom, while a blocking outbound usually uses blackhole. A remote proxy outbound uses the relevant protocol and connection details. The protocol determines the config structure; tag lets other sections refer to the outbound. Renaming a direct outbound to proxy does not turn it into a proxy—the behavior still depends on protocol, protocol settings and transport settings.
When reading a client-generated config, list each outbound's tag and note what it actually does. Common names include proxy, direct and block, but clients can use other names. If a routing rule says outboundTag: "proxy", there must be an outbound with that exact tag. If the runtime config uses a different tag, trust the runtime config. Case, hyphens and surrounding spaces all matter.
Minimal Direct and Block Outbounds
{
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
}
The first outbound in this snippet is direct. Requests that match no routing rule go to the first outbound by default, so changing the array order can redirect a large amount of traffic. Reordering is not just a formatting change. If you want proxy as the default, make sure the first outbound is a working proxy, or define a fallback rule explicitly using syntax supported by the core. Do not add an empty proxy outbound just to make the name appear.
A block outbound actively rejects or drops traffic that matches a rule. That is different from a failed connection to a remote server: blocking may be the intended routing result, while a connection failure usually calls for checking the network or remote parameters. If a domain is consistently unreachable, first check whether a block rule matches it before changing DNS. The longer the rule list, the more important it is to document why each block rule exists. Keep those notes in maintenance docs or client comments rather than forcing them into standard JSON.
Read Protocol and Transport Settings as Separate Layers
A remote proxy outbound can be understood in layers: the server address and port specify where to connect; protocol settings describe identity and protocol behavior; and streamSettings describes the underlying transport and security layers. VMess, VLESS and Trojan use different fields. REALITY is a security-layer option supported by specific cores, not an outbound protocol name to swap in for another. When reviewing imported subscription data, identify the actual protocol first, then check each layer of settings.
Matching server addresses and ports do not make two configs equivalent. A mismatch in the server's required transport, TLS server name, path or other negotiation parameters can cause a connection or handshake failure. Do not borrow values from another node just because they look similar. This guide does not provide working remote credentials. Use connection details from a service you are authorized to access, and redact credentials and subscription URLs before sharing logs.
A server address given as a domain name also depends on DNS. Before the core can connect to the remote server, it needs the IP address for that domain. If resolution itself depends on the same connection that has not yet been established, startup can get stuck in a dependency loop. The symptom may be repeated timeouts rather than an obvious JSON error. Distinguish failure to resolve the remote server address from failure to resolve the destination website: they occur at different points in the request path and cannot be diagnosed from a single DNS setting.
A client may rebuild the proxy outbound based on the currently selected server. In that case, manually editing a temporary runtime file may work briefly, then be overwritten when you switch servers or restart. To keep outbound changes, use a custom-config method supported by the client. For a comparison of how desktop and mobile clients manage settings, see the Client Selection Guide. Do not merge every field exported by different clients into one config.
To verify outbound routing, test one request that should clearly go direct and another that should clearly use the proxy. A single page load does not prove that routing behaves as expected: browser cache, existing connections and app-specific proxy settings can all interfere. After editing, close existing connections and test again. Use the logs to check the destination, outbound tag and stage where any error occurred.
routing: Match Conditions, Rule Order and Fallbacks
routing decides which outbound handles each request. Rules are generally checked from top to bottom; the first matching rule determines the outbound, and the core does not keep searching for a “more specific” match. Rule order is part of the config's behavior. A broad rule placed first can overshadow narrower rules below it, while an exception placed last may never match. Before editing, define the intended order: specific exceptions, general categories, then the final fallback.
Within a rule, different condition fields generally all have to match; multiple values in the same condition array are usually alternatives. For example, specifying both inboundTag and domain means the request must come from the specified inbound and match the domain condition. It does not mean either condition can match on its own. If you need separate paths, use separate rules rather than mixing unrelated conditions in one object.
Define Domain Rules Before Adding IP Rules
This snippet shows rule order, with every match sent to direct to illustrate the condition structure. It requires an outbound with that exact tag. Reserved example domains demonstrate full: and domain:; explicit private address ranges avoid dependence on geodata files. This is not a complete geographic routing setup.
{
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["full:example.com"],
"outboundTag": "direct"
},
{
"type": "field",
"domain": ["domain:example.org"],
"outboundTag": "direct"
},
{
"type": "field",
"ip": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
],
"outboundTag": "direct"
}
]
}
}
full: matches an exact domain, while domain: matches the specified domain and its subdomains. The latter is useful for grouping services under one domain, but a site's pages, images and authentication endpoints may use several domains. Do not assume the site's main domain covers every dependency. Bare strings and regular expressions have their own matching semantics. For maintainable general rules, prefer prefixes that make the intent clear and reduce accidental matches.
geosite: and geoip: refer to data sets; they are not online lookup services. Whether they work, and which categories they contain, depends on the data files and category definitions in the runtime environment. A category named in a rule is not guaranteed to be available to the current core. When importing third-party rules, confirm that the category exists, the resources are readable and every referenced tag is defined. Checking that the JSON brackets are balanced is not enough.
domainStrategy Is Not a DNS Server Setting
AsIs generally tells routing to match using the target information already available, without resolving domains just to check IP conditions. IPIfNonMatch attempts resolution if no domain rule matches, allowing IP rules to be checked next. IPOnDemand triggers resolution when routing needs IP information. Choose a strategy based on your rules, not on the assumption that earlier resolution is more accurate; extra lookups add dependencies and can change when requests are processed.
Routing's domainStrategy and DNS's queryStrategy control different things: the former determines when routing obtains an address; the latter sets the preferred address family for DNS queries. A direct outbound may also have its own domain resolution strategy. When troubleshooting, identify the exact stage where resolution happens instead of saying only that “DNS is enabled.” If the app already resolved the domain, whether the core can still see it depends on what the app passes along and on additional mechanisms such as sniffing.
Check the fallback separately. If no rule matches, the first outbound is selected by default. Assuming the last outbound is the default leads to the opposite conclusion. Some client interfaces summarize the final route as final → proxy; this is a shorthand for understanding the behavior, not a universal top-level final field in core JSON. Check the rules or first outbound in the actual config to see how the client's selection is applied.
After adding a rule, compare one target that should match with one that should not. If the new rule has no effect, first check whether a rule above it catches the request; then check whether the core sees the target as a domain or an IP; finally, check the tag reference and resource loading. “Global” in system proxy settings affects how apps hand off traffic, but core routing can still split it. Verify the two separately; for the relevant steps, see Choosing a mode in the Getting Started guide.
dns: Resolvers, Address Families and Query Paths
dns defines resolution capabilities the core can use, but its presence does not make it take over all DNS on the computer. The app, operating system, browser and core may each perform their own lookups. First identify which layer is making the query, then decide what to change. If the browser uses encrypted DNS on its own, changing system DNS may have no effect. If an app resolves a domain to an IP first, the core may not handle the request using the domain rule you expected.
To decide whether the config behaves as intended, answer three questions: where is the query sent, what path does it take, and which stage uses the result? Replacing the server address answers only the first. It does not guarantee the other two will change. The remote proxy server's address resolution, resolution for IP-based routing and resolution of the direct destination may all happen in different places. Mapping these dependencies is more useful for reliable troubleshooting than adding several resolvers at once.
Explicit DNS Servers and Static hosts
Here is a partial DNS example. Static hosts maps an example domain to a loopback address for local testing only; remove this test mapping for everyday use. The server list uses explicit IP addresses so the resolver's own hostname does not need to be resolved first. You still need to verify that the server is reachable and determine which outbound carries the query based on core behavior and routing.
{
"dns": {
"hosts": {
"example.com": "127.0.0.1"
},
"servers": [
"1.1.1.1",
"8.8.8.8"
],
"queryStrategy": "UseIP"
}
}
hosts is useful for temporarily pinning a known address or distinguishing name resolution from connectivity in a controlled environment. It is not a good long-term replacement for a service address that may change. A mapping to loopback sends requests for that domain to the local machine; if no matching service is running, a connection failure is expected. Remove the test mapping when troubleshooting is done, or a future problem may come from the leftover entry rather than the DNS server.
Common queryStrategy values include UseIP, UseIPv4 and UseIPv6; supported values depend on the current core. On a dual-stack network, receiving an IPv6 address does not mean an IPv6 connection is reachable. Temporarily restricting queries to IPv4 can help identify an address-family issue, but disabling an address family is not a universal fix. Also check that the remote address, destination address and operating system's network capabilities are compatible.
Split DNS and Fallback Behavior
More complex server objects can include domain matching criteria and expected addresses to send different domains to different resolvers. Before adding them, confirm which fields and fallback rules the current core supports. Multiple servers do not simply mean “try each one in array order until one works”; matching criteria, fallback settings and checks on the result all affect the decision. If you do not have a clear requirement, start with one easy-to-explain path and add split DNS gradually.
With encrypted DNS, distinguish transport encryption from outbound selection. Encryption describes how queries are transmitted over a given link; it does not tell you which proxy outbound carries them. Different resolver address formats can follow different paths, so do not assume encrypted DNS behaves like ordinary UDP queries under routing. Before configuring it, check the routing and bootstrap-resolution requirements for the method you use, then verify with logs and real requests.
A DNS dependency loop often occurs when the resolver needs the proxy, while the proxy server's domain in turn depends on that same resolver. The config can pass structural checks and still fail to establish its first connection. Fix the path needed for initial resolution rather than repeatedly increasing timeouts. Write down the proxy server's domain, resolver address and outbound to make the loop visible, then decide where to break the dependency.
When testing DNS, close old pages and existing connections, confirm the app uses the intended proxy settings, then compare the domain's resolution and access results. Operating system or browser caches can make a change appear ineffective, and reused connections may bypass a new lookup. Clear the cache at the layer that actually made the query. For specific checks, see DNS Query Path Tests and Configuration Troubleshooting. Do not treat the result from one test page as proof of how every app behaves.
policy: User Levels, Connection Timeouts and Statistics
policy manages some connection behavior and statistics settings; it does not select remote servers or route traffic by region. It generally contains levels and system: the former sets policies by user level, while the latter controls some system-wide statistics. When reviewing it, first identify the level used by the connection, then check the fields set for that level. Assuming every user is level zero can overlook a different level specified in a protocol user object.
Level names are JSON object keys, so the example uses the string "0"; the level referenced in protocol settings is usually a number. A level is not a server priority or node quality score. A higher number does not improve connection performance; it selects a different set of policies. If you do not need distinct levels, keep the config simple. Adding levels that no user references can make it look as though a change has been applied when it has no effect.
A Partial Example of Connection Timeouts
{
"policy": {
"levels": {
"0": {
"handshake": 4,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5,
"statsUserUplink": false,
"statsUserDownlink": false
}
},
"system": {
"statsInboundUplink": false,
"statsInboundDownlink": false
}
}
}
These values illustrate the field structure; they are not recommended tuning settings for every network. handshake sets the allowed time for the handshake, in seconds; connIdle concerns the idle connection timeout. uplinkOnly and downlinkOnly set how long the other direction remains open after one direction has finished. These settings mainly affect connection lifetime; they cannot fix network congestion, remote server capacity or poor local Wi-Fi.
If the handshake timeout is too short, network jitter may terminate a connection that could otherwise succeed. If it is too long, apps may wait longer for a destination that is truly unreachable. Decide whether to change it based on the stage where the error occurs. If the logs show a server name or security-layer mismatch, a longer timeout will not fix the parameters. If a local port bind fails, policy is not the place to fix the inbound. Find the failure point before changing timeouts.
Keep Statistics Settings Separate from Client Display
User statistics and inbound statistics cover different scopes. Enabling a statistics setting does not automatically make the corresponding data appear in the client interface; the core's statistics module, query interface and client support may also be required. Some statistics require a recognizable user identifier. Copying the switches without confirming the collection and readout path can leave you with empty results. A lack of data in the interface does not mean traffic is bypassing the core.
A client may keep its own app state, connection history or other runtime information, which does not necessarily come from policy. When reviewing an exported config, distinguish whether the core collects data from whether the interface displays it. For temporary troubleshooting statistics, enable only what you need to answer the question. For logs kept long term, consider disk usage and the possibility that they contain destination information.
| Field Group | What It Affects | Does Not Replace |
|---|---|---|
handshake | Handshake Wait Time | Checking Remote Parameters |
connIdle | Idle Connection Cleanup | Routing and DNS Config |
uplinkOnly | Keep-Alive After One Direction Ends | App Long-Connection Design |
statsUserUplink | User Uplink Statistics | Statistics Interface and Readout Config |
Buffer-related policies should not be copied from generic “performance tuning” advice either. Connection count, app traffic patterns and device memory all affect resource use. Simply increasing buffer sizes can consume more memory without improving responsiveness. Cores may also differ in supported fields and defaults. If you encounter an unfamiliar policy field, check the documentation for the current core; its presence in an older article does not mean the current client will accept it.
To test a policy change, keep the app, outbound and network conditions as consistent as possible. Change one related group of values and save the original config. Test new connections, recovery after a short idle period and reconnection after a longer idle period. An app's keepalive mechanism may prevent the connection from becoming idle as expected. A continuous download does not test idle cleanup; observe the situation the field actually controls.
If the defaults meet your needs, you can omit the entire policy section. Omitted fields use the current core’s defaults; they are not disabled. Listing every default adds maintenance overhead and can carry old core settings into a new environment. Document why each default is overridden, not just the values, so you can reassess the change later.
Client-Generated Configs and Customization Boundaries
v2rayN is our recommended desktop client, with support for Windows, macOS and Linux. On Android, you can use v2rayNG, which primarily uses the Xray core, or v2flyNG, which uses V2Fly. Clients provide interfaces for managing subscriptions, servers, routing and settings, but runtime behavior also depends on the selected core and the generated config. Installers are available from the Client Downloads page. This guide covers config relationships; it does not provide standalone core packages or remote server details.
The key boundary in client config management is who owns the data. Subscriptions update connection details, server selection determines the current proxy outbound, routing templates define split-routing rules, and settings control inbound ports and other runtime options. The generated config combines these inputs. Direct edits may be overwritten when you switch servers, update a subscription, restart the core or change app settings. The JSON was saved; it was simply regenerated from the values in the client's source settings.
Subscription Management Is Not a Core Top-Level Key
subscription is a client-side management concept, not a generic top-level field in the V2Ray core JSON covered here. Manage subscription URLs, automatic update intervals and group names in the client's subscription settings; adding an object with the same name to the runtime file will not work. Subscription content may arrive as encoded text or a collection of protocol links. The client parses it first, then builds the protocol and transport config the core needs.
If a subscription update fails, keep your existing working connection details and check the link's validity, access path, response format and client message. Do not delete all your servers because an update failed, and do not paste a subscription URL into a public log. Automatic updates sync connection details; they do not resolve local port conflicts or fix system proxy settings. For more troubleshooting, see Subscription Update Failures and Auto-Refresh Settings.
Check System Proxy, Routing and Traffic-Capture Modes Separately
System proxy settings tell apps that follow them which local inbound to send requests to; core routing determines where requests go after entering. Enabling automatic system proxy configuration does not capture traffic from every app. Clearing system proxy settings does not necessarily stop the core from listening. Some apps keep their own proxy settings and may still use a local port. Check both the operating system and each app's network settings when troubleshooting.
In v2rayN, common related controls include subscription groups, routing settings, parameter settings and the system proxy menu. Names and locations can vary by interface. Android clients typically use system authorization to enable their traffic-capture method; desktop system proxy steps do not apply directly. When moving between platforms, carry over your requirements and connection details, then verify permissions, listening behavior and routing mode on the new platform instead of copying the desktop-generated file as is.
Some clients also offer a virtual network adapter mode to capture traffic. Its traffic coverage, permission requirements and DNS path differ from a standard system proxy. After enabling it, recheck LAN access, name resolution and compatibility with other networking software. Do not assume conclusions from HTTP proxy testing still apply. Before granting elevated permissions, confirm that the mode is necessary. If a browser can use a manual proxy, there is no need to change the capture method just to fix a mistyped port.
Keep Custom Changes Maintainable
Before setting up long-term customizations, check whether the client offers routing-rule editing, custom snippets or a complete custom config. Each has a different scope. A snippet may be merged into generated config; a complete config may bypass parts of the client's generation process. Check the current client documentation and inspect the generated result. Save the original settings and record where you made each change, which field you targeted and what behavior you expected. This makes it easier to find the source when a change is overwritten.
Before importing a complete external config, check for extra listeners, control interfaces, remote addresses and unfamiliar routing rules. A config that runs is not necessarily right for your machine. In particular, confirm that listeners have not changed from loopback to all interfaces, that the system proxy port still maps to the correct inbound, and that the default outbound is what you expect. Look up unfamiliar fields rather than blindly deleting an entire security layer or routing section just to get the core to start.
Config files and logs also have privacy considerations. Protocol credentials, full subscription links, destination domains and local paths may contain personal information. When asking for help publicly, include only the fields needed to reproduce the issue and the stage where it fails; redact sensitive values and identify the core type. Errors from different cores should not be treated as interchangeable just because their wording looks similar. Describe the environment clearly so others can determine whether the issue is syntax, compatibility or the network path.
Config Validation, Log Reading and Rollback
Validate the request path step by step: can the config load, does the inbound listen, does the app connect to it, does routing select the right outbound, is the remote connection established, and does destination resolution and access succeed? Any stage can fail, often with the same symptom: “the website won't load.” Do not change the server, DNS, routing and local port all at once. Changing several variables together makes it hard to identify the cause, even if the issue temporarily disappears.
Before you start, save a known-working copy of the client settings or config and note the current mode. Change one thing, apply the settings and restart the relevant core, then test a clearly defined destination. Saving a file without reloading it leaves the core potentially using the old config. Conversely, if changes disappear after restarting the client, check the config-generation source in the previous section instead of repeatedly editing the same temporary file.
First Distinguish Config Load Errors from Connection Errors
Load-time issues commonly include invalid JSON, incorrect field types, unsupported protocols or fields, and missing routing data resources. Start with the earliest significant error in the logs, not just the final “startup failed” message. Check commas, quotes and array boundaries near the reported line, but remember that an error may point to the position after the actual problem. Also check the end of the preceding object.
If the core has started but you cannot connect to its port, check for an address-family mismatch, port conflict or an app still using the old port. Stale system proxy settings are another common cause: after the client exits, the system may still point to a local listener that no longer exists, affecting every app that follows the system proxy. Restore the correct system proxy first, then check whether the client is running. You do not need to start by changing remote protocol settings.
Temporarily Increase Log Detail
This snippet sets the log level to info to observe connections and routing temporarily. Some clients collect logs in their own interface; others write them to a specified file. If no file path is explicitly set, the display location depends on how the core was launched. If you need more detail, increase verbosity further during controlled troubleshooting, then restore a lower level to avoid accumulating request data over time.
{
"log": {
"loglevel": "info"
}
}
For a single test, use the logs to follow the destination, inbound, outbound and failure stage. A timeout during resolution, connection setup or handshake means different things. A connection refused error usually means no expected service is listening at that address and port, or the request was explicitly rejected. For a security handshake failure, check the server name, time and protocol parameters. Do not attribute every error to an unavailable node, or mistake a normal connection-close entry for the cause of a failure.
| Symptom | Check First | Next Step |
|---|---|---|
| Core Won't Start | Syntax, fields and resource loading | Revert the most recently changed section |
| Can't Connect to the Local Inbound | Listen address, port and process status | Check the app's actual proxy settings |
| Only Some Sites Fail | Rule order and resolution results | Check the matched outbound and address family |
| Fails After Switching Servers | Generated config and custom overrides | Confirm the new outbound tag |
| Disconnects After Waking from Sleep | Network changes and stale connections | Reconnect before testing again |
System time also affects some protocols and security handshakes. If the logs show an authentication error or a certificate time issue, check the device's date and time zone before reviewing remote settings. After a network change, recheck the default network, resolver reachability and existing connections. Switching from wired to wireless does not guarantee that old connections will remain valid. Reconnecting helps distinguish an environment change from a static config issue.
When rolling back, restore the whole related change—not just the field named in the error. For example, reverting an outbound tag also means reverting its routing references; reverting a port means restoring the system and app proxy settings too. Reload the core after restoring, check the logs for any remaining startup errors, then repeat the original test. If the saved config still does not work, consider changes to the external network or remote service rather than looking for a difference that is not in the same JSON section.
A useful reproduction report includes the app tested, traffic-capture method, core type, routing category of the destination, failure stage and most recent change. There is no need to publish a full subscription or every server detail. For a troubleshooting flow focused on app settings, see the Connected but Can't Browse? Troubleshooting Checklist. To rebuild the basic setup, return to the Getting Started Guide and recheck importing connection details, choosing a mode and testing the connection.
The goal is to give every field a clear source, every rule an understandable purpose and every change a practical rollback. A short config does not need every optional section, and a long config is not necessarily more capable. Keep only the inbounds, outbounds and rules you need. Understanding defaults and core compatibility makes a config easier to read and troubleshoot than continually adding snippets of unknown origin.