Skip to main content

Part 1: Core Settings

core.json is the startup configuration file read by the WaterWall executable. It configures process-wide behavior: logging, worker count, memory profile, MTU, optional Linux TCP and BBR tuning, external library path, shared DNS resolution, and the list of node config files to load.

It does not define tunnel nodes directly. Tunnel chains live in the files listed under configs.

Important Startup Rules​

With no startup option or environment override, WaterWall reads ./core.json from the process working directory. The usual layout is:

waterwall/
WaterWall
core.json
configs/
server.json

Run WaterWall from that directory:

cd ~/waterwall
./WaterWall

To select another core JSON source, use any one of these equivalent CLI forms:

-c:PATH
--c:PATH
-config:PATH
--config:PATH
config:PATH

The colon is required. PATH may be relative to the process working directory or absolute. The special value stdin reads the core JSON from standard input:

./WaterWall --config:/etc/waterwall/core.json
./WaterWall --config:stdin < core.json

The WW_CORE_JSON_INPUT environment variable accepts the same path or stdin value. Input selection follows this precedence:

CLI option > WW_CORE_JSON_INPUT > ./core.json

core.json must be valid JSON. Do not put // comments in it. The comment and variable substitution features described later for node config files are handled by the node config loader, not by the core settings parser.

Selecting another core input does not change the process working directory. All paths inside core.json are passed as written, so relative paths—including entries in configs—remain relative to the directory used to start WaterWall, not to the selected core file's directory.

Top-Level Structure​

{
"log": {},
"misc": {},
"dns": {},
"configs": []
}
FieldTypeRequiredPurpose
configsarray of stringsyesNode config files to parse and run.
logobjectnoLogger paths, files, log levels, and console output.
miscobjectnoWorker count, memory profile, MTU, Linux BBR tuning, and library path.
dnsobjectnoShared async DNS resolver and default domain strategy.

configs is mandatory and must contain at least one string path. The other sections are optional, but production deployments should normally keep misc and dns.domain-strategy explicit.

Do not put domain-strategy at the root of core.json:

{
"domain-strategy": "prefer-ipv4"
}

That form is rejected during startup. Use:

{
"dns": {
"domain-strategy": "prefer-ipv4"
}
}

configs​

configs tells WaterWall which node config files to load after the core runtime is initialized.

{
"configs": [
"configs/server.json",
"configs/reverse.json"
]
}

Rules:

RuleBehavior
Missing configsStartup fails.
Empty arrayStartup fails.
No string entriesStartup fails.
Relative pathsResolved relative to the process working directory.
Multiple filesLoaded by the node manager after core settings are parsed.

Use one config file for small setups and multiple files when you want to split large deployments into separate chain groups.

log​

log configures four process loggers.

LoggerPurpose
internalLow-level runtime and internal diagnostics.
coreStartup, config parsing, and manager-level logs.
networkNetwork and tunnel runtime logs.
dnsShared async DNS resolver logs.

Each logger accepts the same fields:

FieldTypeDefaultNotes
loglevelstring"INFO"Minimum level written by that logger.
filestringlogger-specificFile name appended to log.path.
consolebooleantrueAlso print this logger to the console.

log.path is the base directory used to build log file paths. Its default is "log/". WaterWall creates the log directory during startup if needed.

Defaults:

FieldDefault
log.path"log/"
log.internal.loglevel"INFO"
log.internal.file"internal.log"
log.internal.consoletrue
log.core.loglevel"INFO"
log.core.file"core.log"
log.core.consoletrue
log.network.loglevel"INFO"
log.network.file"network.log"
log.network.consoletrue
log.dns.loglevel"INFO"
log.dns.file"dns.log"
log.dns.consoletrue

Accepted log levels:

VERBOSE, DEBUG, INFO, WARN, ERROR, FATAL, SILENT

The logger uppercases the configured value before applying it, so "debug" and "DEBUG" are treated the same by the logger layer.

To show all log levels for one run, add --verbose:

./WaterWall --verbose
./WaterWall --verbose --config:/etc/waterwall/core.json

Equivalent aliases are --log, -log, -verbose, --showlog, -showlog, and --debug-log. Specify only one spelling per run.

This enables console output and sets internal, core, network, and dns to VERBOSE, overriding their loglevel and console settings even when logging is SILENT. Enabled log files also receive the additional messages. File paths and disabled file outputs still follow the configuration. The override is applied in memory before logger initialization and does not rewrite core.json. -v still prints the version. Specify --verbose once; it cannot be combined with a version or Windows recovery request.

