From beginner to advanced · A complete reference guide

V2Ray Configuration Guide
From Subscription Imports to Routing and TUN

Build a solid configuration workflow in nine stages. For Android, start with v2rayNG; for desktop, we recommend v2rayN. Explore v2flyNG if you need the v2fly core.

Just want to make your first connection? Start with the quick-start workflow in the user guide. This page is a reference for understanding what each setting affects, how to verify changes, and where to start when something goes wrong. Read from Chapter 1 if you’re new, or jump to the relevant section if you already have a setup. Menu labels may vary by client and build, so refer to the interface and logs you’re using.

Stage 1: Understand Clients, Cores, and Connection Paths

Know the difference between the interface and the core that runs your configuration

What you see when you open a client is its graphical interface. The interface saves server entries, updates subscriptions, generates configuration, and starts the service; the core handles the actual connections. v2rayNG primarily uses the Xray core, while v2flyNG uses the v2fly core. v2rayN is a desktop client, and the core it uses depends on the selected configuration and the client’s support. The interface accepting a field doesn’t mean the active core can use it.

Project V provides useful background for understanding this proxy ecosystem. V2Fly and Xray are related core families, not three interchangeable app names. When looking up a setting, first check whether the instructions refer to a client menu or a core field. If you come across an unfamiliar term, see the glossary. Don’t replace your current client’s entire configuration with one for another core just because both use the V2Ray name.

Follow a request from its entry point to its outbound route

A request usually starts in an app, enters a local proxy, follows a routing rule to an outbound, and then connects to the destination. A desktop system proxy directs apps that honor the setting to the local proxy. On Android, the VPN service receives traffic through a virtual network interface provided by the system. The entry point receives requests; the outbound determines how they’re sent. Which entry point a request uses and which outbound it takes are separate questions.

A server entry describes how to connect to a remote server. Common fields include the address, port, user ID, protocol, transport, and security settings. A subscription provides a URL for retrieving multiple entries; it isn’t itself a connection. A successful subscription update only means the configuration was fetched and parsed—it doesn’t prove the remote service is reachable. Conversely, an update failure won’t necessarily affect an existing connection if its entry still works.

Check the protocol, transport, and security layers separately

VMess, VLESS, Trojan, and Shadowsocks are different proxy protocols. WebSocket and gRPC describe transport methods, while TLS and REALITY involve connection security. Use the combination provided by the server; matching protocol names don’t mean all the other settings match. For example, the address and port can be correct while an incorrect transport path, service name, or server name still prevents the connection from being established.

VLESS generally needs an appropriate security layer; its relatively simple protocol design doesn’t mean every connection is encrypted by default. VMess also relies on a reasonably accurate device clock for time-based validation. REALITY requires specific settings such as a public key, short ID, and server name; enabling standard TLS isn’t a substitute. For a comparison, see VMess vs. VLESS: A Quick Protocol Guide.

By the end of this stage, you should be able to identify the client app, core, entry point, and outbound in use. There’s no need to write a complete configuration yet. Start with a connection whose settings have a clear source, then add routing and expand the traffic scope one step at a time. Enabling every advanced feature at once can introduce problems at several layers, making it hard to tell which change helped.

Stage 2: Choose a Client for Your Platform and Configuration

Use v2rayN on desktop and start with v2rayNG on Android

Visit the client downloads page, choose your operating system, then check your processor architecture. We recommend v2rayN for Windows, macOS, and Linux desktops, and v2rayNG for Android. This keeps the steps in the guide aligned with the interfaces you’ll see; it doesn’t mean a subscription will work identically in every environment. Check the required protocol, transport, and system-level traffic handling separately.

If you need v2fly-core features on Android, consider v2flyNG as an alternative. Don’t switch clients just because one connection failed; first find out whether the issue is core support, subscription parsing, network access, or permissions. When moving between Android clients, keep the original app and configuration as a fallback until you’ve verified the new setup. This avoids losing both working configurations before you know the replacement works.

Choose the package format and processor architecture separately

The Windows download page offers the v2rayN desktop edition and the classic WPF edition. The desktop edition uses a newer cross-platform interface, while the WPF edition retains the classic workflow. Menus and some system integrations may differ, so confirm which edition you’re using before following instructions. If you can’t find a button with the same name in the newer interface, don’t assume the feature is missing; check the settings and service menus for an equivalent option.

