JSON · inbounds · outbounds · routing · dns · policy

V2Ray Config File Reference

Find fields by traffic entry point, exit, and matching order. Distinguish client settings from core runtime configuration.

This is a reference manual for users who can already import subscriptions and want to understand configuration files. If you're new, follow the Getting Started Guide to install, import, and connect, then return here to look up specific fields. v2rayN is the recommended desktop client; see the Download Center for installers and platform options. This guide covers common V2Fly and Xray JSON structures without treating core-specific fields as universally interchangeable.

1. JSON Structure and Configuration Loading

Understand the Top-Level Object Through a Request

A configuration describes how the core accepts connections, selects a forwarding method, and handles supporting features. It is not another way to write a subscription URL. An app sends a request to an inbound; the routing module checks its destination and source, then passes the connection to the selected outbound. DNS, logging, and policies take part at different stages. Once you understand this flow, you can identify which layer to investigate instead of changing several seemingly related settings at once.

Common top-level objects include log, inbounds, outbounds, routing, dns, and policy. Inbounds and outbounds are usually arrays because a configuration can have multiple listeners and exits; routing uses an ordered array of rules. Array order can affect behavior, while object field order usually does not. Formatting can improve readability, but don't reorder outbounds or routing rules just for appearance.

Start with a Working Local Baseline

This complete example listens for SOCKS connections on the local loopback address and uses a direct outbound. It only verifies that the core can read the file, bind a port, and handle a local request; it does not create a remote proxy tunnel. Before starting, make sure the port isn't already in use by the client, and configure the test app to use this SOCKS endpoint explicitly. UDP is disabled to keep the first test focused on TCP. Later sections cover other capabilities.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "socks-in",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": false
      }
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    }
  ]
}

JSON strings must use double quotes, booleans must be written as true or false, and numbers don't need quotes. Don't leave a trailing comma after the final item in an array or object, and don't put Chinese notes directly in the code. Some tools allow extended formats with comments, but the fact that an editor can open a file doesn't mean the core can parse it. Strict JSON is easier to troubleshoot when sharing configs across tools. Save the file as UTF-8 text.

A Field's Presence Doesn't Mean the Core Supports It

V2Fly and Xray share some configuration history, but their protocol support, transport options, and evolving fields are not identical. Before using an example, confirm which core is running and check its field definitions. An option in the client interface only means the client provides a way to configure it; it doesn't mean every core accepts the same JSON. sing-box uses a different structure, so the examples here cannot be loaded as its configuration.

Also distinguish between the file you edit and the file the process loads. A GUI client may generate a temporary runtime config at startup, while a custom config may be loaded through a separate entry. If saving a JSON file in a particular folder has no effect, check the startup log or configuration details to confirm the active entry and file path. Don't assume a similarly named file in the program folder is the one in use, and don't edit both the client database and generated file hoping one will work.

ObjectPrimary PurposeWhere to Start Troubleshooting
inboundsAccepts local connectionsAddress, port, protocol
outboundsDefines how connections are forwardedProtocol, destination, transport settings
routingSelects an outbound in orderRule conditions and tag references
dnsConfigures built-in domain resolutionServers and resolution path
policySets connection and statistics policiesUser levels and system switches

Clear boundaries make configuration easier to maintain. Keep a working baseline, then limit each change to one object. Once verified, note why you made the change and what you expected it to do. When merging snippets, watch for duplicate top-level fields: keep just one corresponding object or array rather than appending two complete configs. Even if a parser accepts duplicate keys, it may keep only one value, making the result differ from what you expected when reading the file.

This guide covers inbounds, outbounds, routing, DNS, and policies, then returns to client management and validation. You can jump to a section, but troubleshoot forwarding issues along the request path. First confirm that traffic reaches the intended process, then check whether a rule matches. Adjusting routing when no traffic enters only adds variables to the investigation and tells you nothing about whether the rules are correct.

2. Inbounds: Listeners and Traffic Entry Points

Address, Port, and Protocol Each Have a Job

Each object in inbounds defines an entry point. listen selects the local address to bind, port sets the listening port, and protocol specifies what the inbound accepts. The app must use a proxy type that matches the inbound; don't enter an HTTP proxy address for a port that only accepts SOCKS. A port number is just a local convention, not a fixed capability. When copying a config, use the actual values shown in the current file and client.

