01 / JSON Structure and Config Ownership
Separate Client Settings from Core Config
Find out where the active config comes from before deciding what to edit. v2rayN, v2rayNG, and v2flyNG are graphical clients that manage subscriptions, select configs, and start the core. The core handles incoming connections, matches routing rules, and forwards traffic. Subscription URLs, window preferences, and update schedules saved in the client are not necessarily part of the core's JSON. Editing a client settings file as if it were the core config can leave you with valid-looking fields and no change in behavior.
Next, check whether the client generates the config automatically. Switching nodes, changing routing, or restarting may cause the client to rewrite its runtime config. Direct edits to a temporary file often last only until the next generation. For persistent changes, use the client's graphical settings whenever possible. If you need custom JSON, use an entry point the client explicitly supports and keep a copy of the original file. Exporting, viewing, and editing are different operations; seeing JSON does not mean changes made there will persist.
Understand the Structure Through Objects and Arrays
Think of the top level as a set of objects with different responsibilities. inbounds and outbounds are arrays because you can have multiple entry points and outbounds. routing, dns, and policy are generally objects containing their own settings. Objects use curly braces; arrays use square brackets. Field names and strings use double quotes, while numbers and booleans do not. Write ports as numbers and switches as true or false—not as strings.
This standalone local direct-connection example illustrates the structure. It exposes a SOCKS listener on the local machine and sends received connections directly through the system network; it does not create a remote proxy connection. Point the app you want to test at the local port, then check whether the core receives the request. The example has no remote server settings and cannot replace a subscription config. When using it in a client, avoid conflicts with ports the client already listens on.
{
"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": {}
}
]
}
Check Syntax, Then Check What the Fields Mean
Start by checking JSON syntax in a local editor. Common mistakes include a trailing comma after the last item, curly quotation marks copied from formatted text, or a missing brace after deleting an object. The examples on this page use standard JSON without comments. If an editor can open a file, that only means the text is readable; passing a syntax check only means the structure is valid. You still need to verify that the core recognizes the fields, the protocol is supported, and the destination is reachable. Do not treat these as a single all-clear check.
Keep a brief record of each change: which object you edited, what kind of connection should change, and how to roll it back. Change one layer at a time instead of altering DNS, routing, and transport together. When fields look duplicated, compare their paths. A resolution strategy in the routing object and one in an outbound object may apply at different stages. Identical field names do not mean identical behavior; check the parent object along with each field.
02 / Inbounds and Listening Scope
First, Check How Apps Reach the Core
An inbound is where the core accepts connections. First check whether the app uses the system proxy, its own SOCKS settings, or a virtual network interface managed by the client, then inspect the matching config. Desktop apps commonly connect to the core through a local HTTP or SOCKS port. On Android, VPN mode routes traffic through the client and the system interface; a SOCKS port in the JSON alone does not mean every app uses that inbound. If the inbound receives no traffic, changing the outbound usually will not help.
Check listen, port, and protocol one by one. The listen address determines which network interface accepts requests, the port identifies the local endpoint, and the protocol determines the format it accepts. Do not point an HTTP proxy setting at a port that only accepts SOCKS. If the client shows a “mixed port,” check whether that is a client feature combining protocols or a separate inbound in the core config. Do not treat a UI label as a universal protocol name to enter in JSON.
| Field | What to check | Common pitfall |
|---|---|---|
tag | Give each inbound a unique name so routing rules can reference it. | The name alone does not enable a mode. |
listen | Set the local address that accepts connections. | Binding to all interfaces expands who can connect. |
port | Use a local port that is not already in use. | The app's proxy settings do not match the listen address or port. |
settings | Use fields supported by the inbound protocol. | Copying fields from a different protocol unchanged. |
Separate Local Access from LAN Access
For use on this device only, bind to 127.0.0.1. This is the IPv4 loopback address; other devices cannot use it to reach your proxy. Sharing on a LAN usually also requires a suitable listen address, operating system firewall rules, inbound authentication, and network connectivity between devices. Enabling “allow connections from the LAN” alone does not guarantee access for other devices, nor does it automatically authenticate them.
This snippet shows one HTTP inbound object to use as an item in the inbounds array. It uses a different port from the SOCKS listener above, making it easier to distinguish in app settings. The port is a local convention, not a remote server port. If the app sends no requests, first check whether its system proxy still points to an old value, then confirm that the client actually loaded this object. A commonly used port number does not mean a service is already listening on it.
{
"tag": "local-http",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
Understand the Limits of UDP and Sniffing
The udp switch on a SOCKS inbound allows that inbound to handle SOCKS UDP requests. It does not automatically route every app's UDP traffic through the core. The app must support the proxy method, and the outbound and network path must also allow forwarding. With only an HTTP system proxy configured, some real-time communication or game traffic may still take its original route. If so, check which proxy methods the app supports, then read the TUN mode guide instead of repeatedly changing the listen port.
Sniffing identifies destination information from the start of a connection and is commonly used to help with domain-based routing. It does not decrypt all app traffic or replace DNS resolution. Supported sniffing fields, target protocols, and routing-only options vary between cores, so check the implementation before adding them. If an app misbehaves after sniffing is enabled, leave the other settings unchanged and test with sniffing disabled. Judge the result by the affected connection, not by a single overall speed test.
Finally, check the logs to confirm that the inbound bound successfully. If the address is already in use, check whether another client instance is running, an old process is still active, or the client has already created an inbound on the same address and port. Do not switch to listening on all interfaces just to bypass a conflict. If you change the port, update the app's proxy address too. For loopback connections, note the difference between IPv4 and IPv6: localhost may resolve to different addresses first on different systems.
03 / Outbounds and the Default Route
Find the Outbound Actually Being Used
An outbound determines what the core does with a request after receiving it. Common types connect directly to the destination, connect to a remote proxy, or block the request. First check whether routing sends the traffic to a specific tag, then inspect the corresponding outbound object. A remote proxy outbound in the list does not mean every connection uses it: a rule may match a direct outbound first, or a mistyped tag may prevent the config from loading. When troubleshooting, distinguish between an object existing and an object being used.
When no routing rule matches, a common default is the first item in the outbounds array. Reordering the array can therefore change where unmatched traffic goes. Do not move a direct outbound to the top just to make the list easier to read. Clients usually generate the order for their own modes; with a custom config, you need to choose the default outbound deliberately. Documenting the default is more reliable than guessing from a tag name—a tag called “proxy” can point to a direct outbound.
Distinguish Direct, Blocked, and Remote Protocol Outbounds
freedom is a common direct outbound protocol; blackhole blocks traffic. Neither requires a remote account, but either only takes effect when selected. Blocking also does not always look the same: an app might show a connection error, keep retrying, or try another address. A page eventually loading does not prove the block rule failed. Check which connection was blocked and whether the app made a subsequent request.
This complete outbound array snippet is for local routing experiments. It includes only direct and block outbounds, with no remote proxy capability. tag is referenced by routing rules; the protocol name tells the core which implementation to use. They are not interchangeable. If you use outboundTag in a later chapter, it must match one of these tags exactly. Case, hyphens, and extra spaces can all break a reference, so search every reference when renaming a tag.
{
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
}
A remote proxy outbound needs the server address, port, and authentication parameters for its protocol. VMess, VLESS, and Trojan have different authentication structures; do not reuse a user object from one protocol in another. Subscription-generated configs may also include flow control, encryption, security, and related fields. First confirm that the original connection details are complete in the client, then inspect the generated object. If the source details are missing fields, changing routing will not supply the authentication information the server requires.
A Protocol Name Is Not the Whole Connection
The same proxy protocol can use different transport methods and security layers. Two configs that both say VLESS may still be incompatible because of differences in transport, service name, TLS server name, or security parameters. The server address is used to establish the connection; the certificate name is used to verify the remote identity. They may or may not be the same. Enter each value according to the service details. Do not use one domain for every field that looks similar, or put a subscription group name where a network address belongs.
Multi-outbound setups also need to avoid connection loops. If a proxy outbound's connection to the remote server is sent back through the same inbound by a client's global traffic capture, the connection may loop. Graphical clients usually handle details such as their own process, server addresses, or bound interfaces; do not casually remove these safeguards from a custom config. If connection counts keep rising or logs repeatedly show the same address, disable the new chain forwarding and global capture settings first, then verify with a single outbound.
When a remote server is unreachable, check the address, port, authentication, transport, and security layer in that order. A successful TCP connection only proves that the basic network path is reachable; it does not confirm the proxy handshake succeeded. Latency tests and real web requests may also use different destinations. To compare metrics, see the guide to interpreting node speed test results. Confirm what was measured before drawing conclusions; a low latency result does not prove the entire outbound config is correct.
04 / Transport Settings and Security Layers
Check the Protocol, Transport, and Encrypted Connection Separately
Start by separating three questions: how the proxy protocol handles authentication and requests, how the transport carries that data, and how the security layer verifies and protects the outer connection. Clients often put these options on the same edit screen, but they belong to different config layers. A correct proxy protocol with the wrong transport still cannot connect; a working transport with incorrect authentication will be rejected later. When troubleshooting, identify the layer where the failure occurs rather than calling every failure an unavailable node.
In common configs, streamSettings groups transport and security options. It belongs inside the relevant inbound or outbound object; placing it at the top level does not make it a global switch. Supported transport names and their specific fields vary by core, and the same technology may use different config formats at different stages. Keeping the structure generated by the current client and checking it against the current core documentation is safer than replacing the entire object with one from an old article.
Check TLS Names and Verification One by One
TLS provides mechanisms for establishing an encrypted connection and verifying the remote identity. First check the security type, then confirm that serverName matches the service's settings. This name is usually used for Server Name Indication and certificate verification; it is not a free-form note. Using an IP address for the connection does not mean the verification name must also be that IP. If the remote certificate was issued for a domain, changing the name arbitrarily can make a reachable service fail during the handshake.
This snippet shows a common TLS settings object to illustrate the field structure. example.com is a documentation example and must be replaced with the actual service settings; it cannot establish a proxy connection as written. No transport type is specified because it must match the service. With allowInsecure disabled, certificate verification is not deliberately skipped. Do not make disabling verification your standard fix: it can hide name mismatches, certificate chain problems, or an incorrect device clock.
{
"streamSettings": {
"security": "tls",
"tlsSettings": {
"serverName": "example.com",
"allowInsecure": false
}
}
}
For certificate errors, first check the device's date and time zone, then verify the name, server address, and network conditions. A clock that is off can make a certificate appear not yet valid or expired. Some networks also require portal sign-in first, so a request may reach a login page instead of the target service. Once these basics check out, inspect the specific handshake error in the core logs. Being able to open a site in a browser does not prove the proxy service's TLS settings are correct.
Match Paths and Service Names to the Transport
With WebSocket, the path and related request headers may affect server-side routing; with gRPC, the service name is a different parameter. They are not interchangeable, and you cannot infer their values from a UI label such as “host.” Preserve the leading slash and case when copying paths, and check whether an empty or omitted field has a different meaning. The server address, HTTP host field, and TLS name may each serve a different purpose. Do not force them to match just to make the config look consistent.
REALITY is a security option supported by specific cores. VLESS support in another core does not mean all related fields are portable. Match settings such as public keys, short IDs, and flow control to the server agreement and the current core implementation, one by one. No assembled remote config is provided here, to avoid combining parameters from different connection profiles into something that looks complete but will not work. After importing a profile into the client, test it unchanged before making individual adjustments.
Also distinguish transport settings from connection multiplexing. Multiplexing changes how multiple app connections share an underlying channel; it does not fix an incorrect certificate name, transport path, or authentication setting. To isolate variables, verify the original connection first, then decide whether to enable a compatible multiplexing option. Some protocol, flow-control, and transport combinations have additional restrictions; do not copy a single switch from another config. If intermittent disconnects begin only after enabling it, test multiplexing as a separate variable by turning it off.
05 / Routing Rules and Match Order
Read the Rules from the Top Down
Routing selects an outbound for each connection. Read rules in array order, find the first rule that matches, then check which outbound it points to. The rules are not a checklist where every condition is applied in turn. A broad rule near the top can prevent more specific rules below it from ever matching. Before adding a rule, decide which connections it should affect and where it belongs instead of automatically putting it at the end.
Different conditions in one rule usually narrow the match together—for example, both the inbound tag and destination domain must match. Multiple candidates in the same condition array usually mean any one of them can match. Confirm the exact behavior for the core in use. When starting out, test one condition against the target, then add constraints gradually. A rule that specifies the inbound, port, protocol, and domain all at once may look precise, but makes it much harder to find why it did not match.
Start with Rules That Need No Extra Resources
This routing object can be merged into the local example in Chapter 1, but it also requires the block outbound from Chapter 3. It sends the documentation example domain to the block outbound and the listed private IP ranges to the direct outbound. Other traffic keeps the default outbound from the existing config. The example needs no external domain classification files, making references and rule order easier to understand. It is not a complete list of LAN address ranges and should not be used as a production routing policy as-is.
{
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["full:example.com"],
"outboundTag": "block"
},
{
"type": "field",
"ip": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
],
"outboundTag": "direct"
}
]
}
}
Choose the domain matching method deliberately. full: matches an exact domain; domain: is commonly used for the domain and its subdomains; regular expressions suit more specialized patterns. A plain string does not have the same meaning as a prefixed form, so confirm the intended match before writing a rule. Controlling a site may also require accounting for separate domains used for static assets, login, and APIs. Matching only the homepage domain may not cover all connections the app makes.
Separate Routing-Time Resolution from Final Connection Resolution
domainStrategy determines how routing handles destination domains. Commonly, AsIs does not resolve a domain just to apply IP rules; IPIfNonMatch tries to resolve it if no domain rule matches, giving IP conditions a chance to match; IPOnDemand tries to resolve it when the matching process encounters a rule that needs an IP. These options control routing decisions, not a global DNS outbound switch, and do not directly determine how every app resolves names in the end.
Before choosing a more aggressive resolution strategy, check whether you actually need to route by destination IP. Additional lookups can introduce delays, caching effects, and changing response addresses. The same domain may return multiple addresses; check how the core handles them in practice. If the destination is already an IP, domain rules usually cannot recover the original hostname. Sniffing may supply that information for some protocols, but it does not work for every connection and cannot identify all encrypted app traffic.
geosite and geoip refer to classification data; adding them to a string does not create an online lookup automatically. Whether a rule works depends on core support, the presence of resource files, a valid category name, and how the client manages those resources. If classification data fails to load, first fall back to explicit domain or address rules. Do not copy category tags from one environment to another without checking the data source and file location they depend on.
After changing routing, test one request with a clearly defined destination. Clear old connections, observe which outbound the new connection uses, then test other domains used by the app. Existing long-lived connections may keep using their old channel, making a change seem ineffective at first. For unexpected routing or blocked traffic, check the inbound tag, destination details, first matching rule, and outbound tag in that order. For general questions, see the FAQ instead of resetting the entire config first.
06 / DNS Resolvers and Split-Routing Boundaries
First, Find Out Who Resolves the Domain
DNS resolves domain names to network addresses. First check whether the app gives the proxy a domain name or an already-resolved IP, then inspect the core's DNS settings. The browser, operating system, core, and remote service may all be involved; having a dns object in the config does not mean every query on the device passes through it. Apps using their own encrypted DNS may bypass the system's default resolution path, so check app-level settings alongside the core config.
A core's built-in resolver usually serves internal functions that need domain resolution, such as IP-based routing or some direct outbound behavior. A remote proxy may also resolve the domain after receiving it. These two lookups may use different servers and return different results. When troubleshooting, note where the lookup happens, which domain was queried, and what address was returned. Do not assume the core got the same answer as the system's built-in lookup tool.
Choose DNS Servers by Query Purpose
This DNS object snippet uses one resolver first for the documentation example domain and another for other queries. The addresses shown belong to public DNS services; before using them, confirm they are reachable on your network and understand that those services will receive the corresponding queries. This illustrates the object and string forms of servers; it is not suitable for every region or network, and does not guarantee that queries are sent through the proxy outbound.
{
"dns": {
"servers": [
{
"address": "223.5.5.5",
"domains": ["domain:example.com"],
"skipFallback": true
},
"1.1.1.1"
],
"queryStrategy": "UseIP"
}
}
domains sets the domain scope a server should handle preferentially; it does not guarantee that only that server will ever be used. Fallback behavior also depends on global fallback settings and per-server options. skipFallback is commonly used to keep a server out of fallback queries for unrelated domains, not to guarantee exclusive use for its matched domains. For strict separation, check the current core's fallback behavior and verify it in logs or observable query records.
Understand Returned-Address Filtering and Address Families
expectIPs restricts which response address ranges are expected. It filters DNS results; it does not automatically route the connection through a particular outbound. If the expected range is too narrow, valid content delivery addresses may be rejected. If it relies on classification data, make sure the required resource exists. A failed lookup may mean the response did not meet the filter, not that the DNS server failed to respond. Remove the filter and compare before deciding whether a narrower range is needed; do not assume the domain itself is unavailable.
queryStrategy controls which address families to query. Common options include considering both IPv4 and IPv6 or querying only one. Having an IPv6 address does not mean the path to every destination supports IPv6; conversely, querying only IPv4 can hide what the network can actually do. Test the current path and confirm outbound support before choosing a strategy. Do not permanently disable an entire address family just to address one intermittent timeout without recording why.
hosts can provide static mappings in the core's resolver, but it is not a global hosts table for every app on the system. Mappings bypass normal DNS updates and are useful for controlled tests or specific local needs, but not for permanently pinning public service addresses that change frequently. Also check which name TLS will still verify and whether routing matches again using the mapped address. A static address that connects does not prove the original DNS service has recovered.
Encrypted DNS requires you to consider both bootstrap resolution and the connection path. If the DNS service itself uses a domain, first determine how to resolve the service address. Depending on the same service for that lookup can create a startup loop. Syntax for query protocols, direct connections, and routing integration varies across cores; changing an address prefix alone does not complete split routing. Establish a baseline with simple DNS first, then verify resolver reachability and the target domain results separately.
Finally, clear caches before comparing changes. App, system, and core caches, as well as existing connections, can all retain old results. Restarting one component may not clear the state in another. Choose a fixed test domain and record address families, returned addresses, and the actual outbound before and after the change. For more on separate resolution for domains in mainland China and elsewhere, see the DNS split-resolution guide; DNS groups are not the same as connection routing.
07 / Policies and Runtime Behavior
Keep Defaults Until You Understand the Timeout
policy controls aspects of connection lifecycles and statistics; it is not an automatic speed boost. Start with the default policy and consider changing it only when logs and reproducible steps point to a specific timeout. A slow network, a slow server response, and an app that has paused sending can all look like waiting, but have different causes. Increasing every timeout may only make failures take longer; decreasing them all may close idle connections that could have stayed open.
Policies are often organized by user level. Keys in the levels object are level IDs corresponding to protocol user settings; they are not node order or group numbers in the client window. Common scenarios use the default level when none is specified. If the config has multiple users or inbounds, first find out which level the connection actually uses, then decide whether a custom policy applies. Adding an unreferenced level object usually does not change the connection you are observing.
Understand Time Fields by Connection Stage
This policy object example uses seconds for time fields and kilobytes for buffer fields. The values illustrate how fields relate to one another; they are not universal optimization recommendations. The scope of some fields depends on the core's implementation, especially with different protocols and buffer management. After merging the snippet, confirm the core starts, then compare using the same app and steps without changing the outbound at the same time.
{
"policy": {
"levels": {
"0": {
"handshake": 4,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5,
"bufferSize": 64
}
},
"system": {
"statsInboundUplink": false,
"statsInboundDownlink": false,
"statsOutboundUplink": false,
"statsOutboundDownlink": false
}
}
}
handshake limits how long the relevant connection handshake can wait. It is not the full page load time and does not control how long an app takes to download a file. If failures always occur before the connection is established, check the address, port, and transport negotiation first; then decide whether a longer handshake timeout is necessary. With incorrect basic settings, a longer wait only delays the error. It cannot make an incompatible protocol work.
connIdle sets a time limit for idle connections. Push notifications, chat, or remote sessions may go a long time without exchanging noticeable data, so a short idle timeout can make the app reconnect repeatedly. A longer timeout, on the other hand, keeps unused connections around longer. Distinguish app-level heartbeats, network keepalives, and core policies: they operate at different layers, and a keepalive at one layer does not guarantee another layer considers the connection active.
uplinkOnly and downlinkOnly control how long one direction can remain open after the other side of a connection has ended. They are not upload and download speed limits. Some apps finish sending a request and then wait a long time for a response; an aggressive one-way timeout can cut that response off. If a request goes out but the result is interrupted, check the log timeline instead of interpreting “up” and “down” as bandwidth fields.
Understand Buffers and Statistics in Context
bufferSize affects buffer settings for relevant connections. A larger buffer does not directly increase bandwidth and can increase memory use with many connections. On Android in particular, process termination, background restrictions, and oversized buffers can produce similar symptoms, so do not judge by the final disconnect alone. Check system resources and client status first, then decide whether to adjust the setting. Do not treat vague performance advice as a universal fixed value for every device.
Enabling statistics does not automatically produce a visible report. Core statistics usually require a matching stats module, query interface, and client-side reader. The traffic shown in the client may also come from another counting method. Before enabling them, check whether any component actually consumes the data; do not add an unused interface just to make the config look complete. Statistics only report data under their specific counting method and cannot establish actual throughput, response time, or remote service quality.
After changing a policy, record the startup logs, app behavior, and when the issue occurs. If the entire client process disappears, check process exits and system restrictions before increasing connection timeouts. Desktop runtime environments, extraction paths, and config loading errors, as well as Android permissions and background restrictions, are separate troubleshooting paths. See troubleshooting startup crashes to tell them apart. Connection policies only affect active connections; they cannot fix a core that has not started correctly.
08 / Config Validation and Troubleshooting Steps
Test Startup Separately from Access
Save your changes, then have the client reload the relevant config. Confirm the expected config is still selected and check the logs to see which core is actually running. A client showing that it has started only means a startup process was run; it does not verify the listener, routing, or remote access. A custom file on disk is not necessarily the file currently in use. If behavior has no relation to the file contents, check the config source before continuing to edit a file that may not be read.
Step 1: check config loading. Syntax errors, incorrect field types, unknown protocols, and missing classification resources usually occur before the core handles application traffic. Match the first relevant log error to your most recent change rather than focusing only on the final process exit message. Fixing one structural error may reveal another; that does not mean the first fix failed. Address one specific issue at a time and confirm startup succeeds before testing connections.
Narrow Down the Problem Along the Request Path
Step 2: check the inbound. Have an app that explicitly supports proxy settings send a request, confirm its address, port, and protocol, and see whether the core receives it. If there is no inbound log entry, first check the app's own proxy options and which traffic the system proxy covers. Some programs read system settings only at startup, so you may need to close old connections or restart the app; others do not use the system proxy at all. Do not blame outbound authentication before confirming that the request reaches the core.
Step 3: check routing and resolution. First confirm whether the destination arrives as a domain or an IP, then check possible matching rules in order, and finally verify the outbound tag. If the destination is unclear in the logs, you can temporarily increase log verbosity for a controlled test, but restore it afterward. Detailed logs may include domains, connection addresses, and local paths; remove unrelated information before sharing them. Log level affects what you can observe, not the routing result.
| Observed symptom | Check first | Next step |
|---|---|---|
| Core exits immediately after startup | JSON syntax, field types, resources, and ports. | Revert the latest change and check the first error again. |
| No inbound log entry for an app request | App proxy scope, address, protocol, and port. | Test with one app that explicitly supports a proxy. |
| Request uses an unexpected outbound | Rule order, conditions, and tag references. | Clear old connections and observe a new request. |
| Basic connection works, but the handshake fails | Authentication, transport path, and security settings. | Restore the original subscription settings and compare each field. |
| Only some domains fail | Resolution results, resource domains, and routing scope. | Use a fixed destination to compare address families and outbounds. |
Step 4: check the outbound handshake. Distinguish connection refused, timeout, certificate verification failure, and authentication failure; each points to a different stage. For a refused connection, start with the destination port and service status. For a timeout, consider the network path and firewall. For certificate errors, check the name and device clock. For authentication errors, verify the connection details. Do not try to fix all these errors by changing DNS. See how to read runtime logs for more detail.
Use Controlled Rollbacks Instead of Stacking Patches
Keep two clearly documented configs: one as a working baseline and one with only the change you are testing. Use the same app, destination, and connection method for each test. Do not switch servers and routing at the same time and then guess which change mattered. If a new rule causes some connections to fail, revert it first and confirm whether they recover. Once confirmed, narrow the rule conditions; this is more likely to reveal the root cause than adding more exceptions.
Remember that arrays are not ordinary text blocks when merging objects. Pasting two complete routing objects side by side creates duplicate keys or invalid structure. Replacing one outbounds array with another can remove outbounds still referenced by routing rules. First decide which parent object to keep, then merge only the needed array items. Check tag uniqueness, references, and default outbound order. Even if some parsers accept duplicate keys, do not rely on their overwrite behavior.
After validation, move stable settings to a location where they can persist, then test again after restarting the client, switching configs, and updating the subscription. If the old value returns on restart, your change is still in an auto-generated file; use a settings entry point supported by the client. Add a brief note to the final config: which core it uses, which layers changed, any classification resources it depends on, and how to roll back. There is no need to record unrelated device details, and never put credentials in public notes.