On macOS, check your chip in System Information: choose arm64 for Apple Silicon or x64 for Intel. On Linux, check the distribution’s package system as well as the architecture; choose deb or rpm based on your distribution, not the file size. Most recent Android phones use arm64, but a capable processor doesn’t guarantee the operating system uses the same bitness. If a package won’t install, check the system information and consider a universal package.

PlatformClientCheck first
Windowsv2rayNDesktop or WPF edition; system requirements
macOSv2rayNApple Silicon or Intel
Androidv2rayNG; v2flyNG as an alternativeSystem architecture and core compatibility
Linuxv2rayNdeb or rpm; x64 or arm64

Check compatibility against your actual requirements

If your server uses VLESS with REALITY, check that the core used by your client supports the relevant fields and that they’re all present after import. For a traditional configuration, don’t add security settings that the server hasn’t specified. Configurations are portable only when both sides support the same fields, not because their app names look similar. Compare the protocol, transport, security layer, and any special options—not just the protocol name.

Consider how you use the device day to day. For a few desktop apps, start with the system proxy or the apps’ own proxy settings. If you need to capture traffic from apps that ignore the system proxy, consider TUN. On Android, check whether another VPN app is already running; the system usually allows only one active VPN service. Choose the traffic scope that fits your needs rather than enabling every available feature.

Summarize your choice in a way you can verify, for example: “On Android, v2rayNG receives traffic from selected apps through the VPN service.” This gives you a clear baseline for installation, permissions, and troubleshooting. To compare the three clients, see our client comparison. This guide follows one configuration workflow rather than treating a client switch as the default fix.

Stage 3: Install the Client and Set Up Permissions

Install and launch on Android

After choosing the right architecture on the download page, open the installation file with the system installer. If Android says the current source isn’t allowed to install apps, open the “Install unknown apps” setting shown in the prompt and temporarily allow only the browser or file manager you’re using. You can turn off that permission after installation. Don’t change unrelated security settings just to get past the prompt, and don’t leave the permission enabled for sources you don’t recognize or trust.

After opening v2rayNG, make sure the main screen loads, then check notification permissions. A persistent notification helps you see the service status, but notification access and VPN permission are separate. When you start the proxy service for the first time, Android displays a separate VPN connection prompt. Check the app name before allowing it. If another VPN service is active, stop it first so a traffic handoff issue isn’t mistaken for a configuration error.

On desktop, check the files and system integration

On Windows, install or extract the package you selected and keep the supporting files the app needs. Don’t move the main executable to another folder by itself. Start with a standard user account; grant administrator access only when a feature you’re enabling requires it. Choose an installation location where your account can save settings, and watch out for protected folders, synced folders, and read-only drives that can prevent changes from being saved.

On macOS, launch the app from Applications. If macOS displays a security warning, confirm the file’s source and follow the system’s available options rather than disabling system protections outright. On Linux, install the package that matches your distribution and launch it from the app menu. If the interface won’t open, note the error and system architecture. You haven’t reached the remote connection stage yet, so changing subscriptions, protocols, or DNS is unlikely to fix an app that won’t launch.

Save your current settings before connecting

In the client settings, note the current local listening port, operating mode, and routing preset. Use the values shown in your interface rather than assuming every version uses the same ports. If another proxy app is installed, first disable its system proxy and virtual interface handling, then start the new client. Closing the old window may leave its background service running, causing a port conflict or sending traffic to the wrong app.

Next, check the system time, time zone, and network connection. Enable automatic time synchronization and make sure the basic network can reach what it needs to. An incorrect clock can cause certificate validity checks to fail, while a captive portal may block requests until you sign in. If your network requires web authentication, stop traffic handling and complete the sign-in first. Don’t disable certificate checks to work around these environmental issues.

At this stage, success isn’t about opening a website. Check that the interface launches, settings save, the service can request the right permissions, and the system network recovers after you exit. Import a subscription only after all four checks pass. If a local port is already in use, see How to Troubleshoot a Local Port Conflict. Fixing the device environment first helps keep local startup issues separate from remote connection problems.

Also note a recovery point for the new setup: record whether the system proxy was on and whether the previous VPN started automatically. Shared or managed devices may have network policies set by an organization; don’t remove them without authorization. To restore your baseline, stop the new service and reinstate the previous network settings instead of installing more clients and hoping they override one another.