For local use only, set the listening address explicitly to 127.0.0.1. This IPv4 loopback address isn't directly accessible from other devices. Setting it to 0.0.0.0 binds to all IPv4 interfaces; it is not an address to enter on another device. LAN access also involves the host's IP address, firewall, and authentication, so treat it as a separate change. Don't broaden the listening address just to fix a local connection issue.

Separate SOCKS and HTTP Inbounds

This snippet adds an HTTP inbound alongside the SOCKS inbound from Chapter 1, which is useful for apps that need separate proxy settings. Replace the original inbounds array with this complete array; don't add a second top-level field with the same name. The two inbounds use different ports and can share one outbound. Tags are for references within the configuration; they don't make the system detect or enable a proxy automatically.

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

The udp setting on a SOCKS inbound allows SOCKS UDP forwarding requests. It doesn't mean every app using the system proxy gains UDP support. The app must use that capability, and the outbound protocol, server, and network path must also support the traffic. HTTP inbounds are primarily for apps that honor HTTP proxy settings. If a browser works but another app doesn't, first check how each app connects rather than comparing only the destination domain.

Keep tag values short, unique, and easy to recognize. Routing can use inboundTag to match an inbound, allowing different local ports to use different outbounds. Tags aren't automatically updated: if you rename one, update every reference too. Otherwise, a rule may stop matching without an obvious syntax error. Document which apps use each inbound so you don't end up with a list of unexplained port numbers.

Sniffing Can Add Destination Details, but It Isn't DNS

Some apps resolve a domain to an IP before sending a proxy request, so routing no longer sees the original domain. sniffing can try to identify a domain from supported application-layer handshakes and make it available for later processing. Common destOverride values include http and tls. Sniffing doesn't work for all traffic and can't decrypt arbitrary app data. Check the behavior of the specific core before relying on it to change a connection's destination.

{
  "sniffing": {
    "enabled": true,
    "destOverride": [
      "http",
      "tls"
    ]
  }
}

Put this snippet inside an inbound object; it isn't a standalone top-level module. Before enabling it, clarify the goal: supplement domain-based routing or recover destination information. If the app's protocol isn't supported or its handshake provides no identifiable information, routing must still rely on the original destination or IP rules. Sniffing isn't a universal fix for domain-routing problems. After changing its settings, retest connections from affected apps.

If a port fails to bind, first close any duplicate client instances, then check whether another process is using the same address and port. Don't keep changing ports and forget to update the app. If the listener starts but no requests appear, check whether the app uses its own proxy settings, bypasses local addresses, or still points to an old system proxy port. Confirm the entry point before checking the outbound to avoid mistaking “no incoming connection” for “remote server unavailable.”

3. Outbounds: Protocols and Transport Settings

Outbound Objects Define How Traffic Is Forwarded

outbounds can include direct, block, and remote proxy objects. protocol specifies the outbound protocol, settings contains its parameters, and tag lets routing refer to it. An outbound tag isn't a server address or the alias shown in the client. Whether a changed display name also changes the generated tag depends on the client. When checking the runtime config, use the tag actually present in the object; don't infer references from the interface name.

When no routing rule matches, common configurations use the first object in the outbound array. That makes array order more than a formatting detail: moving a direct outbound from second to first can change the destination of all unmatched traffic. For clearer behavior, you can add an explicit fallback rule at the end of routing, but still confirm what the first outbound does, especially when migrating between client-generated and manually maintained configs.

Minimal Direct and Block Outbounds

This defines two outbounds: freedom sends traffic directly to its destination over the local network, while blackhole drops matching traffic. Neither needs remote server details. You can combine this array with the inbound from Chapter 1, but the block outbound won't take effect just because it exists. Configuration objects provide capabilities; routing selects them. Treat those as separate responsibilities.

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

A remote proxy outbound usually also needs a server address, port, identity parameters, and transport settings. These must come from the actual service configuration; you can't infer them from a client name. VMess, VLESS, and Trojan use different authentication fields, and transport settings such as TLS and WebSocket belong at the appropriate level. Import a valid subscription or link from the provider first, then inspect the generated config. This is more reliable than assembling a multi-layer structure from scratch.

