REFERENCE / config.json
V2Ray JSON Configuration Reference
Look up fields by structure and troubleshoot by connection path. Covers inbounds, outbounds, routing, DNS and policy.
Use this page as a reference, not as a first-connection walkthrough. If you haven't installed the client, imported a subscription or selected a server yet, start with the Getting Started guide. If you can already connect but need to change a listening port, select an outbound or adjust DNS resolution, use the sections below. See the download page for client installation.
01 / JSON Structure & Connection Flow
The Configuration Describes Core Behavior
A GUI client manages servers, updates subscriptions, configures the system proxy and starts the core. The core configuration controls listening for connections, routing, DNS resolution and outbound connections. The two are related, but they aren't the same data. Server entries saved in the client's main window usually need to be converted before the core can read them. Renaming an item in the interface also doesn't necessarily rename every corresponding tag in the core.
Think of the connection path as four steps: an app connects to an inbound, the inbound receives the destination, routing selects an outbound, and the outbound establishes the next connection. DNS may come into play during routing or connection setup. Logs record some events along the way, while policy affects timeouts and statistics. Troubleshoot along this path instead of jumping from a browser error to changing every DNS setting.
Objects, Arrays & Field Types
JSON objects use braces; arrays use square brackets. Keys and strings must use straight double quotes, ports are usually numbers, and boolean values are unquoted. Trailing commas, typographic quotes and unclosed brackets all cause strict JSON parsing to fail. Some cores or loading methods allow extensions such as comments, but the examples here use strict JSON for easier validation across tools.
The top level is usually a single object. inbounds and outbounds are arrays because a configuration can declare multiple listeners and exits. routing, dns and policy are objects containing their own rules or settings. Repeating a top-level key doesn't append to its value: parsers may overwrite the earlier value or reject the file. Keep each top-level key unique.
| Field | Type | What It Controls |
|---|---|---|
log | Object | Log level and output destination |
inbounds | Array | Local listener and accepted protocol |
outbounds | Array | Outbound used to reach the destination |
routing | Object | Selects an outbound based on connection details |
dns | Object | Built-in DNS resolution behavior |
policy | Object | Per-user policies and statistics toggles |
Start with a Complete Direct-Connection Configuration
Here is a complete configuration for checking a local listener. It provides only a SOCKS inbound on the loopback address and sends all connections through a direct outbound. It does not include a remote proxy server. The port is an example; make sure no other program is using it before starting. This configuration can confirm that an app can connect to the local core, but it cannot verify a remote node or change the system proxy automatically.
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
}
]
}
When reading the example, check the listening address and port first, then the inbound protocol, and finally the default outbound. Without routing rules, connections commonly use the first item in the outbound array. Array order therefore isn't just a formatting choice. After a client regenerates the configuration, check the default outbound again rather than only confirming that a proxy protocol object is still present.
The tag in a configuration is an identifier referenced by other fields, not a connection credential. Use short, unique, descriptive names that won't need to change. A filename doesn't determine whether a configuration is active: a file named config.json takes effect only if the running core process actually loads it. Keep a working reference configuration as you read the sections below, and change only one related snippet at a time.
02 / inbounds: Listening Addresses & Inbound Protocols
First, Identify Who Connects to the Listener
An inbound defines the address and port where the core accepts a particular type of connection. Desktop apps most commonly use SOCKS or HTTP proxies; you can't swap one for the other based on port number alone. If an app is set to use an HTTP proxy but connects to a port that only accepts a SOCKS handshake, it may immediately return a protocol error even when the port is listening. Check the address, port and proxy type together.
listen determines the listening scope. Binding to 127.0.0.1 makes the listener available primarily to the local machine. Binding to all interfaces expands access and also brings system firewall and network considerations into play. Encryption on a remote proxy protocol does not automatically protect a local SOCKS listener exposed to the LAN. Unless sharing is intentional and access controls are in place, loopback binding is easier to secure.
Declare SOCKS and HTTP Separately
This snippet declares two listeners: SOCKS on the example port 10808 and HTTP on 10809. A port number doesn't identify the software, so update the app's settings if you change it. Two listeners can't bind to the same TCP port on the same address. If another client may still be running in the background, find out which process owns the port before deciding whether to stop it or choose another port.
{
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}
},
{
"tag": "http-in",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
]
}
With settings.auth set to noauth, the SOCKS listener doesn't require a username or password. This is suitable for a controlled local listener, not for sharing it with any network. If authentication is needed, configure an account using the current core's SOCKS inbound schema, and make sure the client supports the same authentication method. HTTP inbounds have their own settings structure; don't copy SOCKS authentication fields into it.
udp lets a SOCKS inbound handle the corresponding UDP requests, but that doesn't provide UDP proxying for every app. The app must support SOCKS UDP relay, and the outbound protocol and remote service must support it too. Setting an HTTP proxy at the operating-system level usually won't route arbitrary UDP apps through this SOCKS listener. This is a difference in how traffic enters the proxy.
Sniffing & Original Destinations
sniffing attempts to extract a domain or other information from supported protocol handshakes. If an app supplies only a destination IP, sniffing may recover a domain for routing. The example's destOverride lists protocol types that can be used for destination overrides, but whether information can be extracted depends on the traffic. Sniffing doesn't decrypt all app traffic or guarantee that the original domain can be recovered from every connection.
Enabling sniffing can change the destination information used for routing or outbound processing. Some cores also provide related options that affect routing only; check their support and behavior separately. If an app starts misbehaving after sniffing is enabled, compare the logs and destination with sniffing on and off, then check whether the outbound accepts domain destinations. Don't assume the inbound port is the cause.
Routing rules can reference an inbound's tag with inboundTag to send traffic from different local app entry points through different outbounds. Keep tags unique and assign each app to the intended inbound. A common mistake is renaming an inbound without updating the reference in routing. The connection then matches a later rule or the default outbound, making it look as though the inbound change wasn't saved.
After making changes, restart or reload the current core and check that the listener starts successfully. The system proxy toggle belongs to the client or operating system; adding an inbound object won't enable it automatically. Follow the quick-start steps to confirm the app is actually using this listener before troubleshooting outbounds and routing. This avoids repeated changes across unrelated layers.
03 / outbounds: Exits, Tags & Protocol Settings
Outbound Order & Routing References
An outbound defines how the core continues toward a destination. It can connect directly, block traffic or reach a remote server through a proxy protocol. A tag lets routing select a specific outbound; protocol determines the structure of settings. An outbound named proxy isn't necessarily a proxy—check its protocol. A proxy outbound can also use any other unique tag.
In common configurations, connections that don't match a rule use the first item in the array. If the proxy should be the default route, explicitly put it first and keep any necessary direct rules. Don't reorder outbounds just to make the configuration easier to read. A client generator may reorder them based on the selected server and routing mode. Check the configuration that is actually running, not just the order shown in the client interface.
Minimal Direct and Block Outbounds
freedom connects directly through the local network; blackhole blocks the corresponding connections. The snippet below can serve as the outbound section of a direct-connection test configuration or be merged into a configuration that includes proxy outbounds. Declaring a block outbound alone doesn't block any destination. A routing rule must select it, or it must become the default outbound, for it to have an effect.
{
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
}
A direct connection doesn't bypass DNS resolution. If the destination is a domain, a component still needs to resolve it to an address that can be reached. Whether the built-in DNS or system resolver is used, and which address family is selected, depends on the core and outbound configuration. A routing rule that selects a direct outbound says nothing by itself about which path handles DNS.
Remote Protocol Fields Aren't Interchangeable
A proxy outbound needs a real server address, port and authentication parameters. VMess, VLESS, Trojan and Shadowsocks use different credential structures, and each transport and security layer has its own requirements. This page doesn't provide ready-to-use proxy configurations with invented accounts. Generate an outbound from trusted connection details, then check that its fields match the server. Don't swap the protocol name while keeping another protocol's user object.
| Parameter Layer | What to Check | Common Mistake |
|---|---|---|
| Remote connection | Address, port and reachability | Using the local listening port as the remote port |
| Protocol authentication | User ID, password or method | Mixing credential structures from different protocols |
| Transport settings | Transport type, path and related headers | Checking the server address but not the transport |
| Security settings | Service name, certificate and protocol requirements | Treating the connection IP as the value for every identity field |
Transport settings are usually defined in a structure such as streamSettings. The transport must match the server's actual setup, and support depends on the core. Features such as REALITY aren't universal fields accepted by every core. Before choosing a core, see Xray vs. V2Fly core differences, then check whether the client-generated configuration is compatible.
The server address, TLS service name and transport-layer host field may happen to be the same, but they serve different purposes. The server address determines where to connect; the service name is used during the handshake and identity verification; and some transport headers help the server route requests. When a handshake fails, compare each value against its source instead of replacing every domain-related field with the server IP.
Don't treat skipping certificate verification as a routine fix. It won't resolve a protocol mismatch, incorrect remote port or system clock drift, and it weakens identity verification. For certificate errors, first check the system time, service name and certificate coverage. If the server uses a special setup, get explicit configuration instructions before changing security options.
Finally, check dependencies: every outbound tag referenced by a routing rule must exist, with matching capitalization. When you remove an outbound for an old server, also remove rules that point to it. If you need multiple outbounds, validate two clearly defined exits first, then add features such as balancing or chaining. Adding nesting before confirming the basic path only makes failures harder to locate.
04 / routing: Match Conditions & Rule Order
Rules Are Evaluated from Top to Bottom
Routing selects an outbound based on connection details. Common field rules use an object with type set to field, plus conditions such as a domain, IP, port or inbound tag. Rules are generally checked in array order, and the matching outbound is used. Put narrow, high-priority exceptions first and broad rules later, so earlier rules don't prevent later ones from being considered.
Different condition fields in one rule usually all have to match, while multiple entries in the same domain or IP list usually act as alternatives. To route traffic when it matches a domain OR comes from a particular inbound, use two separate rules instead of putting both fields in one object. Before writing a rule, spell out the intended AND and OR conditions in plain language, then map them to the configuration.
Verify Rule Order with a Controlled Destination
The snippet below sends an example domain to a block outbound and private addresses to a direct outbound. It depends on the direct and block outbounds from the previous section, and the current core must be able to read the relevant geodata. The example domain is for testing, not a recommended production blocklist. All other connections still use the default outbound; these two rules don't enable global proxying.
{
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": ["full:example.com"],
"outboundTag": "block"
},
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "direct"
}
]
}
}
Domain matching should distinguish exact domains, domains that include subdomains and regular expressions. For example, full:example.com matches only that exact domain, while domain:example.com covers the domain and its subdomains. Plain strings don't have the same matching semantics as these prefixes and may match more broadly than expected. Use explicit forms that clearly express the intent of each rule.
Routing Resolution Strategy Isn't Outbound Resolution Strategy
AsIs generally doesn't resolve an incoming domain just to check IP rules; an original destination that is already an IP can still match an IP condition. With IPIfNonMatch, the core attempts to resolve the domain and check IP rules if no domain rule matched. IPOnDemand may resolve earlier when a rule check needs IP information. These settings control routing decisions, not the overall DNS traffic setup.
In this example, if the incoming destination is the full domain example.com, the first rule can match without relying on the private-address rule below it. If the destination doesn't match a domain rule, the resolved address may be used to check IP rules. If a broad direct-domain rule appears earlier, a later IP block rule may never run. Changing the resolution strategy alone can't fix a rule-order problem.
If rule data is missing, the core may report an error during startup. If the data is outdated or a category doesn't match expectations, the core may start successfully but route traffic incorrectly. Distinguish these cases before checking file paths or rule contents. Updating rule data in the client is separate from updating a server subscription; a successful subscription update doesn't prove the geodata is current.
Use inboundTag to route traffic from a specific inbound, or a port expression supported by the current core to match destination ports. Start by validating a single condition before combining several. In the logs, check both the destination and the actual outbound. Focus on routing only when the destination is as expected but the selected outbound isn't; don't immediately change remote protocol parameters.
Global, rule-based and direct modes are often client presets for generating a configuration, not universal JSON fields in their own right. After switching modes, check the default outbound and rule array again. When routing results are unclear, record a specific destination, inbound, matching condition and final outbound. This is easier to reproduce than “some sites don't work” and makes it easier to compare changes.
05 / dns: Resolver Paths, Address Families & Routing Dependencies
Start by Distinguishing Three DNS Paths
An app may resolve a domain itself before connecting to the proxy, or pass the domain directly to the proxy. The core may also use its own resolver for routing or outbound connections, and a remote service may handle the final resolution. These paths can coexist. The presence of a dns object doesn't mean every app's queries go through it, or that the system DNS settings have been replaced.
For example, if an app resolves a domain first and sends the resulting IP as its SOCKS destination, the core may receive no domain in the original request. Whether domain rules can still match depends on mechanisms such as sniffing. If the app passes the domain to the proxy, routing and the outbound can retain more destination information. Before comparing connection results between two apps, check whether each sends the local proxy a domain or an already-resolved address.
A Basic Built-In DNS Configuration
The example configures two standard DNS servers and asks the built-in resolver to query IPv4 addresses only. The addresses illustrate the structure; they aren't necessarily the best choice for every network and won't change the operating system's DNS settings. Evaluate resolver reachability, network suitability and privacy requirements separately. Adding an address doesn't automatically create a working route to it.
{
"dns": {
"servers": [
"1.1.1.1",
"8.8.8.8"
],
"queryStrategy": "UseIPv4"
}
}
servers can contain simple server entries. Some use cases also support objects with conditions such as domain scope or preferred addresses. The fields in such an object don't mean that the resolver will always contact only that server; candidate selection and fallback behavior also depend on the core's algorithm. Don't add several complex options at once. First confirm that a simple query works, then add domain restrictions and fallback behavior one at a time.
Match the Address Family to the Network
queryStrategy controls the address-family strategy for built-in DNS resolution. Querying IPv4 only can help isolate address-family issues, but it isn't a universal fix for connection failures. Networks with working IPv6 may need IPv6 queries enabled. Conversely, getting an IPv6 address from the system doesn't mean the route to the remote service, proxy server and outbound policy all support IPv6.
The address family ultimately selected for a domain can also depend on app-side resolution, outbound resolution and remote processing. Changing the top-level DNS query strategy can't guarantee that every connection uses only one address family. Compare the DNS answer with the actual dialed address. If they differ, first check whether another resolver or a remote service handled the request; don't keep layering on conflicting DNS options.
| Symptom | Check First | What You Can't Conclude Yet |
|---|---|---|
| Queries time out immediately | Resolver address, network path and outbound dependencies | It doesn't prove that node authentication failed |
| Resolution works, but the connection fails | Actual destination address, address family and remote port | It doesn't prove DNS is correct |
| Results differ between apps | App-specific DNS resolution and proxy settings | It doesn't mean the core is routing randomly |
| Domain rule doesn't match | Whether the inbound destination still contains domain information | Changing DNS alone may not fix it |
Avoid DNS and Proxy Dependency Loops
If the proxy server address is a domain and the resolver can only be reached through that proxy, the setup can create a startup dependency loop. With encrypted DNS, consider how the endpoint's domain is initially resolved too. Make clear which step handles bootstrap resolution, which route works without the proxy, and whether those routes are actually reachable on the current network.
Cores can handle DNS query routing, tags and local resolution modes differently. Pasting an encrypted DNS address into a string doesn't guarantee that its queries will use a particular proxy outbound. If you need remote resolution, check the endpoint format, core support and outbound path. If you use the system resolver, account for the operating system's own cache and network settings.
When testing DNS changes, app and system caches or existing connections can hide the result. Close old connections in the test app, clear caches using the platform's supported method, then test the same domain again. Don't substitute a large batch of random domains for a planned test. Keep the before-and-after resolution target and logs so you can distinguish a changed DNS answer from a changed outbound route.
DNS isn't an isolated performance toggle. Routing may depend on DNS results, while DNS connections may depend on the outbound path. Map those dependencies before changing domain rules or address-family settings. If the basic connection isn't working yet, go back to a simple DNS configuration instead of adding complex domain categories, fallback restrictions and multiple forwarding layers.
06 / policy: Timeouts, User Levels & Statistics
Policies Are Linked by Level, Not Display Name
policy sets connection-handling policies and some statistics toggles. Keys under levels are string representations of user-level IDs. How a connection gets assigned a level depends on the protocol's user fields or inbound settings. Client group names, server notes and subscription names aren't policy levels; changing those labels won't select a different set of timeout values.
When no user level is explicitly set, common configurations typically use level zero, but check the behavior of the current protocol and core. To use different levels, find the level field in the relevant user object and use the same ID in the policy table. Defining a level that no connection uses won't affect existing connections or prove that policy support is working.
Timeouts Define Phase Boundaries
The values below are teaching examples, not documentation of core defaults. handshake sets a limit for the handshake phase; connIdle controls idle connections; and uplinkOnly and downlinkOnly affect how long to wait after one direction ends. These fields are commonly measured in seconds. Check the documentation for your core, and don't treat them as speed-test or bandwidth settings.
{
"policy": {
"levels": {
"0": {
"handshake": 8,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5,
"statsUserUplink": false,
"statsUserDownlink": false
}
},
"system": {
"statsInboundUplink": false,
"statsInboundDownlink": false
}
}
}
A handshake timeout that's too short may abort connections during network jitter or a slightly slow server response. A much longer timeout won't make a wrong password or incompatible transport work. For handshake failures, first check whether the log reports a timeout, authentication error or protocol mismatch. Adjust the timeout only when a viable connection is being cut off during the waiting period.
An idle connection isn't the same as one carrying continuous data. Browser connection pools, messaging sessions and terminal sessions may go without app data for a while. An idle timeout that's too short can force them to reconnect. If the first request fails after a period of inactivity, consider app reconnection behavior, network-device keepalives and the remote server too. The time gap alone doesn't prove a local policy is at fault.
One Direction Ending Doesn't End the Whole Session
Some protocols allow one side to stop sending while it continues to receive the remaining data. Half-close waiting parameters handle this state; they aren't a way to make downloads last longer. Setting the wait too low may cut off useful cleanup, while setting it too high may keep dead connections around longer. Unless there's a specific problem to address, keeping the core defaults is easier to maintain than copying a set of supposed “optimization” values.
Policy can't fix packet loss on the underlying network, a remote server refusing connections or an incorrect certificate service name. If a failure happens before TCP connects, changing an idle timeout that applies after the protocol handshake won't help. Identify the phase in the error: local listener, DNS resolution, remote connection, protocol handshake or sustained transfer. Each points to different settings; don't guess across phases.
Configure Statistics Separately from How They're Displayed
The example explicitly turns off the related statistics toggles to show where the fields go. Enabling user statistics usually also requires identifiable user information and the relevant statistics feature. Making data available to another program may additionally require a statistics object, an API service and a controlled access point. Changing one boolean to true won't automatically add a usable statistics panel to the client interface.
Statistics fields aren't the same as a service provider's billing data. The core may count bytes at a particular layer, while the remote service uses a different method. Restarts, core changes and state cleanup may also affect the local totals. If you need to keep records, define what's being measured, the time range and reset conditions first. Don't interpret a local measurement as an account balance or final bill.
A safe way to change policy is to keep the defaults and adjust one setting only for a reproducible issue. Record the original value, the behavior you're investigating and the retest result; revert if it doesn't help. Make sure the connection was actually re-established so an old connection doesn't mask the result. Policy manages connection lifecycles and should be addressed after the protocol and routing are confirmed.
07 / Client-Generated Config, Subscriptions & TUN
Are You Editing Source Data or the Running Configuration?
v2rayN is suited to managing servers and launching a core on desktop. Android users primarily use v2rayNG, with v2flyNG as an alternative for the V2Fly core. Their interface settings and persistence differ, but each requires you to distinguish server data, client preferences and the final core configuration. Don't assume one client's configuration directory structure applies to another.
A client-generated runtime file may be overwritten when you switch servers or routing modes, or restart the client. Editing a temporary file can appear to work briefly without persisting. Before making a change, confirm whether it belongs in custom configuration, routing settings or ordinary server parameters. For full control of the JSON, use a custom configuration mechanism explicitly supported by the client and check what it overrides.
A Subscription Provides Servers; It Isn't a Top-Level Config Field
A subscription link typically retrieves server entries or configuration content supported by the client. It's processed at the client layer. subscription isn't a universal field to add to the top-level V2Ray object. Updating a subscription doesn't guarantee that the core has switched to a new server: the client may update the list while the selected item, active connection and running process remain unchanged. Check each separately.
If an update fails, first check that the link is complete, you still have access and the response format is supported by the client. A login page, error message or empty response can all cause a parsing failure. Don't post subscription links publicly or include query parameters containing access credentials in log screenshots. For a full troubleshooting workflow, see the subscription update troubleshooting checklist.
TUN Captures Traffic at the Entry Point and Adds Routing
TUN uses a virtual network interface to feed traffic into a processing chain. A client may use a different core or helper component that supports this entry point, and its configuration model may not be the V2Ray-style JSON covered here. The presence of a TUN toggle in the interface doesn't mean you should add a top-level object with the same name. First identify the component TUN actually uses, its permission requirements and configuration format; then look up the relevant fields.
The system proxy mainly affects apps that honor proxy settings. TUN coverage depends on system routing, exclusions, address families and the implementation. Some traffic may still bypass it, or incorrect routes may disrupt network access. “Global” doesn't mean every program and protocol necessarily uses the same outbound. Verify the result against routing rules and app behavior.
With TUN, take particular care not to send the proxy's own connection back through the virtual interface. Clients usually set the required bypass and exclusions; manually adding routes can break them. If the core can't connect to the server after TUN is enabled, first check for a capture loop, then check permissions, the virtual interface and DNS. See v2rayN TUN mode setup guide for the steps.
| Interface Feature | Main Layer | What to Check After Saving |
|---|---|---|
| Subscription update | Client data management | Did the entries update, and did you reselect a server? |
| System proxy | Operating-system proxy settings | Do the proxy type and local port match? |
| Routing mode | Client preset and generated configuration | Did the rule order or default outbound change? |
| TUN | Virtual interface and traffic-capture component | Permissions, routes, DNS and exclusions |
When migrating a configuration, first note the client and actual core names, then list the protocols and extensions in use. An outbound or security option supported by Xray may not be readable by V2Fly; being part of the Project V ecosystem doesn't guarantee permanent field compatibility. After changing cores, recheck configuration loading, the handshake and routing rather than just confirming that the client window opens.
A backup should include the source data needed to understand and restore the setup, not just a temporary generated file. Keep custom rules, notes about changes and any necessary client exports, and protect server credentials. When sharing troubleshooting material, replace sensitive credentials with clearly fake values. Then inspect nested objects, notes and subscription query parameters too; don't just cover the first line in the interface.
If you need to reinstall, choose the right OS and architecture from the client and platform downloads. Similar filenames don't guarantee compatibility. After installation, restore a minimal configuration first, then bring back subscriptions, routing and traffic-capture settings one at a time. If you import all the old settings before testing, it's hard to tell whether a failure comes from the new client environment or compatibility issues in the old configuration.
08 / Configuration Validation, Logs & Rollback
Narrow It Down by Failure Stage
Start troubleshooting by identifying the layer where the failure occurs. An open client window only shows that the interface is running. The core must start successfully for its configuration to have passed the relevant loading process. A connection to the local port still doesn't prove remote authentication succeeded. Test these conditions separately to avoid changing servers when the JSON has a syntax error, or changing routing when the app isn't using the proxy.
Before saving, check the basics: matching brackets, closed strings, commas between array items and correct field value types. Then check the meaning of the structure: referenced outbounds exist, rule data is readable and protocol fields are supported by the current core. Passing strict JSON parsing proves syntax only. It doesn't prove that the core understands every field or that the configuration matches the actual server.
Set the Log Level for Troubleshooting, Then Restore It
warning reduces routine output, but may not show which outbound was selected. For troubleshooting, temporarily switch to info, or briefly use a more detailed level supported by the current core if needed. Each level displays different events; a missing log entry doesn't prove that an event didn't happen. Detailed logs may include destination addresses, so collect them only as long as needed and limit who can see them.
{
"log": {
"loglevel": "info"
}
}
This snippet only replaces the log object; it isn't a complete configuration that can be started on its own. If you configure an output file path separately, make sure the core process can write to it and that the path exists in the current runtime environment. Portable folders, protected installation directories and mobile storage permissions can behave differently. If the log file is empty, check the path and permissions before concluding that the core produced no events.
Troubleshooting Order for Common Symptoms
| Symptom | First Check | Next Check |
|---|---|---|
| Configuration fails to load | JSON syntax, field types and core support | External rule data and file paths |
| Local connection refused | Core process, listening address and port | Port conflicts and app proxy type |
| Remote connection times out | Server address, port and connection path | DNS results, address family and firewall |
| Handshake or authentication fails | Protocol parameters, credentials and transport settings | Service name, system time and security settings |
| Unexpected outbound selected | Rule order and actual destination details | Default outbound, rule data and configuration overrides |
If a local connection is refused, there's no need to start with remote DNS. Confirm the client is still running, the core hasn't crashed immediately after startup, and the app is using the actual listening port. If multiple clients are running, identify which process you're testing. Port conflicts often happen when a client is still running in the background; closing the visible window may not stop its service.
Treat remote connection timeouts and authentication failures separately. For a timeout, first check whether the destination is reachable, the domain resolves to the expected address and the outbound avoids looping back to itself. For authentication failures, compare the protocol, password or user ID, and transport security settings. Changing several unrelated fields at once turns a clear error into multiple overlapping problems and makes logs harder to compare.
If only certain destinations fail, test with the same app and destination each time. Check whether the app bypasses the proxy, uses its own DNS or keeps an old connection, then inspect the matched rule and final outbound. A successful speed test doesn't replace testing the actual app: it may use a different destination, connection method and set of routing conditions. See the glossary for related terms.
Retest One Variable at a Time and Keep a Rollback Record
Change only one set of fields with a direct cause-and-effect relationship at a time. For example, when changing a local port, update the app's connection port too—but don't also change DNS, the outbound protocol and routing mode. After saving, reload or restart the core as required by the client, then create a new test connection. An existing long-lived connection may keep using the old path; refreshing a page doesn't always create a new connection.
A retest record should include why you made the change, the original and updated configuration snippets, the test destination and what the logs showed. There's no need to claim unverified performance improvements or keep every log indefinitely. “This rule selected the wrong outbound” is more useful than “the network is slow” without context. If the change doesn't help, restore the original value and confirm the baseline still works.
For everyday use, let the client handle routine settings it can represent reliably, and reserve custom JSON for behavior that genuinely requires it. If you can't find a setting, see the v2rayN main window guide. For the initial setup sequence, follow Getting Started. This manual explains fields and boundaries; actual behavior always depends on the current core, running configuration and retest results.