Example:

{
"log": {
"path": "logs/",
"internal": {
"loglevel": "INFO",
"file": "internal.log",
"console": false
},
"core": {
"loglevel": "INFO",
"file": "core.log",
"console": true
},
"network": {
"loglevel": "WARN",
"file": "network.log",
"console": true
},
"dns": {
"loglevel": "DEBUG",
"file": "dns.log",
"console": true
}
}
}

Operational advice:

SituationRecommendation
First setup or debuggingKeep core, network, and dns console output enabled.
Production with many connectionsUse INFO or WARN for network; avoid very noisy levels unless debugging.
DNS troubleshootingTemporarily set log.dns.loglevel to "DEBUG".
Long-running serviceKeep file logging enabled even if console logging is disabled.

misc​

misc controls process-wide runtime sizing and library loading.

{
"misc": {
"workers": 4,
"ram-profile": "server",
"mtu": 1500,
"splice": true,
"tcp-tune": true,
"try-enabling-bbr": true,
"libs-path": "libs/"
}
}
FieldTypeDefaultValidation and behavior
workersintegerCPU core count0 or negative falls back to CPU core count. Values above 254 are reduced to 254.
ram-profilestring or integer"server"Selects memory sizing for pools and profile-aware node defaults. A number must be a whole number in 0..6. Invalid values stop startup.
mtuinteger1500Construction-time default for per-node MTUs. Must be a whole number in 68..65535. Invalid values stop startup.
splicebooleantrueAllow splice on eligible stream chains. false disables splice eligibility for every chain. Platform support and support from every node are still required; packet chains remain ineligible.
tcp-tunebooleantrue on Linux; false otherwiseBest-effort core startup tuning with 128 MiB socket ceilings and TCP autotuning maxima; also raises RAM-based TCP memory thresholds when splice is enabled.
try-enabling-bbrbooleantrueLinux-only best-effort attempt to enable TCP BBR if the running kernel reports BBR support.
libs-pathstring"libs/"Directory used when loading external tunnel libraries.

The defaults above apply whether misc is present, empty, or absent entirely - including mtu and ram-profile, which an omitted block used to leave unset. Invalid values are never replaced by a default: they stop startup with the field named. For predictable deployments, keep the whole misc block explicit anyway.

workers​

workers controls how many worker threads WaterWall creates.

On Linux, automatic worker sizing uses the online logical CPU count from get_nprocs(), excluding configured CPUs that are offline.

Use cases:

DeploymentStarting point
Small client or test instance1 or 2
General VPS serverCPU core count
Heavy traffic serverCPU core count, then tune from CPU and latency metrics

More workers are not always better. Each worker has its own event loop and some per-worker resources. Very high values are capped to 254.

ram-profile​

String values:

ValueInternal profileTypical use
"server"L2 memory profileDefault server-side profile with larger pools.
"client"M1 memory profileGeneric client-side profile.
"client-larger"M2 memory profileLarger client-side profile.
"minimal"S1 memory profileMinimal memory profile.
"ultralow"S1 memory profileAlias of "minimal".

The string value is lowercased by the parser before matching. Prefer string values in new configs.

Legacy integer values are also accepted:

ValueProfile
0 or 1S1 memory profile
2S2 memory profile
3M1 memory profile
4M2 memory profile
5L1 memory profile
6L2 memory profile

The profile also selects defaults for memory-sensitive node settings. In particular, omitted MuxClient and MuxServer detached-buffer-limit and detached-child-limit values scale from the S1 minimum to the L2 maximum; explicit node settings still override them. The Mux node references list the exact limits for every profile.

Invalid numeric or string values stop startup.

mtu​

mtu supplies the construction-time default for node MTUs. Each node validates and stores its own value; overrides never change the core default or other nodes. It is not one enforced MTU for every node or a measured path MTU.

Use 1500 unless your topology requires a different value. Packet tunnels, TUN devices, encapsulation layers, and VPN-like chains may need a smaller effective MTU to avoid fragmentation.

The parser requires a whole number in 68..65535 - RFC 791's minimum IPv4 MTU up to what the field can hold. A fractional value, a wrong type, or anything outside that range terminates startup with the field named, rather than being quietly replaced by a default.

splice​

On Linux, keeping both splice and tcp-tune enabled is recommended for supported stream chains to benefit from splice and the intended startup TCP tuning.