Check Protocol, Transport, and Security Separately

streamSettings describes transport and security settings, separate from the protocol identity information in settings. The client structure must match the server's transport, with paths, hostnames, and security parameters aligned as a set. Changing the transport type won't convert the server. If a handshake fails, first check the combination required by the server, then review local options instead of trying every item in a dropdown.

LevelWhat It ContainsCommon Mistake
protocolSelects the outbound protocolAssuming similarly named protocols are interchangeable
settingsProtocol-specific address and identity parametersMixing authentication structures from different protocols
streamSettingsTransport and security settingsChanging the network type without checking the server
tagUsed by internal rulesTreating a display alias as a fixed tag

The connection address and the server name used for TLS verification are different concepts. The former determines where to connect; the latter is used to verify the certificate identity. They can differ in some deployments, but only when supported by the service configuration. If you see a certificate error, check the system clock, server name, and certificate chain instead of disabling verification. Skipping verification changes the connection's security properties and isn't a long-term fix or proof that the other settings are correct.

Special features such as REALITY require a compatible core and matching server parameters. Don't copy an example to another core just because both JSON configs contain security-related fields. v2rayN can manage entries for different cores, but that doesn't mean every entry can switch executables freely. On Android, choose settings for the capabilities of v2rayNG or v2flyNG's respective cores. See the Client Comparison for an overview of the three clients.

Troubleshoot an outbound one object at a time. Temporarily use an explicit fallback rule to send a test request through the target outbound, then restore complex routing after confirming the basic connection. If only certain sites fail, check destination resolution and routing rules; if all destinations fail, check the remote address, authentication, and transport. Don't enable multiple connection optimizations at once to mask basic errors; extra layers make it harder to match log failures to the original request.

A subscription update may change connection parameters, and the client may regenerate internal tags. Check any manually maintained routing rules for references to old tags. Let subscription entries manage remote parameters, and keep long-term routing intent in routing settings. If you need a fully custom outbound, document the parameter source and core requirements. Remove identity fields and private subscription URLs before sharing troubleshooting material.

4. Routing: Rules and Match Order

The First Matching Rule Selects the Outbound

routing.rules is an ordered array of rules. For common rule fields, multiple candidates within the same array field generally match if any one of them matches, while different condition fields must all match. For example, a rule with both an inbound tag and a domain applies only to connections arriving through that inbound with a matching destination domain. Earlier rules are checked first, so put exceptions before broad rules and fallback rules last.

This example blocks a documentation domain first, sends private IP addresses directly next, then sends all remaining TCP and UDP traffic directly. It demonstrates rule order and tag references; it isn't a remote proxy routing setup. Before using it, make sure outbounds tagged direct and block exist, and install GeoIP data readable by the current core. If the database isn't available, remove the related rule for the baseline test.

{
  "routing": {
    "domainStrategy": "AsIs",
    "rules": [
      {
        "type": "field",
        "domain": [
          "full:example.org"
        ],
        "outboundTag": "block"
      },
      {
        "type": "field",
        "ip": [
          "geoip:private"
        ],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "network": "tcp,udp",
        "outboundTag": "direct"
      }
    ]
  }
}

Write Domain Rules to Match Your Intent

full:example.org matches this exact domain; domain:example.org matches the domain and its subdomains. Regular expressions are more powerful but easier to make overly broad, so use an explicit full-domain or suffix rule for ordinary cases. Don't put a full web address in a domain list: paths, query parameters, and fragments aren't part of a domain condition. A URL with a path may leave a rule in place that never matches.

IP conditions can use a single address, a network range, or a database reference. Domain and IP rules use different information. Don't assume that if a domain matches a category, all its resolved IP addresses belong to the same category. CDNs may use shared addresses for multiple sites or return different regional addresses for one site. For precise control, state the matching basis first, then choose a domain or IP rule. Avoid using a large IP range in place of a few specific domains.

domainStrategy Controls When Routing Resolves Domains

AsIs routes using the destination information already available, without extra resolution for IP-rule matching. IPIfNonMatch tries to resolve a domain if existing conditions don't match, then checks the resulting address. IPOnDemand triggers resolution when IP information is needed during matching. The latter two make routing dependent on DNS results, so resolution failures or address changes can affect decisions. Read this section alongside Chapter 5.

