LoadBalance
Structure
{
"type": "loadbalance",
"tag": "lb",
"primary_outbounds": [
"proxy-a",
"proxy-b",
"proxy-c"
],
"backup_outbounds": [
"proxy-backup-1",
"proxy-backup-2"
],
"url": "https://www.gstatic.com/generate_204",
"interval": "3m",
"timeout": "5s",
"idle_timeout": "30m",
"top_n": {
"primary": 3,
"backup": 1
},
"tolerance": 50,
"strategy": "consistent_hash",
"hash": {
"key_parts": ["src_ip", "matched_ruleset_or_etld"],
"virtual_nodes": 100,
"on_empty_key": "random",
"key_salt": ""
},
"hysteresis": {
"primary_failures": 3,
"backup_hold_time": "5m"
},
"empty_pool_action": "error",
"interrupt_exist_connections": false,
"prefer_domain": true
}
LoadBalance Overview
The LoadBalance outbound provides intelligent load distribution across multiple proxy servers with health checking, failover, and flexible routing strategies. It supports URL-test driven Top-N selection (similar to HAProxy), consistent hashing, and automatic failover with hysteresis.
Fields
primary_outbounds
Required
List of primary outbound tags to load balance across. These outbounds are continuously health-checked and ranked by latency.
backup_outbounds
List of backup outbound tags. Backup outbounds are only used when primary outbounds are unavailable or when hysteresis conditions trigger failover.
url
The URL to test for health checking. https://www.gstatic.com/generate_204 will be used if empty.
interval
Health check interval. 3m will be used if empty.
timeout
Health check timeout per outbound. 5s will be used if empty.
idle_timeout
The idle timeout. 30m will be used if empty.
top_n
URL-test driven Top-N selection configuration. This determines how many healthy outbounds from each pool are used for load balancing.
top_n.primary
Number of fastest primary outbounds to use. Default: use all healthy primary outbounds.
When set to a value like 3, only the 3 fastest (lowest latency) primary outbounds will be included in the load balancing pool.
top_n.backup
Number of fastest backup outbounds to use. Default: use all healthy backup outbounds.
tolerance
Tolerance threshold in milliseconds for Top-N candidate pool stabilization. Previous Top-N members are retained if their latency is within tolerance ms of the cutoff. Reduces hash ring rebuilds for consistent_hash. Default: 0 (disabled).
When enabled, this setting adds stability to the candidate selection process by preventing rapid changes when nodes have similar latencies. For example, with tolerance: 50 and Top-3 selection, a node that was previously in the Top-3 but is now ranked 4th with only a 30ms increase will still be eligible for selection, preventing unnecessary hash ring rebuilds and sticky session disruptions.
strategy
Required
Load balancing strategy. Available values:
random: Random selection from healthy outboundsconsistent_hash: Consistent hashing based on connection metadata
hash
Consistent hash configuration. Only used when strategy is consistent_hash.
hash.key_parts
Required for consistent_hash
Array of metadata fields to use for hash key construction. Parts are joined with | separator.
Available key parts:
| Key Part | Description | Example |
|---|---|---|
src_ip |
Source IP address | 192.168.1.100 |
dst_ip |
Destination IP address | 8.8.8.8 |
src_port |
Source port number | 12345 |
dst_port |
Destination port number | 443 |
network |
Network type | tcp or udp |
domain |
Full destination domain name | api.example.com |
inbound_tag |
Tag of the inbound that accepted the connection | mixed-in |
matched_ruleset |
Tag of the ruleset that matched this connection | geosite-netflix |
etld_plus_one |
eTLD+1 of the destination domain (PSL-based) | example.com |
matched_ruleset_or_etld |
Smart fallback: Use matched ruleset if available, otherwise eTLD+1 | See below |
dst_asn |
Destination IP's Autonomous System Number (requires ASN database) | AS13335 |
dst_geosite |
Destination domain's geosite category (requires geosite database) | geosite:google |
Common Configurations:
- Traditional 5-tuple hashing:
["src_ip", "dst_ip", "dst_port"] - Per-source IP:
["src_ip"] - By ruleset category:
["src_ip", "matched_ruleset"] - By top domain:
["src_ip", "etld_plus_one"] - Smart routing:
["src_ip", "matched_ruleset_or_etld"] - By ASN (CDN optimization):
["src_ip", "dst_asn"] - By geosite (service grouping):
["src_ip", "dst_geosite"]
hash.virtual_nodes
Number of virtual nodes per real outbound in the consistent hash ring. Higher values provide better distribution but use more memory. Default: 100.
Recommended values: 50-200
hash.on_empty_key
Behavior when hash key is empty. Available values:
random: Select a random outbound (default)hash_empty: Hash the empty string (always selects the same outbound)
hash.key_salt
Optional salt prefix for hash key namespace isolation. Useful when multiple LoadBalance instances need different hash rings.
hysteresis
Hysteresis configuration for failover damping. Prevents rapid switching between primary and backup pools.
hysteresis.primary_failures
Number of consecutive primary pool failures before failing over to backup pool. Default: 3.
hysteresis.backup_hold_time
Minimum time to stay on backup pool before returning to primary pool. Default: 30s.
empty_pool_action
Action to take when all outbounds are unavailable. Available values:
error: Return an error for the connection (default)fallback_all: Attempt all outbounds regardless of health status
interrupt_exist_connections
Interrupt existing connections when the active outbound pool changes.
Only inbound connections are affected by this setting, internal connections will always be interrupted.
prefer_domain
Same as the prefer_domain option in dial fields.
When enabled on a group outbound, the IP destination is overridden with the sniffed domain name before the connection is handed to the selected child outbound.
Hash-Based Routing Modes
matched_ruleset_or_etld: Smart Fallback Mode
The matched_ruleset_or_etld key part provides intelligent routing that adapts based on whether the connection matched a rule set.
Priority Logic:
- Ruleset Priority: If the connection matched a ruleset (e.g.,
geosite-netflix,geosite-openai), use the ruleset tag for hashing - Domain Fallback: If no ruleset matched, extract the eTLD+1 (top domain) using Public Suffix List
Behavior Examples:
| Scenario | Input | Hash Key Part | Description |
|---|---|---|---|
| Ruleset matched | api.netflix.com matched geosite-netflix |
geosite-netflix |
Uses ruleset tag |
| No match, valid domain | cdn.example.com (no match) |
example.com |
Extracts eTLD+1 |
| No match, subdomain | a.b.example.com (no match) |
example.com |
Groups by top domain |
| Multi-part TLD | www.example.co.uk (no match) |
example.co.uk |
PSL-aware extraction |
| IP address | 8.8.8.8 (no match) |
- |
Placeholder for IPs |
Full Example:
With configuration ["src_ip", "matched_ruleset_or_etld"]:
- Request from
192.168.1.100toapi.netflix.comwith rulesetgeosite-netflix→ hash key:192.168.1.100|geosite-netflix - Request from
192.168.1.100tocdn1.example.com(no ruleset) → hash key:192.168.1.100|example.com - Request from
192.168.1.100tocdn2.example.com(no ruleset) → hash key:192.168.1.100|example.com(same as above)
Benefits:
- Unified configuration: Single hash mode handles both rule-based and direct traffic
- Content category routing: Rule-matched traffic routes by category (e.g., all Netflix traffic to same outbound)
- Domain grouping: Non-matched traffic routes by top domain (e.g., all example.com subdomains together)
- Sticky sessions: Same source + category/domain always uses same outbound
Use Cases:
- CDN optimization: Route all subdomains of a CDN to the same outbound
- Service isolation: Keep video streaming, API calls, and CDN traffic consistent per source
- Mixed routing: Handle both categorized (via rulesets) and uncategorized traffic intelligently
Configuration Examples
Example 1: Simple Round-Robin with Health Checking
{
"type": "loadbalance",
"tag": "lb-simple",
"primary_outbounds": [
"proxy-1",
"proxy-2",
"proxy-3"
],
"url": "https://www.gstatic.com/generate_204",
"interval": "3m",
"strategy": "random"
}
Randomly distributes connections across all healthy proxies.
Example 2: Top-N Selection with Failover
{
"type": "loadbalance",
"tag": "lb-topn",
"primary_outbounds": [
"proxy-1",
"proxy-2",
"proxy-3",
"proxy-4",
"proxy-5"
],
"backup_outbounds": [
"proxy-backup-1",
"proxy-backup-2"
],
"url": "https://www.gstatic.com/generate_204",
"interval": "2m",
"timeout": "5s",
"top_n": {
"primary": 3,
"backup": 1
},
"strategy": "random",
"hysteresis": {
"primary_failures": 3,
"backup_hold_time": "5m"
}
}
Uses the 3 fastest primary proxies. Falls back to the fastest backup proxy after 3 consecutive failures, and stays on backup for at least 5 minutes.
Example 3: Per-Source IP Consistent Hashing
{
"type": "loadbalance",
"tag": "lb-per-ip",
"primary_outbounds": [
"proxy-1",
"proxy-2",
"proxy-3",
"proxy-4"
],
"url": "https://www.gstatic.com/generate_204",
"interval": "3m",
"strategy": "consistent_hash",
"hash": {
"key_parts": ["src_ip"],
"virtual_nodes": 100
}
}
Each source IP always routes to the same proxy (sticky sessions per client).
Example 4: Ruleset-Based Routing
{
"type": "loadbalance",
"tag": "lb-by-category",
"primary_outbounds": [
"proxy-1",
"proxy-2",
"proxy-3",
"proxy-4"
],
"url": "https://www.gstatic.com/generate_204",
"interval": "3m",
"strategy": "consistent_hash",
"hash": {
"key_parts": ["src_ip", "matched_ruleset"],
"virtual_nodes": 100
}
}
Routes by source IP + ruleset category. Same source accessing same category (e.g., Netflix) always uses same proxy.
Requires route configuration:
{
"route": {
"rule_set": [
{
"tag": "geosite-netflix",
"type": "remote",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-netflix.srs"
}
],
"rules": [
{
"rule_set": ["geosite-netflix"],
"outbound": "lb-by-category"
}
]
}
}
Example 5: Top Domain Routing
{
"type": "loadbalance",
"tag": "lb-by-domain",
"primary_outbounds": [
"proxy-1",
"proxy-2",
"proxy-3",
"proxy-4"
],
"url": "https://www.gstatic.com/generate_204",
"interval": "3m",
"strategy": "consistent_hash",
"hash": {
"key_parts": ["src_ip", "etld_plus_one"],
"virtual_nodes": 100
}
}
Routes by source IP + top domain. All subdomains of example.com from the same source use the same proxy.
Examples:
- 192.168.1.100 → api.example.com and cdn.example.com → same proxy
- 192.168.1.100 → example.com and example.co.uk → different proxies
Example 6: Smart Routing with Ruleset Fallback
{
"type": "loadbalance",
"tag": "lb-smart",
"primary_outbounds": [
"proxy-1",
"proxy-2",
"proxy-3",
"proxy-4"
],
"url": "https://www.gstatic.com/generate_204",
"interval": "3m",
"top_n": {
"primary": 3
},
"strategy": "consistent_hash",
"hash": {
"key_parts": ["src_ip", "matched_ruleset_or_etld"],
"virtual_nodes": 100,
"on_empty_key": "random"
}
}
Smart routing behavior:
- Categorized traffic (matched by rulesets): Routes by category
- Netflix traffic →
192.168.1.100|geosite-netflix -
Google traffic →
192.168.1.100|geosite-google -
Uncategorized traffic (no ruleset match): Routes by top domain
cdn1.example.com→192.168.1.100|example.comcdn2.example.com→192.168.1.100|example.com(same as above)api.other.com→192.168.1.100|other.com(different)
Benefits: - Handles both rule-based and direct traffic intelligently - No need for separate LoadBalance instances - Consistent routing for both categorized and uncategorized content
Example 7: ASN-Based Routing (CDN Optimization)
{
"route": {
"asn": {
"path": "asn.mmdb",
"download_url": "https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-ASN.mmdb"
},
"rules": [
{
"outbound": "lb-by-asn"
}
]
},
"outbounds": [
{
"type": "loadbalance",
"tag": "lb-by-asn",
"primary_outbounds": [
"proxy-1",
"proxy-2",
"proxy-3",
"proxy-4"
],
"url": "https://www.gstatic.com/generate_204",
"interval": "3m",
"strategy": "consistent_hash",
"hash": {
"key_parts": ["src_ip", "dst_asn"],
"virtual_nodes": 100
}
}
]
}
Routes by source IP + destination ASN (Autonomous System Number). All connections to the same ISP/CDN/cloud provider from the same source use the same proxy.
ASN Grouping Examples: - All Cloudflare IPs (AS13335) → same proxy per user - All AWS IPs (AS16509) → same proxy per user - All Google IPs (AS15169) → same proxy per user
Benefits: - CDN optimization: Maintains connection pooling with CDN edge servers - Session consistency: Keeps all requests to same ASN on same proxy - Network topology awareness: Routes based on destination network, not just IP
Requirements:
- ASN database must be configured in route.asn section
- Database can be downloaded automatically from configured URL
- Supports MaxMind GeoLite2-ASN format (MMDB)
Example 8: Geosite-Based Routing (Service Grouping)
{
"route": {
"geosite": {
"path": "geosite.db",
"download_url": "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
},
"rules": [
{
"outbound": "lb-by-geosite"
}
]
},
"outbounds": [
{
"type": "loadbalance",
"tag": "lb-by-geosite",
"primary_outbounds": [
"proxy-1",
"proxy-2",
"proxy-3",
"proxy-4"
],
"url": "https://www.gstatic.com/generate_204",
"interval": "3m",
"strategy": "consistent_hash",
"hash": {
"key_parts": ["src_ip", "dst_geosite"],
"virtual_nodes": 100
}
}
]
}
Routes by source IP + destination geosite category. All domains within the same geosite category from the same source use the same proxy.
Geosite Grouping Examples:
- All Google services (youtube.com, googlevideo.com, googleapis.com) → geosite:google → same proxy per user
- All Netflix domains (netflix.com, nflxvideo.net, nflxext.com) → geosite:netflix → same proxy per user
- All OpenAI domains (openai.com, oaistatic.com, oaiusercontent.com) → geosite:openai → same proxy per user
Benefits: - Service isolation: Keeps all requests to the same service on same proxy - Session consistency: Maintains cookies and authentication across service subdomains - Category-based routing: Groups conceptually related domains together - Fine-grained control: More specific than domain suffix, more flexible than exact domain
How it works: 1. Load balancer looks up the destination domain in geosite database 2. Finds matching geosite code (e.g., "google", "netflix") 3. Uses the geosite code as part of the hash key 4. All domains in the same geosite category get the same hash → same proxy
Difference from matched_ruleset:
- matched_ruleset: Uses the ruleset tag from routing rules (requires rule to match first)
- dst_geosite: Direct geosite lookup, works without routing rules
- dst_geosite can be used even when no routing rule matches
Requirements:
- Geosite database must be configured in route.geosite section
- Supports sing-box geosite.db format
- Database can be downloaded from SagerNet/sing-geosite repository
Best Practices
Health Checking
- Interval: Use
3mfor most scenarios,1mfor high-availability setups - Timeout: Set to
5sor less to detect failures quickly - URL: Use a lightweight endpoint like
/generate_204or health check endpoints
Top-N Selection
- Primary count: Choose 50-70% of total primary outbounds for balance between redundancy and performance
- Backup count: Usually 1-2 backups are sufficient
- Example: With 6 primary proxies, use
top_n.primary: 4
Consistent Hashing
- Virtual nodes: 100 is a good default. Increase to 200 for better distribution if you have many outbounds
- Key parts: Choose based on your routing needs:
- Session persistence per client:
["src_ip"] - Application-level routing:
["src_ip", "matched_ruleset"] - Domain grouping:
["src_ip", "etld_plus_one"] - Smart hybrid:
["src_ip", "matched_ruleset_or_etld"]
Hysteresis Configuration
- Primary failures: Set to
2-3to avoid flapping during transient failures - Backup hold time: Set to
3m-10mto give primary time to recover - Example:
primary_failures: 3+backup_hold_time: 5mworks well for most cases
Failover Strategy
- With backups: Use
top_nto keep best primaries active, fall back to backups when needed - Without backups: Use
empty_pool_action: "fallback_all"for graceful degradation - High availability: Combine
top_n,hysteresis, and backup pools
Technical Details
Hash Key Construction
Hash keys are constructed by joining key parts with | separator:
- Missing or empty values use
-placeholder - Optional salt prefix:
{key_salt}{part1}|{part2}|{part3} - Hash function: xxHash (fast, high-quality distribution)
Examples:
| Configuration | Metadata | Hash Key |
|---|---|---|
["src_ip", "dst_port"] |
src=192.168.1.100, dst_port=443 | 192.168.1.100\|443 |
["src_ip", "matched_ruleset"] |
src=10.0.0.1, ruleset=geosite-google | 10.0.0.1\|geosite-google |
["src_ip", "etld_plus_one"] |
src=10.0.0.1, domain=api.example.com | 10.0.0.1\|example.com |
With salt "prod-" |
Same as above | prod-10.0.0.1\|example.com |
eTLD+1 Extraction
The etld_plus_one and matched_ruleset_or_etld key parts use the Public Suffix List (PSL) for accurate domain extraction:
- PSL-based: Correctly handles multi-part TLDs like
.co.uk,.com.au,.gov.uk - Normalization: Lowercase, strip ports, strip trailing dots
- IP handling: IPv4 and IPv6 addresses return
-placeholder - Fallback: If PSL extraction fails, returns the normalized full domain
Extraction Examples:
| Input | eTLD+1 | Notes |
|---|---|---|
www.example.com |
example.com |
Standard domain |
api.v2.example.com |
example.com |
Deep subdomain |
example.co.uk |
example.co.uk |
Multi-part TLD (PSL) |
www.example.co.uk |
example.co.uk |
UK subdomain |
example.com:443 |
example.com |
Port stripped |
EXAMPLE.COM |
example.com |
Normalized lowercase |
192.168.1.1 |
- |
IPv4 placeholder |
2001:db8::1 |
- |
IPv6 placeholder |
Consistent Hash Ring
- Algorithm: NGINX-style consistent hashing with virtual nodes
- Virtual nodes: Each outbound gets N positions on the hash ring
- Minimal remapping: When outbounds change, only K/N connections remap (K=total connections, N=outbound count)
- Stability: Same hash key + same outbound set → always same outbound
Health Check States
Outbounds transition through states based on health checks:
- Healthy: Responds successfully within timeout
- Unhealthy: Failed health check or timeout exceeded
- Recovering: Was unhealthy, now responding (uses hysteresis if configured)
Top-N selection only considers healthy outbounds, ranked by response latency.
Troubleshooting
All connections go to one outbound
Cause: Hash key has no variance (e.g., only using dst_port when all traffic goes to port 443)
Solution: Add more dynamic parts like src_ip or domain
Connections switch outbounds frequently
Cause 1: Using random strategy
Solution: Use consistent_hash for sticky sessions
Cause 2: Health check flapping
Solution: Configure hysteresis to dampen rapid switching
No traffic when primary outbounds fail
Cause: No backup outbounds configured and empty_pool_action is reject
Solution: Add backup_outbounds or set empty_pool_action: "fallback_all"
Different hash results than expected
Cause: Missing metadata fields (e.g., matched_ruleset is empty)
Solution: Check that routing rules are setting the expected metadata. Enable debug logging to inspect hash keys.