Stage 4: Import Subscriptions and Manage Configuration Sources

Save a subscription URL in a group, then update it

In v2rayNG, open the side menu and go to “Subscription group settings.” Add a group and enter a recognizable label and the full subscription URL. Save it, return to the subscription menu on the main screen, and update the subscription. Some versions put the update option in a More menu, but the steps are the same: save the source, then fetch its contents. Adding the URL to a group alone won’t add server entries to the main list.

When copying a URL, check the beginning, end, and middle for stray spaces or line breaks. Subscription URLs may contain account access tokens, so treat them as sensitive. Don’t include them in screenshots, logs, or support requests, and don’t paste them into an unfamiliar online conversion tool. If you need to convert a format, check who provides the tool and how it handles your data, and be aware that conversion may omit protocol fields.

Tell subscriptions, share links, and full configurations apart

A subscription usually contains a set of server entries. A single share link describes one configuration, while a complete core configuration may also include inbounds, outbounds, routing, and DNS. These are different types of input. Before importing from the clipboard, check what you’ve copied and choose the matching import option. Treating a webpage URL as a subscription or a full configuration as a single-node link can result in a parsing error or an empty list.

In v2rayN, open the subscription group settings, add a source, and save it. Then use the subscription update menu to fetch its entries. For the first update, use the standard update option. If the client offers an option to update through the current proxy, use it only if that proxy already works. Otherwise, you can end up in a loop: you need a proxy to update, but need the updated configuration to connect.

After updating, open an entry’s edit screen and compare its address, port, protocol, user ID, transport, and security settings with the server’s documentation. The entry’s label is just a name, not proof that its settings are correct. Check the exact case and full value of any required path, service name, or server name. Don’t guess missing settings from another entry or randomly switch security options.

Back up before an update and check for changes afterward

Subscription updates may overwrite entries, so don’t make lasting changes directly to their key settings. Before experimenting, export or copy an entry as a standalone configuration and label its source. Keep a verified working copy, then update the original group. If an entry disappears, its settings change, or the source is temporarily unavailable, you’ll still be able to tell whether the issue came from the update or the connection environment.

When an update fails, first identify the error: name resolution failure, connection timeout, an error from the remote server, or content that couldn’t be parsed. An expired URL, changed permissions, or a returned login page can all cause failures, but each needs a different fix. Check the source status without sharing the URL, then decide whether to try a different update path. Clearing every configuration won’t fix invalid content returned by the server.

By the end of this stage, keep at least one entry with a known source and complete settings, and know whether a subscription update will overwrite it. Don’t import several subscriptions from unknown sources at once. More sources can mean more duplicate entries, inconsistent settings, and expired configurations. Validate a single configuration first, then expand your groups to make ongoing maintenance easier to track.

Stage 5: Choose a Traffic Entry Point and Proxy Mode, Then Test

System proxy settings and routing presets do different things

In v2rayN, select an imported entry as the active configuration and start the service. Then set the system proxy if needed. The system proxy tells apps that honor it where to send requests; the routing preset determines which outbound handles a request after it reaches the core. Selecting global routing in the interface doesn’t automatically send traffic from every app through the client. Apps that ignore the system proxy may still use the regular network.

For an initial desktop test, start with a browser. Confirm the core has started, configure the browser to use the system proxy, then visit a known working test site. If the browser works but other apps don’t, first check whether those apps use system settings or have their own proxy configured. Don’t switch servers right away. If traffic never reaches the entry point, changing the remote protocol usually won’t help.

On Android, choose between VPN mode and proxy-only mode

v2rayNG’s VPN mode receives traffic through the Android VPN service, and Android displays its VPN status. Proxy-only mode usually provides a local proxy endpoint that apps must be configured to use; it doesn’t automatically capture traffic from every app. For your first setup, follow the guide to choose VPN mode and grant system permission. Then check that per-app settings haven’t excluded the app you want to test.

With per-app proxy settings, distinguish between an allowlist and a bypass mode. In bypass mode, selected apps may be excluded from the VPN; with bypass off, the selected apps may be the ones whose traffic is captured. Follow the explanation in your current interface rather than guessing from the checkboxes. After changing the list, restart the service if required and fully close and reopen the test app so it doesn’t reuse an old connection.