Routing's domain-resolution strategy isn't the same as address handling for all outbounds or the OS DNS settings. Similarly named fields in different modules may have different timing and scope. Before changing one, locate its object path and describe the expected behavior: “use the resolved IP for rules when the domain doesn't match” is clearer than “enable smart DNS.” If an app has already sent an IP as the destination, a domain-resolution strategy can't recover the original domain.

Manage GeoIP and GeoSite data together. After updating the files, confirm that the running core reads the new versions, then restart or reload as required by the client. Replacing a copy in another directory won't change the result. If you see errors about missing categories or unreadable data, check the file source and path before changing the rules. See the GeoIP and GeoSite Database Update Guide for update guidance.

Client presets such as “bypass mainland China” are sets of rules, not synonyms for a single switch. Changing a preset may alter domain rules, IP rules, and the fallback outbound at the same time, so inspect the complete generated rules when troubleshooting. Put a single exception before any broad rule that would match first, and confirm that it reloads after saving. Test one destination that should match and one that shouldn't to check for overly broad conditions.

With multiple inbounds, you can also use inboundTag to isolate apps—for example, sending a specific local port through a fixed outbound. This works better than repeatedly changing the system proxy for apps that let you specify a proxy manually, but only if the app actually connects to that inbound. If routing doesn't work, check the inbound tag, original destination, rule order, and outbound tag in that order. Don't switch databases or change the remote protocol as your first step.

5. DNS Configuration and Query Paths

First Identify Which Layer Performs Resolution

A single connection may involve the app's resolver, OS resolution, the core's built-in DNS, and resolution by the remote server. The top-level dns configuration only controls the part handled by the core's built-in resolver; it doesn't take over every app's domain queries. A browser using its own encrypted DNS may follow a different path. When troubleshooting, first note whether the destination reaches the core as a domain or an IP, then decide whether this object is relevant.

Built-in DNS can be used by routing's domain-resolution strategy and certain outbound behaviors. Adding a server array alone doesn't make every proxy request resolve locally first; a remote protocol may pass the domain to the other end. Conversely, if an app has already resolved a domain and sent an IP, the core may not get a chance to use these servers. A successful OS lookup only proves that the OS resolution path works, not that the core resolves the same way.

Minimal Server and Static Mapping Example

This snippet uses a standard DNS server and adds a static mapping for a documentation example domain. The mapped address is for demonstration only and doesn't host a real site. When testing, check the resolution result; don't use it to judge whether a website is reachable. The static mapping illustrates the structure, not a recommendation to hard-code many website addresses. Fixed records can quietly cause problems when service addresses change.

{
  "dns": {
    "hosts": {
      "example.org": "192.0.2.10"
    },
    "servers": [
      "1.1.1.1"
    ]
  }
}

hosts provides static results for the built-in resolver, while servers defines upstream servers. Whether a server address is reachable depends on the network; the example address isn't guaranteed to work everywhere. Standard DNS and encrypted DNS differ in transport, certificates, and bootstrap resolution. Before switching, verify that the selected service is reachable and check the supported config format for your core. Don't use a webpage URL as a DNS endpoint.

Split-DNS and Result Constraints

A server can also be written as an object, with domains specifying the domain range to match first. This example sends the documentation domain to a dedicated server and uses the default candidate for other queries. Whether a failed match falls back, and which candidates it tries, also depends on the core's settings. Don't assume that appearing first in the array means a server always takes priority. Understand domain matching, candidate selection, and fallback as separate stages.

{
  "dns": {
    "servers": [
      {
        "address": "1.1.1.1",
        "domains": [
          "domain:example.org"
        ]
      },
      "8.8.8.8"
    ]
  }
}

Some configurations also use expectIPs to restrict acceptable resolution results. This filters results; it doesn't correct a bad answer automatically. If the constraints are too strict, the core may reject an address returned by the server, making it look like resolution failed. Using a database reference as a constraint also adds a data-file dependency. To troubleshoot, first remove result restrictions, then restore them one at a time to identify their effects.

