How to Debug Connectivity Issues in S-UI: A Step-by-Step Guide to Sing-Box Diagnostics

To debug connectivity issues in S-UI, verify the core is running via corePtr.IsRunning(), inspect active connections in ConnTracker, force-close stale connections with CloseConnByInbound(), and test outbound reachability using core.CheckOutbound().

S-UI is a web-based user interface built on the sing-box core that simplifies proxy management, but when clients report "cannot reach the server," administrators need systematic debugging tools. The codebase in alireza0/s-ui exposes granular connection tracking and diagnostic hooks that allow you to pinpoint failures across the inbound, routing, and outbound layers. This guide walks through the exact source code paths and methods used to diagnose network problems.

Understanding S-UI’s Connection Architecture

S-UI’s networking stack centers on the core.Box instance, which creates a route.ConnectionManager and maintains a ConnTracker in core/tracker_conn.go. Every inbound-side TCP or UDP stream passing through the router is wrapped in wrappedConn or wrappedPacketConn and stored in the tracker map, keyed by a unique UUID.

The tracker serves as the source of truth for active sessions. When an inbound configuration changes, old connections may linger and block ports or consume file descriptors. The shouldUntrackIOErr helper (lines 14-27 in core/tracker_conn.go) automatically removes finished connections to prevent memory leaks, but manual intervention is often required during debugging.

Step-by-Step Debugging Workflow

When troubleshooting "cannot reach the server" errors, investigate these layers in sequence:

  1. Core State – Confirm the global corePtr initialized in main.go is running via IsRunning().
  2. Inbound Configuration – Verify the inbound has been reloaded; stale connections persist after config changes unless explicitly closed.
  3. Connection Tracking – Check ConnTracker.connections for entries matching your inbound tag. Use CloseConnByInbound(tag) to force cleanup.
  4. Outbound Reachability – Execute a URL test through the suspect outbound to isolate routing failures.
  5. System Diagnostics – Query CPU, memory, and network stats to rule out resource starvation.
  6. Log Analysis – Pull recent entries with GetLogs() to identify specific errors like "dial timeout" or "peer closed".

Verify Core State

Before inspecting connections, ensure the sing-box core is active. The global pointer corePtr exposes IsRunning(), which returns false if the core has crashed or failed to initialize.

Inspect and Manage Active Connections

Access the live connection map through corePtr.GetInstance().ConnTracker(). Iterate over the connections map to count active sessions for a specific inbound tag. If you observe stale entries from previous configurations, call CloseConnByInbound(tag) to close all associated net.Conn and network.PacketConn instances. This method is invoked automatically in service/inbounds.go during RestartInbounds.

Test Outbound Reachability

The core.CheckOutbound(ctx, tag, testURL) function (defined in core/outbound_check.go) attempts to fetch a URL through the specified outbound. It returns a CheckOutboundResult struct with three fields:

  • OK – Boolean indicating success.
  • Delay – Rounded latency in milliseconds.
  • Error – Human-readable failure message.

This test isolates whether connectivity issues stem from the client-to-inbound path or the outbound-to-destination path.

Check System Resources

Resource exhaustion can manifest as dropped packets or refused connections. ServerService.GetStatus("cpu,net,sys") (from service/server.go) returns JSON-encoded diagnostics. High CPU usage or saturated network interfaces indicate the host, not the proxy configuration, is the bottleneck.

Analyze Logs

Use logger.GetLogs(count, level) to retrieve recent entries. Filtering for "error" level often reveals the exact disconnect reason, such as DNS resolution failures or TLS handshake timeouts.

Practical Code Examples

Verify Core and Count Inbound Connections

// Check if core is running and count connections for tag "socks-in"
if !corePtr.IsRunning() {
    fmt.Println("❌ Core is not running")
    return
}

tag := "socks-in"
tracker := corePtr.GetInstance().ConnTracker()