Test each layer instead of relying on a single latency result

A connection test may check only a remote port or one specific request. It doesn’t confirm that every destination, protocol, or DNS lookup works. During testing, check at least three things: whether the core keeps running, whether the test app makes a new request, and whether that request completes as expected. Start with one app and one destination, then expand the scope. If a website won’t load, rule out a problem with the destination and browser cache too.

If every app fails, check startup and handshake information in the logs. If only certain destinations fail, look at routing, DNS, and destination restrictions. If apps that use UDP are the only ones affected, confirm that the client entry point, core, remote server, and network path all support the required traffic. A successful TCP web request doesn’t prove that other traffic works or that all processes on the device are being captured.

ModeWhat must be true to capture trafficWhat to check
Desktop system proxyThe app honors system proxy settingsLocal port and app-specific settings
App-configured proxyThe app is configured with the correct endpointProxy type, address, and port
Android VPNSystem permission granted; app included in traffic scopeActive VPN and per-app rules
Desktop TUNVirtual interface and system routes are workingPermissions, DNS, and bypass routes

After testing, check what happens when you stop the service. Confirm that the system proxy returns to the expected state; if it still points to a local port that’s no longer active, apps using it will lose their connection. Don’t mistake this for an unavailable remote server. A working start and stop process completes the basic setup. Add routing afterward so you can tell traffic-capture issues apart from routing issues.

Stage 6: Build Clear Routing and DNS Rules

Start with rule order and outbound names

Open the client’s routing settings and review the current preset before adding custom rules. Rules are usually evaluated in order: the first matching rule selects an outbound, and later rules don’t handle that request. Put precise domain exceptions before broader rules. First list which requests should go direct, use the proxy, or be blocked; then express those requirements as rules instead of copying a configuration you don’t understand.

Outbound labels are internal configuration references, not protocol names. The snippet below illustrates the rule structure and must be merged into an existing configuration. Make sure the referenced direct and proxy labels match outbounds that actually exist in your configuration. Don’t try to run the snippet as a complete configuration. If the client has a graphical rule editor, use it to create the equivalent rules to reduce the risk that saving will overwrite manual changes.

{
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "ip": ["geoip:private"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "domain": ["domain:example.com"],
        "outboundTag": "proxy"
      }
    ]
  }
}

Domain strategy determines when DNS resolution is used for matching

AsIs tries to match the domain as received, without resolving it to an address just to apply IP rules. IPIfNonMatch attempts a lookup if no domain rule matches, then checks the result against IP rules. IPOnDemand triggers a lookup when a rule needs address information. Supported values depend on the core you’re using. Don’t confuse the routing domain strategy with the address selection settings used when connecting to an outbound.

If the entry point receives only an IP address, domain rules alone may not match as expected. Traffic sniffing can identify a destination name in some protocols, but it can’t recover the domain for every request and shouldn’t be used to work around a DNS error. Before enabling it, check the supported protocols and privacy implications. Then use the logs to see which rules actually match. The domain, address, and information available at the entry point all affect whether a rule can work.

Test the DNS query path separately from application traffic

When changing DNS settings, first find out who performs the lookup: the app, the operating system, a resolver built into the client, or a remote server. Setting a DNS server address doesn’t guarantee that queries will travel through the proxy. The resolver’s own access path, routing rules, and lookups needed to connect to a secure DNS server can all affect the result. Avoid a startup loop in which the resolver depends on a connection that hasn’t been established yet.

Printers, admin pages, and internal domains on a local network may need local DNS and a direct route; a remote resolver may not know those names. First check that the local IP address is reachable, then test the internal name separately. If the address works but the name doesn’t, check DNS before changing the proxy protocol. If the name resolves but the connection times out, check the returned address, outbound route, and destination service.

Add one rule at a time and test one destination that should match and one that shouldn’t. After each test, record the rule’s position, outbound, and resolution method before adding another exception. A good rule set explains why each request takes its route; more rules aren’t necessarily better. If something breaks, return to the verified preset and restore your new rules one by one.

Stage 7: Extend Traffic Capture with VPN and TUN

First decide whether you need to capture more traffic