The combination of routing's domainStrategy and DNS is especially easy to misunderstand. With on-demand resolution, a new connection may need to wait for a DNS result before selecting an outbound. If the upstream DNS server depends on a proxy path that hasn't been established yet, check for a circular dependency. A remote server identified by a domain also needs a working bootstrap resolution method. Test server-address resolution separately from ordinary domain lookups; one website test isn't enough.

Check IPv4 and IPv6 results separately. Getting an IPv6 address doesn't mean the outbound has full IPv6 connectivity. An app may try one address family first and then fall back, causing intermittent delays. Don't permanently disable an address family just because a connection is slow. Test resolution and connectivity to the same destination separately, then adjust the query strategy supported by your core. Don't mistake a network-path issue for a domain-classification problem.

TUN setups add further steps, such as how system DNS reaches the virtual interface and whether traffic is intercepted or redirected. The top-level DNS object alone can't explain these behaviors; check the TUN config generated by the client as well. For background, see the v2rayN TUN Mode Guide. After any DNS change, reconnect the app and account for cached results. Don't judge a new config using an old lookup result.

6. Policy: Levels, Timeouts, and Statistics

Policy Levels Are Not Routing Priorities

policy defines connection behavior and statistics policies. Its levels object groups settings by user level. These levels aren't subscription group numbers or routing-rule priorities. In some protocol user configs, level selects the corresponding policy; if none is specified, the default level is generally used. Changing an unused level may have no effect on current connections, so first confirm which users the policy applies to.

This snippet sets connection-stage and idle-handling values for level zero. The values are in seconds and illustrate how the fields relate; they aren't a recommended template for every network. Merge it into the top-level policy object. If you already have other levels or system statistics settings, preserve those fields and adjust them as needed. Don't replace the whole object with the example and then mistake missing functionality for a change in core behavior.

{
  "policy": {
    "levels": {
      "0": {
        "handshake": 4,
        "connIdle": 300,
        "uplinkOnly": 2,
        "downlinkOnly": 5
      }
    }
  }
}

Four Time-Related Fields in a Connection's Lifecycle

handshake limits the time an inbound connection can take to complete its handshake. It isn't a universal timeout for every remote connection stage. Increasing it won't fix incorrect authentication fields or a mismatched transport. If a connection stalls only after it has been established, check idle policies, the remote server, and app behavior. The word “timeout” in a log doesn't mean this field controls every timeout; identify the stage where the error occurs.

connIdle sets how long a connection can remain idle without data transfer. A very short value may disrupt sessions that stay open with occasional traffic; a very long one may delay releasing connections that are no longer useful. Messaging apps, remote terminals, and ordinary browsing have different connection patterns, so one page load isn't enough to judge the setting. After changing it, test the app that had the issue and cover its realistic wait time.

uplinkOnly and downlinkOnly control waiting behavior when a connection has only one direction of data transfer remaining. They aren't upload or download speed limits, nor routing conditions. Some apps continue waiting for a response long after sending is complete, so handling the one-way stage affects when the connection closes. If a response is cut off early, check connection state and logs before changing these fields. Don't mistake a server-initiated close for a client policy issue.

FieldStage CoveredDoesn't Replace
handshakeInbound handshake waitProtocol or authentication fixes
connIdleIdle period with no data transferApp keep-alive behavior
uplinkOnlyUplink-only stageUpload throttling
downlinkOnlyDownlink-only stageDownload throttling

Statistics Switches Don't Create a Statistics Dashboard

To enable core statistics, common configurations include both a top-level stats object and statistics switches under policy.system. This example only enables aggregate inbound and outbound traffic counts. It doesn't create a graphical dashboard or guarantee that the client reads those counters; that depends on the client implementation or an API. Adding statistics fields won't make a new section appear in the interface without a way to query them.

{
  "stats": {},
  "policy": {
    "system": {
      "statsInboundUplink": true,
      "statsInboundDownlink": true,
      "statsOutboundUplink": true,
      "statsOutboundDownlink": true
    }
  }
}

If you also need the level policies above, merge levels and system into the same policy object instead of creating two objects with the same name. User-level statistics also depend on protocol and user identifiers; don't confuse system statistics switches with user statistics configuration. When comparing counters, account for process restarts, session endings, and how the client aggregates results. Different readings may simply cover different periods.