cnt := 0
for _, c := range tracker.connections {
    if c.Inbound == tag {
        cnt++
    }
}
fmt.Printf("🔌 %d active connections for inbound %s\n", cnt, tag)

Reset Connections for a Specific Inbound

// Reproduce RestartInbounds logic from service/inbounds.go
func resetInbound(tag string) error {
    if !corePtr.IsRunning() {
        return fmt.Errorf("core not running")
    }
    
    // Close stale connections
    closed := corePtr.GetInstance().ConnTracker().CloseConnByInbound(tag)
    fmt.Printf("🗑️ Closed %d stale connections for %s\n", closed, tag)

    // Fetch and reload configuration (simplified)
    inboundConfig, err := fetchInboundJSON(tag) // user-defined helper
    if err != nil {
        return err
    }
    
    // Add users if inbound type supports them
    inboundConfig, err = inboundService.addUsers(db, inboundConfig, inboundId, inboundType)
    if err != nil {
        return err
    }
    
    return corePtr.AddInbound(inboundConfig)
}

Test Outbound Endpoint

ctx := context.Background()
outTag := "proxy-1"
testURL := "https://www.google.com/generate_204"

result := core.CheckOutbound(ctx, outTag, testURL)
if result.OK {
    fmt.Printf("✅ Outbound %s reachable, latency %d ms\n", outTag, result.Delay)
} else {
    fmt.Printf("❌ Outbound %s error: %s\n", outTag, result.Error)
}

Pull System Diagnostics

svc := service.ServerService{}
status := svc.GetStatus("cpu,mem,net,sys")
b, _ := json.MarshalIndent(status, "", "  ")
fmt.Println(string(b))

Retrieve Recent Error Logs

logs := service.ServerService{}.GetLogs("20", "error")
for _, line := range logs {
    fmt.Println(line)
}

Key Source Files for Connectivity Debugging

File Purpose
core/box.go Initializes the sing-box core, registers managers, and exposes the corePtr global state.
core/tracker_conn.go Implements ConnTracker with CloseConnByInbound() and connection untracking logic.
service/inbounds.go Contains RestartInbounds(), which forces clean reloads and closes lingering connections.
core/outbound_check.go Provides CheckOutbound() for latency testing and error detection.
service/server.go Exports GetStatus() for CPU, memory, and network resource monitoring.

Summary

  • Verify core state with corePtr.IsRunning() before investigating connection issues.
  • Inspect active connections in core/tracker_conn.go using ConnTracker to identify stale sessions.
  • Force-close connections via CloseConnByInbound(tag) when reloading inbound configurations.
  • Test outbound paths with core.CheckOutbound() to isolate routing failures.
  • Monitor system resources through ServerService.GetStatus() to detect resource starvation.
  • Analyze logs using GetLogs() to capture specific error messages from the sing-box core.

Frequently Asked Questions

How do I know if the S-UI core is actually running?

Check the global corePtr instance exposed in core/box.go. Calling corePtr.IsRunning() returns a boolean indicating whether the sing-box core has successfully initialized and is currently active. If this returns false, no connections can be established regardless of configuration correctness.

What causes stale connections to block new inbound traffic?

When you modify an inbound configuration via the UI or API, the old listener may close but existing wrappedConn or wrappedPacketConn instances remain in the ConnTracker map. These sockets hold file descriptors and can prevent port rebinding. Call CloseConnByInbound(tag) to forcibly terminate all connections associated with that inbound tag.

How can I test if an outbound server is reachable from S-UI?

Use the core.CheckOutbound() function in core/outbound_check.go. Pass a context, the outbound tag, and a test URL such as https://www.google.com/generate_204. The function returns a CheckOutboundResult indicating success, latency in milliseconds, or a specific error string detailing the failure reason.

Where does S-UI store active connection metadata?

Active connections are stored in the connections map inside the ConnTracker struct defined in core/tracker_conn.go. This map uses a UUID as the key and stores connection wrappers that include the inbound tag, network type (TCP/UDP), and underlying net.Conn or network.PacketConn interfaces.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →