01 / STRUCTURE
JSON Structure and Configuration Boundaries
Separate Client Settings from Core Configuration
In the V2Ray ecosystem, graphical clients and proxy cores have different roles. v2rayN, v2rayNG, and v2flyNG manage servers, update subscriptions, expose interface settings, and control startup. The core listens for connections, connects to servers, matches routing rules, and handles traffic. A single interface toggle may generate several JSON fields—or change the system proxy without writing to the core config at all. When troubleshooting, first identify whether the issue is in the client, operating system, or core; interface labels alone don't reveal what is actually happening.
Clients typically generate a runtime config from the selected server and global settings. The file may be stored in a working or temporary directory and regenerated on restart, so direct edits may not persist. For lasting changes, use the client's routing, DNS, or custom configuration options. Option names vary between clients; verify the config actually in use after saving rather than relying on a file that happens to be named config.json.
Top-Level Objects and References
A basic config has a single root object. inbounds and outbounds are arrays that can define multiple entry points and exits; routing, dns, and policy are objects. An inbound passes app connections to the core, routing selects an outbound, and the outbound determines whether traffic connects directly or goes through a remote server. DNS provides lookup results where needed, while policy controls timeouts or statistics for specific user levels. These components depend on one another; they aren't an independent feature checklist.
This complete starter config is for local connectivity tests: an app connects through a SOCKS inbound, and the core uses a direct outbound. It does not include a remote proxy server. Use it to check the listening address, port, and app proxy settings—not to test a remote protocol. Before running it, make sure the port is available and load it through a config option supported by your core. In a graphical client, use its custom configuration feature; don't paste the entire JSON into a server address field.
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "local-socks",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": []
}
}
Valid Syntax Doesn't Guarantee a Valid Configuration
In strict JSON, keys and strings must use double quotes. Booleans are written as true or false, and the last array item must not have a trailing comma. Ports are usually numbers; don't turn them all into strings just for consistency. Some config loaders accept comments or other extensions, but another client may not. The examples here use standard JSON without comments, with explanations kept outside the code blocks so it's clear what can be parsed.
tag is an internal reference name, not a protocol name or a label that must match the server name shown in the interface. A routing rule's outboundTag must match an actual outbound, including spelling, capitalization, and hyphens. For a complex config, map out the tags before adding protocol fields. Don't define the same key more than once in an object: parsers may handle duplicate keys differently, so they aren't a reliable way to override values.
Follow the connection path: first confirm what the inbound receives, then whether the outbound can connect, and finally check routing and DNS. Don't add fields from every section at once. Once the basic path works, add one object or rule at a time and record the expected and actual results. This makes changes easier to undo and keeps syntax errors, reference errors, and network issues from getting mixed together.
02 / INBOUNDS
inbounds: Local Listeners
The Listen Address Controls Who Can Connect
inbounds defines where the core accepts connections. Desktop clients commonly use SOCKS and HTTP proxy inbounds; apps must connect to the specified address and port using the matching protocol. Setting listen to 127.0.0.1 allows access only from the same device over the IPv4 loopback interface. This is neither a server address nor the address of a remote proxy node. For apps on the same device, loopback listening is usually the easiest way to limit access.
Setting the listener to 0.0.0.0 means listening on all local IPv4 interfaces. It does not open every system firewall rule, or guarantee that other devices can reach the port. For LAN access, also check the device's IP address, inbound firewall rules, network isolation, and authentication settings. A loopback address always refers to the device making the connection; when configuring a phone to use a computer's proxy, use the computer's LAN address, not the phone's loopback address.
SOCKS and HTTP Inbounds: Different Roles
Use this snippet to replace the basic config's inbounds array. The two inbounds use separate ports and tags; the existing outbound and routing settings remain unchanged. SOCKS has UDP support enabled, but the app still needs a way to send proxied UDP requests. The HTTP inbound is for apps that only support HTTP proxy settings. Having both available doesn't make an app switch between them automatically.
[
{
"tag": "local-socks",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}
},
{
"tag": "local-http",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
]
| Field | Purpose | What to check |
|---|---|---|
listen | Local listen address | Loopback vs. LAN access |
port | Listen port | Port availability and matching client settings |
protocol | Inbound protocol | Don't mix up SOCKS and HTTP |
tag | Inbound tag | Referenced by inbound-based routing conditions |
sniffing | Protocol and destination sniffing | Whether domain matching is actually needed |
Sniffing Is Not a Replacement for DNS
sniffing can extract domain clues from some connection protocols, such as an HTTP hostname or the server name in a TLS handshake. It can't reliably recover the original domain for every connection, and it doesn't decrypt app traffic. How sniffed targets are handled also depends on fields supported by the core, so enabling sniffing doesn't guarantee that every domain rule will match. If a connection only exposes an IP address, first check how the app resolved the domain.
destOverride lists the protocol types that can be used for destination overrides. Overriding a destination isn't always the same as using it only for routing; some cores provide additional fields to control this behavior, and those extensions aren't universal. If a service depends on the original destination address or has special connection handling for a shared IP, test changes to sniffing separately. Without a clear need, start with the fewest sniffing types to avoid adding unnecessary variables.
Ports, System Proxy, and Traffic Capture
A port-in-use error occurs while the core is starting its listener; it has nothing to do with whether the remote server is online. Close any duplicate client processes, check which ports are in use, then decide whether to change one. When you change an inbound port, update the corresponding setting in browsers, developer tools, and the system proxy. If the core uses the new port but the system proxy still points to the old one, apps may get connection-refused errors. Repeatedly updating the subscription won't fix that mismatch.
The system proxy tells apps that follow system settings to use a particular proxy inbound; it doesn't guarantee that every app's traffic passes through the core. TUN-based capture also involves a virtual interface, system routes, and permissions, so adding a SOCKS inbound isn't a substitute. Android traffic capture likewise depends on both the client and platform mechanisms. To confirm that traffic reaches the core, check actual connection logs rather than just the system proxy toggle.
To verify an inbound, use one test app and a known reachable destination. First check whether a connection appears, then whether it uses the expected outbound. If there's no connection record, check whether the app uses a proxy, whether the protocol and address match, and whether it has cached old settings. If a connection does appear, move on to outbounds and routing. For first-time setup steps, see Getting v2rayN set up.
03 / OUTBOUNDS
outbounds: Protocols and Tags
Outbounds Define the Next Hop
outbounds determines how the core handles connections it has received. freedom connects directly to the destination, while blackhole blocks traffic. Proxy protocols such as VLESS send connections to a configured remote server. An outbound isn't a routing rule: multiple rules can reference the same outbound, and one rule can match multiple domains and send them all through the same exit. Keeping protocol settings separate from traffic classification makes both easier to maintain.
In a typical config, traffic that matches no routing rule uses the first outbound in the array as its default exit. Array order therefore matters. Putting direct first doesn't just change the display order; it can change how all unmatched traffic is handled. Decide on the default behavior before adding exceptions. For a proxy-by-default setup, put a usable proxy outbound first. For direct-by-default, keep the direct outbound first.
Test Direct Connections and Blocking Separately
Use this array to test direct connections and blocking; replace the basic config's outbounds. Both tags can be referenced by routing rules, and direct remains the default because it's first. blackhole actively drops connections; it isn't a backup route and won't look for another available server. If you use it for a test domain, a failed connection is expected. Check the routing log to confirm the rule matched rather than treating the result as a client failure.
[
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
VLESS Settings Must Match the Server
This shows the structure of a VLESS outbound; it is not a public server you can connect to. your-server.example is a documentation-only domain, and the example UUID is for formatting purposes only. Replace both with valid server details you are authorized to use. This object combines TCP and TLS, and the remote server must offer the same combination. Adding it to the array won't make an existing server configured for a different protocol accept VLESS connections.
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "your-server.example",
"port": 443,
"users": [
{
"id": "00000000-0000-4000-8000-000000000001",
"encryption": "none",
"level": 0
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "your-server.example",
"allowInsecure": false
}
}
}
address is the server address, port is its listening port, and id is the protocol user ID. The VLESS user's encryption setting is separate from the transport security layer; setting it to none does not disable outer TLS. Transport security is controlled by fields such as streamSettings.security. Don't treat similarly named “encryption” settings as interchangeable, or the config may import successfully but fail to connect.
A user's level can be linked to policy.levels; it is neither a node priority nor a routing rule order. Extension parameters such as flow should only be used when supported by the server, core, and transport combination. Don't add a value to every server just because it appeared in an imported link. After importing, check the address, port, user ID, transport, and security layer, and pay attention to warnings about unknown fields.
Evaluate the Protocol and Client Separately
A client's name doesn't tell you which protocols it supports. For desktop, v2rayN is the recommended choice. On Android, choose v2rayNG or v2flyNG based on the core you need. Support for an extension ultimately depends on the core and config combination, not on whether a protocol name appears in the client notes. For help choosing, see How the three clients differ by core. All clients are available from the downloads page.
Break outbound failures into three categories: the server is unreachable, protocol authentication failed, or the transport handshake failed. An incorrect address or port usually fails while establishing the underlying connection. A mismatched user ID fails at the protocol stage, while an incorrect TLS name is a security-layer issue. Use the logs to identify the stage, then check the relevant fields. Changing the address, user ID, and transport all at once may restore the connection, but won't reveal what caused the failure or make the config easier to maintain.
04 / STREAM SETTINGS
Transport and TLS Security
Separate the Protocol, Transport, and Security Layer
The proxy protocol defines how the two sides exchange proxy requests. The transport defines the channel that carries the data, while the security layer handles the handshake, authentication, and encrypted connection. These settings usually appear together in an outbound, but not every combination is valid. Two VLESS servers in the same interface may require different transports and security layers. Copying one server's transport settings to another can cause a failure before protocol authentication even begins.
streamSettings.network selects the transport type; its corresponding settings object holds the details. TCP is relatively straightforward, while WebSocket usually needs a path and may also require an HTTP hostname. If the remote server sits behind a reverse proxy, the public port and path depend on the server deployment. Every client value should be based on the server's actual settings; guessing common paths is no substitute for checking them.
WebSocket Example: Field Structure
This object shows a common WebSocket and TLS combination. Replace the entire streamSettings object inside the outbound; don't replace the top-level config. The domain and path are examples for documentation. address remains in the protocol's server object and determines the actual connection destination. The hostname, server name, and path here are for the upper-layer handshake. Enter each value required by your deployment; don't assume all three must match.
{
"network": "ws",
"security": "tls",
"tlsSettings": {
"serverName": "your-server.example",
"allowInsecure": false
},
"wsSettings": {
"path": "/proxy",
"headers": {
"Host": "your-server.example"
}
}
}
| Field | Layer | Purpose |
|---|---|---|
address | Protocol server object | Determines the underlying connection destination |
serverName | TLS settings | Server name and certificate name verification |
Host | WebSocket request headers | Selects the HTTP virtual host |
path | WebSocket settings | Matches the server-side request path |
security | Transport settings | Selects the transport security layer |
For Certificate Errors, Check the Name and Validity Period
A TLS connection verifies that the certificate covers the expected name and is still valid. A badly wrong system clock, a mismatch between the server name and certificate, or an incomplete certificate chain can all cause a handshake failure. If you connect by IP and the certificate was issued for a domain, enter the correct server name as required by the server. The server address, HTTP hostname, and certificate name aren't interchangeable, even if they happen to use the same value.
allowInsecure controls whether certain certificate checks are skipped; it doesn't fix a misconfigured certificate. This example keeps it set to false. For a certificate error, first sync the system clock, then check the server name, certificate coverage, and server certificate chain. Disabling verification long-term changes how the connection authenticates the server and can hide an incorrect name. See TLS handshake and certificate errors for troubleshooting steps.
Limits of Extensions and Connection Multiplexing
REALITY is a security layer supported by specific cores, not another name for standard TLS. Its server name, public key, and related parameters must be set together according to the server's requirements; changing only the security value isn't enough. Similarly, some transport names and extension settings differ between cores. Confirm the implementation and supported fields before migrating a config. If the core reports an unknown field, don't simply delete it and assume the behavior will remain the same.
Mux attempts to reuse an underlying connection under certain conditions. It isn't a traffic-routing feature and can't fix incorrect protocol settings. Whether to enable it depends on the protocol, server support, app connection patterns, and core limitations. When troubleshooting a single-connection failure, leave multiplexing off until the basic connection is stable, then test it separately. Don't confuse Mux with multiplexing already provided by the transport, and don't change Mux, DNS, and routing all at once.
When troubleshooting transport, keep the same server and test destination, then check each stage in order: underlying connection, TLS handshake, transport handshake, and proxy request. A connection timeout usually calls for checking the address and reachability first. A certificate error points to the name and certificate. A WebSocket path mismatch calls for checking the request forwarding rules. Logs indicate where a failure occurred, but aren't the whole story; verify them against the server settings and the config the client actually generated.
05 / ROUTING
routing: Rule Matching and Outbound Selection
Rule Order and the Default Outbound Matter More Than Rule Count
routing.rules are checked in the order they appear. Once a rule matches, the core typically uses its specified outbound rather than continuing to search for a “more specific” domain rule. A broad rule near the top can prevent later, more specific rules from ever matching. Put clear exceptions first, broader categories after them, and a well-defined default outbound last. More rules don't guarantee better coverage; unclear ordering just makes the config harder to understand.
Within a rule, different condition fields usually all need to match. Multiple candidate values in one condition array, however, are alternatives for that field. For example, setting both inboundTag and domain means the connection must come from the specified inbound and match the domain condition—not either one or the other. For two independent exceptions, separate rules are usually easier to inspect than a complex condition.
Domain Strategy and Domain Matching Are Different Settings
domainStrategy controls when routing resolves a domain to an IP address. AsIs keeps the target in its current form. IPIfNonMatch attempts DNS resolution and IP rule matching when no domain rule matches. IPOnDemand resolves the domain when the matching process needs IP information. This setting doesn't force all apps to use a particular DNS resolver, and it can't recover a domain an app has already resolved. Choose a strategy with the actual form of the incoming target in mind.
This object can replace the top-level routing section, provided the outbounds include direct. Private addresses are shown as standard IP ranges for clarity, and the domain exception uses a reserved example domain. Traffic that matches no rule still uses the first outbound. If the basic config only has direct, this example won't send traffic through a proxy. To change the default, add a usable outbound for the behavior you want.
{
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"ip": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
],
"outboundTag": "direct"
},
{
"type": "field",
"domain": [
"full:example.com",
"domain:example.org"
],
"outboundTag": "direct"
}
]
}
Prefixes Define the Matching Scope
full:example.com matches the exact domain. domain:example.org matches the domain and its subdomains. regexp: introduces a regular expression, where backslashes must be escaped in JSON strings. A string without a prefix may use keyword-matching behavior defined by the core, so it isn't a good substitute for an exact domain when scope matters. To target a specific host, start with a clear prefix and add subdomains only when actual requests require them.
geosite: references a domain category resource, while geoip: references an IP category resource, as in geosite:cn and geoip:private. These aren't online lookup commands: the runtime environment must have the relevant data files, and those files must include the requested categories. Category names, data contents, and update status can all affect the result. If a category is missing or a rule fails to load, check the resources and core rather than blaming the destination website.
Build Rules from the Request Conditions
When debugging, record the inbound tag, whether the target is a domain or IP, the destination port, and the expected outbound. Then check each rule from the top. If a domain rule doesn't match, confirm the core received the domain. If an IP rule doesn't match, check when resolution occurred, the returned address family, and the IP range. Don't add a broad rule without evidence: it may hide the issue temporarily while changing the outbound for other connections.
Changing a rule affects outbound selection for new connections; existing long-lived connections may continue using their original route. Close relevant app connections or start a new request before testing, so an old connection isn't mistaken for an unapplied rule. For more syntax and ordering examples, see Writing custom routing rules. For terminology, use the glossary to understand the differences between domain categories, IP ranges, and outbound tags.
06 / DNS
dns: Resolvers and Query Paths
First Identify What Is Making the DNS Query
A single app request may involve several DNS lookups: the app resolves the destination domain, the OS resolves the server address, the core resolves the destination for routing, or the outbound resolves it while connecting. The top-level dns section only affects relevant operations that use the built-in resolver; it doesn't automatically capture every app's DNS requests. A browser or other app using its own encrypted DNS may take a separate path, so changing the core DNS settings alone doesn't tell you what the app will do.
Before troubleshooting, define the exact problem: is the proxy server's domain failing to resolve, or the destination website's domain? Is resolution failing, or does it return an address that can't be reached? Is the problem with system resolution or only with routing matches? Each case calls for different checks. Resolving a server domain is a prerequisite for connecting to the proxy. The destination domain, by contrast, may be resolved locally or remotely; don't lump both into a single “DNS is down” diagnosis.
hosts, servers, and Address Families
This example shows the structure of top-level dns. hosts maps a documentation domain to a reserved example address; it isn't a reachable service. The two numeric resolver addresses demonstrate the server array format. In practice, choose resolvers that are reachable from your network and suit your needs. Adding this object only has an effect if the config also triggers the relevant built-in resolver behavior.
{
"hosts": {
"example.test": "192.0.2.10"
},
"servers": [
"1.1.1.1",
"8.8.8.8"
],
"queryStrategy": "UseIP"
}
hosts is useful for explicit, stable mappings—not for permanently pinning business addresses that change often. A destination may have several addresses, use regional routing, or change its certificate and service endpoint; an outdated mapping can bypass normal DNS results. Remove temporary mappings after troubleshooting and check for overrides from the system hosts file or other client DNS settings. Otherwise, requests may keep using an old static result even after you change the resolver.
queryStrategy controls which address families are queried. Common values include UseIP, UseIPv4, and UseIPv6; check support in your core. This isn't an OS switch to enable or disable IPv6. Returning an IPv6 address doesn't mean the network can establish an IPv6 connection. Similarly, querying only IPv4 won't fix connectivity to the proxy server itself. Check that the address family and route actually work before changing the query strategy.
DNS Queries Also Depend on Routing
The path taken by DNS queries from the core depends on the resolver type, core implementation, and relevant routing settings. Standard remote queries, system resolver calls, and some resolution modes with local semantics don't necessarily follow the same routing rules. Encrypted DNS also depends on resolving the resolver's hostname, verifying its TLS name, and reaching it. Seeing an encrypted DNS address format doesn't tell you which outbound the query ultimately uses.
Watch out for dependency loops: the proxy server uses a domain, but its DNS query is routed through that same proxy before the connection exists; or the encrypted resolver's domain can only be resolved by the resolver itself. Provide a reachable path for startup resolution, using explicit bootstrap DNS or a server-approved connection address if needed. Don't point every DNS-related address to the proxy outbound and expect the core to resolve the circular dependency automatically.
| Symptom | Check first | Don't assume |
|---|---|---|
| Proxy server domain fails to resolve | Startup resolution path and system network | A routing problem for the destination website |
| Domain resolves, but the connection times out | Returned address, address family, and outbound | The resolver is definitely broken |
| Old address still returned after a change | App, system, and core caches | The new settings weren't saved |
| Only some apps are affected | The app's own DNS and proxy settings | All traffic uses the same DNS resolver |
Check Caches and Routing Together
DNS results may be cached by the app, OS, core, or upstream resolver. After changing the config, an app may still use a cached result or an existing connection—even if you restart the core. For testing, use one fixed domain, record the returned address and outbound, then check each possible cache source. Don't clear every setting and change several resolvers at once; that makes before-and-after results impossible to compare. With a multi-address service, a different result from a single query doesn't necessarily indicate an unstable config.
When domain and IP rules are both present, treat DNS as an input to routing, not a standalone optimization toggle. A domain rule may match before local resolution is needed; if the core moves on to IP matching, the DNS result can change the selected outbound. When designing split routing, specify both when resolution happens and how the result is matched. If you're fixing one problematic domain, test that case first before applying changes globally.
07 / POLICY & LOG
policy: Connection Policies, Logs, and Statistics
User Levels Are Policy Indexes
policy defines settings related to connection handling and statistics. levels is indexed by user level, and the protocol user's level can link to a corresponding policy. This level isn't an account rank, server performance rating, or routing priority. The settings only take effect when a protocol connection uses that level; filling in an unused level won't change every connection.
In a JSON object, a level key is written as a string, such as "0", while the protocol user's level field is usually a number. The names look similar, but their types and locations differ. If you maintain multiple levels, document which objects use each one and why its settings changed. Most everyday client setups rarely need policy changes; keeping defaults is easier to troubleshoot than applying unexplained “universal optimization” values.
Timeout Fields Apply at Different Stages
This object shows common policy fields and can serve as a reference for top-level policy. Values are in seconds and are examples, not universal recommendations. handshake sets the time budget for the relevant handshake stage. connIdle concerns idle connections, while uplinkOnly and downlinkOnly handle stages where only one direction remains active. These aren't all variants of a single overall connection timeout; their exact effect depends on the protocol implementation.
{
"levels": {
"0": {
"handshake": 4,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5,
"statsUserUplink": false,
"statsUserDownlink": false
}
},
"system": {
"statsInboundUplink": false,
"statsInboundDownlink": false,
"statsOutboundUplink": false,
"statsOutboundDownlink": false
}
}
An idle timeout that's too short can make interactive terminals, notification connections, or temporarily inactive sessions reconnect more often. One that's too long may keep unused connections open. Apps also have their own heartbeat, read/write timeout, and reconnect logic, so compare app settings with core logs when investigating regular disconnects. Don't assume the cause just because the interval is close to a policy value; network equipment and remote servers can have independent timeouts.
For a failed handshake, check the protocol, address, and security settings before increasing the timeout. Extra waiting won't fix mismatched parameters. A longer time budget may help on a genuinely slow network, but verify it with repeatable tests. Change one field at a time and note when the handshake starts and where it fails. If the issue occurs before the transport handshake, changing an idle timeout for the app data stage is unlikely to help.
Log Level Controls Detail
Logging is configured in the top-level log section, not in policy. Use warning for everyday operation and switch temporarily to info or a more detailed level for troubleshooting. The object below sets only the logging detail; output location depends on the client and how the core runs. If no disk path is specified, don't assume logs were saved in a particular directory. Check the client's log panel or its documented log file option first.
{
"loglevel": "warning"
}
Connection logs may include destination domains, IP addresses, inbound tags, and error details. Before sharing logs publicly, remove subscription URLs, user IDs, personal file paths, and other unnecessary information. To preserve diagnostic value, replace sensitive names with consistent example labels while keeping the failure stage, field types, and tag relationships. A single line saying “connection failed” is rarely enough; preceding startup and handshake details matter too.
Enabling Statistics Doesn't Create a Dashboard
Statistics toggles in a policy determine whether the corresponding counters are collected; they don't create charts in the client. Reading those counters also requires supporting core statistics, an API, and client support. Some per-user statistics rely on identifying protocol users, so enabling system statistics won't automatically provide separate data for every app or server. If you only need to diagnose a failed connection, start with the logs; you don't need to enable a full API setup.
Logs and statistics are diagnostic tools, not prerequisites for a successful connection. A bad policy change can make troubleshooting harder, and the absence of charts doesn't mean the proxy isn't running. Verify real requests after configuring the client, then choose a logging level that suits your maintenance needs. If the client already offers statistics, use its existing feature rather than exposing an unnecessary management API. When troubleshooting is done, restore the normal log level and remove temporary diagnostic files you no longer need.
08 / APPLY & DIAGNOSE
Applying Config Changes, Managing Subscriptions, and Troubleshooting by Layer
Use a Change Process You Can Roll Back
Before applying a new config, save the last working client settings and a copy of your custom config. Record which fields are changing and what you expect them to do. Keep backups somewhere identifiable, separate from temporary runtime files. Restoring the client may also require restoring the selected server, system proxy, routing mode, and DNS settings; replacing a JSON file alone may not restore the full client state. The clearer the scope of each change, the more reliably you can roll it back.
The effective config may come from several layers: subscriptions provide server details, global client settings provide inbounds and routing templates, and custom config may override some fields. Check whether the client starts with a standard server config or a full custom config, and identify which source takes precedence. If the startup mode changes, the same interface toggle may no longer affect the running config. Don't keep editing an unused custom file and then assume the core ignored the changes.
A Subscription Isn't the Full Runtime Config
Subscriptions usually distribute server entries or share links; they don't necessarily include local listeners, system proxy settings, routing, and DNS policies. After a subscription update, server names, addresses, and optional parameters may change. Whether local custom routing is preserved depends on the client. Treat the subscription URL as sensitive and keep it out of public screenshots, articles, and support discussions. A successful import only means the client recognized the content; it doesn't prove every server parameter is suitable.
When syncing across devices, keep shared server sources separate from device-specific settings. A computer and phone may use the same valid subscription, but they don't need the same listen ports, traffic capture method, or network permissions. Don't copy a complete desktop runtime config directly to a mobile client. To migrate, sync the server entries first, then check inbounds, routing, and DNS for each device. See How to sync subscriptions, share links, and exported configs for a walkthrough.
Find the Failure by Connection Stage
Start with config loading and startup. For a JSON parse error, check nearby quotes, commas, and brackets. For an unsupported field, check which core is running and where the field belongs. For a listener error, check port conflicts and permissions. All of these failures happen before a proxy request reaches a remote server, so there's no need to change server settings yet. After fixing the issue, confirm the core stays running before moving to the next stage.
Next, check whether the request reaches the core. Use a test app with proxy settings configured, send a new request, and check the logs. If nothing appears, verify the app's proxy protocol, loopback address, and port, and check whether the system proxy still points to an old inbound. If a request does appear but uses the wrong outbound, check rule order, inbound tags, and the target format. Reinstalling the client won't replace confirming the request's actual path.
| Stage | Typical symptom | Check first |
|---|---|---|
| Load config | Exits immediately on startup | JSON syntax, field support, tag references |
| Start listener | Port binding fails | Duplicate process, address, permissions |
| Connect app | No request appears in logs | App proxy settings and system traffic capture |
| Select outbound | Unexpected outbound selected | Rule order, target format, default outbound |
| Connect to remote server | Timeout or handshake error | Server address, protocol, transport security |
| Reach destination | Only some services fail | DNS, address family, destination rules, and app behavior |
Then check the remote server and destination. If the server connection fails, verify its address, port, and startup DNS. For a handshake failure, check the protocol user, transport path, and certificate name. If the server connects but only some destinations fail, check destination DNS, routing rules, and address family. Test more than one destination to distinguish a service-specific issue from a global config problem, keeping all other variables constant each time. One successful request proves only that the current path works—not that every rule is correct.
Verify Changes and Clean Up
At minimum, test the local inbound, default outbound, a clearly defined direct exception, and a test condition that should be blocked. If your config has no blocking rule, there's no need to add one just for testing. For each check, record the input conditions and expected outbound, then verify with a new connection. Long-lived connections and cached results can preserve old behavior, so start a fresh test session. After confirming the config is saved, restart the client normally and check that the runtime config still reflects your changes.
Restore the normal log level, remove temporary hosts mappings and test rules, and keep a brief record of your changes. For first-time setup, return to the getting started guide for the client workflow. For field details, work through this page section by section. Good config maintenance isn't about accumulating toggles; it's about keeping the relationships between inbounds, DNS, routing, and outbounds clear, verifiable, and easy to roll back when something fails.