Consider desktop TUN only if an app ignores the system proxy or you need to capture more types of traffic. TUN passes traffic to the proxy through a virtual network interface and system routes. It won’t fix incorrect server settings or guarantee that the remote server supports every protocol. Before enabling it, verify that the same configuration works through the simpler system proxy path; otherwise, the virtual interface adds more variables to troubleshoot.

Android VPN mode and desktop TUN both involve virtual network interfaces, but their system permissions, routing implementations, and settings differ. Don’t follow desktop driver installation steps on Android, or treat Android VPN permission as the equivalent of desktop administrator access. Desktop TUN behavior also depends on the selected core and the client’s implementation. Follow the options shown in your current interface.

Resolve conflicts and plan a way back before enabling TUN

In v2rayN, find the TUN settings or switch. Read the permission prompt and check whether service mode or system authorization is required. Stop any other tool that captures traffic through a virtual interface, and note the current system proxy state. For the first test, don’t let multiple capture modes change the default route at once. Even if they can coexist, verify each one separately. On a managed computer, follow your organization’s policy and don’t remove existing network components.

After startup, check that the virtual interface is up and the core is still running, then test a regular website, a local network device, and a real-world app. The remote server’s own connection must bypass the capture loop; otherwise, requests may be sent back to the entry point. The client usually handles this, but custom routes, static routes, and other network software can override them. If something goes wrong, first restore the default settings.

Pay particular attention to DNS, UDP, and behavior after a connection drop

TUN capturing traffic doesn’t guarantee that DNS queries reach the intended resolver. Apps may use their own secure DNS, the system may have cached results, and the client may offer different DNS capture options. Choose a clear resolution path and avoid enabling multiple redirections before you understand what each one does. If local network names stop working, check whether local lookups are being sent to a remote resolver unnecessarily.

UDP support depends on the entry point, core, remote server, and network path all supporting it. Test with an app that actually needs UDP and check the relevant logs; a single website visit won’t tell you whether every transport works. If only some apps fail, distinguish app-specific limitations from transport issues. Record the original values before changing virtual interface settings, and don’t combine settings from different devices without testing them.

Switching from Wi-Fi to mobile data or waking a computer from sleep can change the default route and DNS environment. If the connection fails after a network change, stop and restart the client service to rebuild its interface, then check that no old proxy settings remain. If only restarting the device restores service, record the errors before and after the transition. Repeated restarts aren’t a long-term maintenance plan.

Finish by deliberately stopping the service. Confirm that system routes, DNS, and regular network access recover after you exit TUN, then restart it and repeat the tests. If local network access or basic connectivity is affected, turn off TUN and restore the system proxy and previous network settings before troubleshooting further. Keep a verified, simple entry point as a control so you can tell whether a problem is limited to traffic capture without reimporting every subscription.

Stage 8: Back Up, Update, and Troubleshoot Day to Day

Keep working configurations separate from test configurations

Use the client’s export or backup option to save your current settings, and check what the export includes. Some exports contain server entries but omit subscription sources, per-app lists, or routing presets, so record those separately. Backups may contain access credentials; store them securely rather than syncing them to a public folder. After restoring a configuration, check system permissions again instead of assuming device-level settings were transferred too.

Give test entries a clear purpose, such as checking a transport setting or routing exception, rather than leaving similar default labels. Watch for entries being replaced when updating subscriptions, and save working configurations and current settings before updating the client. Don’t update the app, switch cores, rewrite routes, and change DNS all in one maintenance session. The more things change at once, the harder it is to know what to roll back.

Check startup logs before connection logs

If the service won’t start, open the logs and work forward from the earliest clear error. A configuration parse error, missing field, nonexistent outbound label, missing resource file, or port conflict can occur before any remote connection is attempted. Fix the first error that prevents startup, then test again. Later failures may be knock-on effects of the same problem, so you don’t need to change a separate setting for every log line.

If the core has started normally, distinguish between connection timeouts, name resolution failures, authentication errors, and TLS handshake failures. A timeout doesn’t necessarily mean a protocol problem; the address, port, or current network path may be unreachable. For an authentication error, check the user fields against the server’s documentation. For configuration errors, follow How to Check Logs When a Core Won’t Start: verify syntax and fields first, then check entry and outbound references.

Collect only the information needed to diagnose the issue