Buffering and resource options are for more advanced tuning. Unless you have evidence of a resource bottleneck, keep the core defaults rather than copying generic “optimization” settings. Larger buffers may use more memory; smaller ones may affect certain traffic. Results also depend on concurrent connections and app read/write patterns. First verify the protocol, routing, and DNS. Then test one identified bottleneck at a time, rather than changing many policies at once.

Keep a consistent observation window when troubleshooting policies. Record the app, idle duration, and connection stage where the issue occurs, then change one field and retest with a new connection. If the change makes no difference, check the active file, user level, and running core. Don't keep increasing timeouts just to postpone failure. If the cause is a remote rejection or unreachable address, waiting longer only delays the error; it won't restore the connection.

7. v2rayN Generated Configs and Custom Configs

Manage Persistent Settings Separately from Runtime Output

v2rayN stores nodes, subscription groups, routing, and parameter settings as client-managed data, then generates a runtime config for the selected core. The generated file is an execution artifact, not necessarily the place to make lasting changes. Switching servers, reloading, or restarting may overwrite edits made directly to it. For persistent settings, use the relevant client interface. If you need full control over JSON, first understand how custom configuration entries are managed.

Before making changes, identify the current mode. For ordinary node entries, the client typically assembles inbounds, outbounds, and shared settings. Custom config entries may preserve more of the file as-is. Menu actions may affect different layers in each mode. Don't assume that changing a routing preset in the interface will rewrite arbitrary custom JSON. To verify a setting, inspect the runtime config after reloading, not just whether a button appears selected.

Shared Settings, Node Settings, and Subscription Sources

Look for shared parameters such as the local listening port and core selection in the relevant settings pages; labels may change as the client interface evolves. Remote addresses, authentication, and transport belong to node settings. Subscription URLs and update intervals belong to subscription groups. Handle each issue in the right place: an update interval doesn't change the handshake protocol, system proxy settings don't modify the remote server, and switching a routing preset doesn't download subscription data again.

A subscription update can overwrite connection details from its source, so consider the next update before manually changing a subscribed node long term. For a temporary test, duplicate it as an independently managed entry and label its purpose clearly. If the provider changed the parameters, update the source instead of repeatedly patching old fields. If a subscription can't be fetched, start with the URL, network, and system clock. See Troubleshooting Subscription Update Failures for steps.

ChangePreferred Place to Manage ItWhat to Verify
Local proxy entry pointClient-wide settingsRuntime port matches app settings
Remote connection parametersNode entry or subscription sourceGenerated outbound matches server config
Website routingRouting rulesRule order and outbound tags are correct
Full JSON controlCustom configuration entryActive file path matches the core

Switching Cores Takes More Than Changing the Executable

v2rayN can manage different cores through the options provided by the client, but whether an entry can switch depends on its protocol and transport capabilities. Don't give an Xray-specific config to V2Fly and assume it works just because the JSON syntax is valid. sing-box has a different configuration structure, so it can't reuse the snippets on this page. Before switching, confirm that the client will generate the matching format, or prepare a separate file for the custom configuration.

The Windows WPF and cross-platform desktop interfaces may differ in menu layout, tray behavior, and system integration, but the concepts of inbounds, outbounds, and routing still apply. On macOS and Linux, also account for differences in system proxy and permissions, particularly for TUN. See Desktop vs. WPF Edition: What's the Difference? for installer options. Don't install a package unsuited to your platform just to find a familiar menu.

On Android, manage settings in v2rayNG and v2flyNG according to each app's core and interface; don't copy desktop menu paths mechanically. When importing a link or subscription, check the client's supported protocols and parameters. For a full JSON file, confirm that the client explicitly supports that import type. Subscription URLs, single-node share links, and complete configuration files are three different things. They can't substitute for one another just because they're all text.

Keep a simple change log for custom configs, noting the object changed, expected behavior, and rollback steps. Store it locally; there's no need to upload private configs publicly. When sharing troubleshooting materials, keep the object structure, tags, and error details, but remove identity fields, private server addresses, and subscription links. Hiding one authentication field may not be enough; other fields could expose service details or usage patterns.