splice defaults to true, including when misc is empty or omitted. Set it to false to use the ordinary TCP read/write path instead of splice. The setting is applied when chains are finalized at startup, after internally inserted nodes are known. Enabling it permits splice only where the platform and every node support it. Values other than JSON true or false stop startup with a configuration error.

Splice has a process-wide pipe-capacity ceiling selected by ram-profile:

ProfilePipe-capacity ceiling
S1 (minimal, ultralow, numeric 0 or 1)32 MiB
S2 (numeric 2)64 MiB
M1 (client, numeric 3)128 MiB
M2 (client-larger, numeric 4)256 MiB
L1 (numeric 5)384 MiB
L2 (server, numeric 6)512 MiB

When RAM detection is available at startup, the budget is capped further at 20% of effective maximum RAM: host physical RAM bounded by applicable finite container/cgroup limits. It uses total capacity rather than currently free RAM, and rounds down to whole 1 MiB pipes. The RAM cap may reduce the budget below 32 MiB, including to zero. If detection is unavailable, WaterWall warns and uses the profile ceiling. For example, server with 640 MiB effective RAM gets a 128 MiB budget.

On supported Linux builds, enabled splice with a nonzero budget also makes a best-effort startup request to raise fs.pipe-user-pages-soft to that budget converted to system pages, capped by fs.pipe-user-pages-hard when that limit is nonzero. These settings apply system-wide to each user's pipe usage. WaterWall preserves an unlimited soft limit (0) and any soft limit already at or above the target; lack of permission or another failure does not stop startup. It never changes the hard limit or fs.pipe-max-size, or writes persistent sysctl configuration. A successful increase remains in effect after exit.

After every startup configuration's chains are finalized, and before prepare/start hooks or producers run, WaterWall records the fixed maximum required padding and initializes an empty shared splice inventory. Startup creates no splice buffers or pipes. The number of pipes that can be created is also capped at floor((RLIMIT_NOFILE.soft - open_descriptors) / 8): each pipe has two descriptors, so inventory uses at most one quarter of the remaining descriptor allowance, leaving the rest for configuration and runtime resources. The /proc/self/fd count includes its temporary scan descriptor conservatively; an unavailable limit or scan prevents pipe creation. Startup logs the configured capacity ceiling and zero allocated capacity. The budget bounds kernel pipe capacity; it does not reserve that much physical payload memory.

Each worker caches up to eight unused splice buffers. An empty local cache draws from the shared master, then creates a small refill on demand within the byte and descriptor ceilings. Each new buffer gets a full-sized 1 MiB private pipe; smaller pipes are refused. Concurrent workers share the same growth budget. If no entry can be obtained, callers normally fall back to ordinary buffers, even if other worker caches retain pipes. Kernel refusals stop growth briefly without disabling reuse of existing pipes. Healthy pipes are retained until shutdown. An unusable pipe is retired without restoring its creation allowance. Chains that never request splice buffers create no pipes. Setting splice to false skips both pipe-limit tuning and inventory setup.

tcp-tune​

tcp-tune defaults to true on native Linux and false on other platforms, including Android and Cygwin. Omitted or empty misc blocks use the same default. Only a JSON boolean is accepted. Set "tcp-tune": false to skip this core startup phase. The four fixed socket/TCP buffer settings apply even when splice is disabled; the RAM-based tcp_mem step requires both flags to be enabled. try-enabling-bbr remains independent. On other platforms, explicit true is accepted but performs no tuning.

After runtime logging is ready and before loading node configurations, WaterWall attempts these four fixed live sysctl writes, independent of the memory profile:

SettingValue
net.core.rmem_max134217728 (128 MiB)
net.core.wmem_max134217728 (128 MiB)
net.ipv4.tcp_rmem4096 87380 134217728
net.ipv4.tcp_wmem4096 87380 134217728

When both tcp-tune and splice are enabled, it also tries to increase the system-wide net.ipv4.tcp_mem thresholds using total host physical RAM and the system page size. Setting "splice": false skips this step. With unit = floor(floor(total_ram_bytes / page_size) / 16), the target is 3 * unit, 4 * unit, 6 * unit pages. This preserves the 3:4:6 ratio: approximately 18.75% of RAM for leaving pressure mode, 25% for entering it, and 37.5% for the maximum TCP-accounting threshold. These values do not preallocate memory and do not override cgroup limits.

WaterWall reads the current triple first and preserves it if any target value would lower an existing threshold. A changed triple is read back before success is logged. Invalid or unavailable memory/page-size information, unreadable settings, failed writes, and verification failures are nonfatal and do not skip the other tuning commands. This host-wide target does not use the RAM profile, current free RAM, or a container's memory quota.