Before asking for help, note the system, client name, operating mode, action that triggered the issue, and exact error message. Say whether it happens consistently or only after changing networks. Include only the relevant time range from the logs, and redact subscription URLs, user IDs, passwords, and other access tokens. Don’t post your full configuration. The error type and a few surrounding lines are usually more useful than a screenshot that only says “failed.”

For a certificate error, first check the system time and time zone, then confirm the server name and server-side certificate settings. Don’t use “allow insecure” as a default fix; it may only bypass the problem you need to resolve. If the server’s certificate or name has changed, update the relevant fields using the server’s documentation. For more details, see Common Causes of TLS Handshake and Certificate Errors. Don’t try random security settings and hope one works.

Maintenance also includes how the app runs in the background. If v2rayNG stops after the screen locks, first check Android’s battery restrictions, background activity, and auto-start settings. The controls vary by device, so follow the system’s instructions instead of granting every app every permission. If a desktop setup fails after waking from sleep, check whether the core and virtual interface are still working. Troubleshooting based on when the issue occurs is more effective than adding permissions blindly.

Keep a brief record of each maintenance change: what you changed, what you tested, and how to undo it. There’s no need to include sensitive values; record only setting names and results. If the same issue comes up again, compare recent changes and the environment before deciding to update or switch configurations. Reliable day-to-day use comes from repeatable steps and recoverable settings, not a growing stack of untested tweaks.

Stage 9: Move from Graphical Settings to Reading Configurations

Read the generated configuration before making small changes

Use the client’s configuration viewer or export option to find the active configuration, and start by reading it without making changes. Label the purpose of its inbounds, outbounds, routing, and DNS, then match each graphical setting to the part of the configuration it affects. Generated configurations may include fields added automatically by the client; don’t remove them just because the guide doesn’t explain them. Understanding how the interface maps to the configuration is safer than writing a complete configuration from scratch.

Pay particular attention to references, array order, and nesting. Every outbound label referenced by a routing rule must exist; the same field name may mean different things in different objects. JSON quotation marks, commas, and brackets are part of the syntax too. A tool accepting comments doesn’t mean the client’s parser will. Before saving, check the format supported by your current core and don’t mix snippets from different formats.

Build configuration skills with small exercises

For a first exercise, change only the log level and observe the difference. The snippet below shows a common logging configuration; add it to the appropriate part of a complete configuration rather than running it as a standalone file. Use more detailed logging temporarily while troubleshooting, then restore the usual level. Detailed logs may contain destination addresses and connection context, so review them before sharing.

{
  "log": {
    "loglevel": "warning"
  }
}

For a second exercise, add one clear domain routing rule and leave the default outbound unchanged. Choose one test destination that should match and another as a control. Use the logs or the client’s connection details to confirm the route. If you can’t verify that the rule matched, don’t infer success from a website loading; either outbound could reach the same destination. The point of the exercise is to test a hypothesis, not just get a working result.

For a third exercise, map out your DNS dependencies: who resolves a name, where it’s resolved, how the query is sent, and which outbound uses the returned address. Write down those relationships and compare them with the actual configuration. Once you can explain the full path, consider more detailed domain categories, address family selection, or local network exceptions. Don’t split the resolution path just to add complexity; every advanced setting should address a clear, testable need.

Know where client-generated settings end and custom configuration begins

Some clients regenerate the running configuration when you save settings, switch entries, or update a subscription. Direct edits to a temporary generated file may disappear the next time the client starts. Save settings you need to keep through the graphical interface whenever possible. Before using a custom configuration, find out how the client starts, updates, and backs it up. Don’t maintain two sources that overwrite each other; the interface and the running configuration may end up out of sync.

Treat a core switch as a separate experiment too. Check the target core’s supported protocols, security fields, and routing syntax, then test a copy of the configuration without replacing the working setup. Identically named fields don’t guarantee identical behavior. If you hit a compatibility issue, keep the original error and a minimal reproduction with only the necessary structure and redacted sensitive values. Don’t expose real subscriptions or access credentials to reproduce a problem.

Choose your next step based on the problem: return to the quick-start guide to repeat the first-connection workflow; check the FAQ and relevant technical notes for a specific error; or look up unfamiliar fields in the glossary. You don’t need to learn every setting at once. Implement and verify what your device needs now, then expand one layer at a time to keep the configuration readable, maintainable, and recoverable.