Use a single source of truth: manage interface-owned settings in the interface and file-owned settings in the file. Don't let the two configurations overwrite each other indefinitely. After each change, inspect the generated config, check process logs, and test the app. If the interface and actual behavior differ, verify the loading path first. If your goal is simply to connect, return to the Quick Start Guide; you don't need to master every top-level object.

8. Configuration Validation, Logs, and Rollback

Check Syntax, Loading, Inbound, and Outbound in Order

The first step in troubleshooting is to identify where the failure occurs. A JSON syntax error prevents the file from being parsed. An unsupported field or missing reference may fail during loading. A port conflict affects the listener. A remote handshake problem usually occurs after a request has entered the core. Each stage requires a different fix. Read the first meaningful error, not just the final “startup failed” line; later messages often result from the original error.

Before editing, keep a copy of the working file or client settings and note which object you intend to change. After editing, check brackets, commas, value types, and duplicate keys, then reload through the client and confirm the core starts. If the client provides a config viewer or log, make sure it shows the entry you just edited. Don't keep piling on new rules after a failure. Return to the last working state first so you can identify which change caused the difference.

Verify the Inbound with a Clear Local Request

These two commands use a compatible version of curl to test the local SOCKS and HTTP inbounds. They use the ports from Chapter 2 and apply only when that config is running. In the SOCKS command, socks5-hostname sends the destination domain to the proxy for resolution; using local resolution would test a different DNS path. Whether the test site is reachable still depends on the network and outbound. The commands don't guarantee success.

curl --proxy socks5h://127.0.0.1:10808 --head https://example.org/
curl --proxy http://127.0.0.1:10809 --head https://example.org/

If the local port can't be reached, check the process, listening address, and port before changing remote settings. If the local connection succeeds but the request then fails, check destination resolution, routing, and the outbound handshake. Keep the destination and inbound constant while changing one condition at a time. Mixing browser caches, system proxy settings, and direct command-line connections introduces different paths, making apparently identical tests incomparable.

Temporarily Increase Logging; Don't Keep Verbose Logs on

The default warning level is suitable for everyday use. For routing or connection issues, temporarily raise it to info. This snippet only changes error-log detail and doesn't set a file path, so the client can display the logs. If you write to a file, check that the directory is writable, use a path format appropriate for the OS, and watch the file size. Logs may contain destination domains and connection details; review and redact sensitive information before sharing.

{
  "log": {
    "loglevel": "info"
  }
}

When reading logs, follow a single new request in time order: inbound acceptance, destination identification, outbound selection, and failure point. If no rule matches, check conditions and order; if a tag is missing, check references; for certificate errors, check the clock and server identity; for database read failures, check the file path and category. Different cores may use different wording. Don't treat a commonly quoted error from online as definitive; interpret it in the context of this request.

SymptomCheck FirstNext Step
Config won't loadSyntax, field support, tag referencesReduce to a minimal complete config
Can't connect to the local portProcess, listening address, port conflictsCheck the app's proxy entry point
Inbound works, but handshake failsOutbound authentication, transport, security settingsTest the target outbound by itself
Some destinations use the wrong outboundDestination details, rule order, databaseCompare one matching and one non-matching destination
Intermittent DNS failuresQuery path, cache, address familyTest each resolution layer separately

The most reliable way to roll back a complex config is to restore it one layer at a time. First verify the local direct-connection baseline from Chapter 1, then add the target outbound, restore a few routing rules, and finally bring back DNS and policy changes. With a GUI client, keep the production entry unchanged and use a separate test entry instead of replacing the entire runtime file by hand. Establish a new connection at every step and record the result so an old session doesn't skew your findings.

A successful request doesn't prove the whole config is correct. Test at least one destination expected to connect directly, one expected to use another outbound, and any app that needs to stay connected over time. If UDP or TUN is enabled, test that traffic separately. Make the validation scope match the change; a working TCP website doesn't prove that all traffic works. If only a specific app fails, see the Troubleshooting FAQ and narrow down the issue by its inbound method.

After troubleshooting, restore an appropriate log level, remove temporary domain mappings and test rules, and confirm the client still loads the same settings after restarting. Save the final working changes to their long-term source, not just the generated file. For background on core families, see Project V, V2Fly, and Xray: How the Ecosystem Fits Together. The goal is to make every behavior explainable, testable, and reversible—not to keep adding fields.