This phase does not change net.core.netdev_max_backlog or net.core.somaxconn. A chain containing either MuxClient or MuxServer activates TcpListener's and TcpConnector's 16 MiB send/receive defaults for omitted options, independently of tcp-tune or sysctl success. Without a Mux node, omitted options use kernel defaults. Explicit large-send-buffer and large-recv-buffer settings always take precedence, including connector destination overrides.

Each failed command logs a warning, and the remaining commands are still attempted; permission denial or a missing sysctl never fails startup. Successful writes change the live kernel settings and are not restored on exit. WaterWall does not persist them in /etc/sysctl.conf.

RawSocket's separate tuning batch, controlled by skip-sysctl, tunes backlogs and other TCP parameters. The buffer and tcp_mem writes above belong exclusively to the core tcp-tune phase.

try-enabling-bbr​

When this is true on Linux, WaterWall checks the current TCP congestion control and net.ipv4.tcp_available_congestion_control. If the running kernel reports bbr and BBR is not already active, WaterWall applies live sysctl values for net.core.default_qdisc=fq and net.ipv4.tcp_congestion_control=bbr.

This is a best-effort startup tuning step. WaterWall does not install kernels and does not edit /etc/sysctl.conf. Set it to false if you do not want WaterWall to try enabling BBR at startup. tcp-tune controls the separate socket/TCP memory tuning phase.

libs-path​

libs-path points to the directory used for external tunnel libraries.

Most normal deployments use the built-in nodes and can leave this as:

{
"misc": {
"libs-path": "libs/"
}
}

Only change it when you intentionally load external node libraries from another directory.

dns​

dns configures the shared c-ares based async resolver used by WaterWall workers. It also sets the default domain address selection strategy used by connector nodes.

If dns is omitted or empty:

SettingDefault
Resolver optionsWaterWall's c-ares defaults
dns.domain-strategy"prefer-ipv4"
timeout-ms1000
max-timeout-ms5000
tries2
query-cache-max-ttl1800
server-failover.retry-chance10
server-failover.retry-delay-ms5000

Other DNS behavior options are passed to c-ares only when configured. If omitted, c-ares and the operating system resolver configuration decide their behavior.

dns.domain-strategy​

domain-strategy controls how WaterWall chooses an address when DNS returns IPv4 and/or IPv6 records.

This is the core default for nodes that do not set their own strategy. TcpConnector and UdpConnector can override it in their own settings. Weighted TcpConnector.addresses entries can also override it per destination.

Valid string values:

ValueBehavior
"prefer-ipv4"Choose the first IPv4 address if one is returned; otherwise use IPv6. This is the core default.
"prefer-ipv6"Choose the first IPv6 address if one is returned; otherwise use IPv4.
"only-ipv4"Use only IPv4 results. If no IPv4 address is returned, the result is unusable for that connection.
"only-ipv6"Use only IPv6 results. If no IPv6 address is returned, the result is unusable for that connection.
"accept-dns-returned-order"Use the first usable address in the order returned by DNS.

String matching is case-insensitive.

Legacy integer values:

ValueStrategy
0accept-dns-returned-order
1prefer-ipv4
2prefer-ipv6
3only-ipv4
4only-ipv6

Example:

{
"dns": {
"domain-strategy": "prefer-ipv4"
}
}

Practical guidance:

EnvironmentStrategy
Most IPv4 VPS deployments"prefer-ipv4"
IPv6-first network"prefer-ipv6"
IPv4-only server or firewall rules"only-ipv4"
IPv6-only deployment"only-ipv6"
You trust resolver ordering"accept-dns-returned-order"

Resolver Timing and Cache​

FieldTypeDefaultValidationMeaning
timeout-msinteger1000greater than 0Initial DNS query timeout in milliseconds.
max-timeout-msinteger5000greater than 0Maximum DNS query timeout after retries.
triesinteger2greater than 0Number of query attempts.
query-cache-max-ttlinteger18000 or greaterMaximum DNS cache TTL in seconds.

Example:

{
"dns": {
"timeout-ms": 750,
"max-timeout-ms": 3000,
"tries": 2,
"query-cache-max-ttl": 600
}
}

Lower timeouts make failed domains fail faster but can hurt reliability on slow or lossy networks. A lower cache TTL makes DNS changes visible sooner but increases resolver traffic.

Resolver Behavior Options​

These options are optional. When omitted, c-ares defaults apply.

FieldTypeValidationMeaning
ndotsinteger0 to 15Number of dots required before c-ares tries a name as absolute.
udp-portinteger1 to 65535DNS UDP server port.
tcp-portinteger1 to 65535DNS TCP server port.
socket-send-buffer-sizeintegergreater than 0DNS socket send buffer size.
socket-receive-buffer-sizeintegergreater than 0DNS socket receive buffer size.
edns-packet-sizeinteger1 to 65535EDNS packet size.
udp-max-queriesinteger0 or greaterc-ares UDP query limit.
rotatebooleanbooleanEnable or disable c-ares server rotation.

Example:

{
"dns": {
"ndots": 1,
"udp-port": 53,
"tcp-port": 53,
"socket-send-buffer-size": 262144,
"socket-receive-buffer-size": 262144,
"edns-packet-size": 1232,
"udp-max-queries": 0,
"rotate": true
}
}

Avoid setting these unless you know why you need them. For most users, servers, domain-strategy, and the timing options are enough.

Search and Source Options​

FieldTypeValidationMeaning
domainsarray of stringsnon-empty array; non-empty stringsSearch domains passed to c-ares.
lookupsstringonly b and f; no repeated charactersLookup source order. b means DNS, f means hosts file.
resolvconf-pathstringnon-empty stringCustom resolv.conf path.
hosts-pathstringnon-empty stringCustom hosts file path.
sortliststringnon-empty stringc-ares sortlist string.
serversstring or array of stringsnon-empty; array items cannot contain commasDNS servers passed to c-ares.

servers may be a single c-ares CSV-style string or an array. When it is an array, WaterWall joins the entries with commas before passing them to c-ares.

Example:

{
"dns": {
"servers": [
"1.1.1.1",
"8.8.8.8"
],
"lookups": "bf",
"domains": [
"example.com"
],
"resolvconf-path": "/etc/resolv.conf",
"hosts-path": "/etc/hosts",
"sortlist": "10.0.0.0/8"
}
}

lookups values:

ValueMeaning
"b"DNS only.
"f"Hosts file only.
"bf"Try DNS, then hosts file.
"fb"Try hosts file, then DNS.

Do not repeat lookup sources. "bb", "ff", and "bfb" are rejected.

dns.flags​

flags configures supported c-ares flags. It can be written in four forms.

Numeric bitmask:

{
"dns": {
"flags": 0
}
}

Single flag name:

{
"dns": {
"flags": "edns"
}
}

Array of flag names:

{
"dns": {
"flags": [
"edns",
"dns0x20"
]
}
}

Boolean object:

{
"dns": {
"flags": {
"edns": true,
"dns0x20": true,
"use-vc": false
}
}
}

In object syntax, true enables a flag and false leaves it disabled.

Supported flag names:

Namec-ares flag
"usevc"ARES_FLAG_USEVC
"use-vc"ARES_FLAG_USEVC
"tcp"ARES_FLAG_USEVC
"primary"ARES_FLAG_PRIMARY
"igntc"ARES_FLAG_IGNTC
"ignore-truncated"ARES_FLAG_IGNTC
"norecurse"ARES_FLAG_NORECURSE
"no-recurse"ARES_FLAG_NORECURSE
"stayopen"ARES_FLAG_STAYOPEN
"stay-open"ARES_FLAG_STAYOPEN
"no-search"ARES_FLAG_NOSEARCH
"no-aliases"ARES_FLAG_NOALIASES
"nocheckresp"ARES_FLAG_NOCHECKRESP
"no-check-response"ARES_FLAG_NOCHECKRESP
"edns"ARES_FLAG_EDNS
"no-default-server"ARES_FLAG_NO_DFLT_SVR
"no-dflt-svr"ARES_FLAG_NO_DFLT_SVR
"dns0x20"ARES_FLAG_DNS0x20

Unknown flag names stop startup. Numeric bitmasks must only contain supported c-ares flag bits.

dns.server-failover​

server-failover configures c-ares server failover options.

FieldTypeDefaultValidationMeaning
retry-chanceinteger100 to 65535c-ares retry chance value.
retry-delay-msinteger50000 or greaterDelay before retrying a failed DNS server.

Example:

{
"dns": {
"server-failover": {
"retry-chance": 25,
"retry-delay-ms": 2000
}
}
}

If server-failover is present, it must be an object. Its fields are optional; omitted fields keep their defaults.

Complete Structural Example​

This example shows every currently documented core section and most DNS fields. Use it as a reference, not as a recommendation to tune every DNS value.

{
"log": {
"path": "log/",
"internal": {
"loglevel": "INFO",
"file": "internal.log",
"console": true
},
"core": {
"loglevel": "INFO",
"file": "core.log",
"console": true
},
"network": {
"loglevel": "INFO",
"file": "network.log",
"console": true
},
"dns": {
"loglevel": "INFO",
"file": "dns.log",
"console": true
}
},
"misc": {
"workers": 4,
"ram-profile": "server",
"mtu": 1500,
"try-enabling-bbr": true,
"libs-path": "libs/"
},
"dns": {
"domain-strategy": "prefer-ipv4",
"timeout-ms": 1000,
"max-timeout-ms": 5000,
"tries": 2,
"query-cache-max-ttl": 1800,
"ndots": 1,
"udp-port": 53,
"tcp-port": 53,
"socket-send-buffer-size": 262144,
"socket-receive-buffer-size": 262144,
"edns-packet-size": 1232,
"udp-max-queries": 0,
"flags": [
"edns",
"dns0x20"
],
"rotate": true,
"domains": [
"example.com"
],
"lookups": "bf",
"resolvconf-path": "/etc/resolv.conf",
"hosts-path": "/etc/hosts",
"sortlist": "10.0.0.0/8",
"servers": [
"1.1.1.1",
"8.8.8.8"
],
"server-failover": {
"retry-chance": 10,
"retry-delay-ms": 5000
}
},
"configs": [
"configs/server.json"
]
}

Minimal Practical Example​

For most users, start smaller:

{
"log": {
"path": "logs/",
"core": {
"loglevel": "INFO",
"file": "core.log",
"console": true
},
"network": {
"loglevel": "INFO",
"file": "network.log",
"console": true
},
"dns": {
"loglevel": "INFO",
"file": "dns.log",
"console": true
},
"internal": {
"loglevel": "INFO",
"file": "internal.log",
"console": true
}
},
"misc": {
"workers": 4,
"ram-profile": "server",
"mtu": 1500,
"try-enabling-bbr": true,
"libs-path": "libs/"
},
"dns": {
"domain-strategy": "prefer-ipv4"
},
"configs": [
"configs/server.json"
]
}

DNS-Tuned Example​

This example is useful when you want explicit DNS servers and faster diagnosis of resolver behavior:

{
"log": {
"path": "logs/",
"core": {
"loglevel": "INFO",
"file": "core.log",
"console": true
},
"network": {
"loglevel": "WARN",
"file": "network.log",
"console": false
},
"dns": {
"loglevel": "DEBUG",
"file": "dns.log",
"console": true
},
"internal": {
"loglevel": "INFO",
"file": "internal.log",
"console": false
}
},
"misc": {
"workers": 8,
"ram-profile": "client-larger",
"mtu": 1500,
"try-enabling-bbr": true,
"libs-path": "libs/"
},
"dns": {
"domain-strategy": "prefer-ipv4",
"servers": [
"1.1.1.1",
"8.8.8.8"
],
"timeout-ms": 1000,
"max-timeout-ms": 5000,
"tries": 2,
"query-cache-max-ttl": 600,
"lookups": "bf",
"flags": [
"edns",
"dns0x20"
],
"rotate": true,
"server-failover": {
"retry-chance": 10,
"retry-delay-ms": 5000
}
},
"configs": [
"configs/server.json",
"configs/reverse.json"
]
}

Common Startup Mistakes​

MistakeResultFix
core.json contains // commentsJSON parse errorRemove comments from core.json.
configs is missing or emptyStartup failsAdd at least one config file path.
A configs path is relative to the wrong directoryConfig file cannot be readStart WaterWall from the intended working directory or use correct paths.
domain-strategy is at the rootStartup failsMove it to dns.domain-strategy.
dns is not an objectStartup failsUse an object or omit dns.
dns.flags has an unknown nameStartup failsUse one of the documented flag names.
misc.ram-profile has a typoStartup failsUse server, client, client-larger, minimal, or ultralow.
Too many workersValue is reduced to 254Use a realistic worker count.

What to Tune First​

Start with only these values:

GoalField
Run the right chainsconfigs
Control CPU usagemisc.workers
Choose server/client memory behaviormisc.ram-profile
Make logs usefullog.network.loglevel, log.dns.loglevel
Prefer IPv4 or IPv6dns.domain-strategy
Use known resolversdns.servers

Leave advanced DNS options alone until you have a specific reason to change them. The defaults are intentionally conservative for ordinary deployments.