---
name: sing-box-reality-vps-bootstrap
description: "Use when provisioning or diagnosing a minimal VLESS REALITY Vision endpoint on a VPS with the official sing-box container, especially when existing services must stay online and route loss must be separated from protocol or client problems."
metadata:
  id: "sing-box-reality-vps-bootstrap"
  type: "engineering"
  title: "Bootstrap and diagnose a minimal sing-box REALITY VPS"
  summary_zh: "新 VPS 部署 sing-box REALITY 时用官方容器最小模板，并用跨协议对照决定是否启用 BBR 或直接处理线路"
  tags: "docker,networking,self-hosted,debugging"
  created: "2026-07-14"
  last_verified: "2026-07-14"
  status: "valid"
  context_cost: "high"
  produces: "workflow_checklist"
  verify_command: "On the target VPS, render the compose file with docker compose config (or docker-compose config), run sing-box check against the mounted config, require a healthy container and an active synchronized NTP peer; from an authorized client, require five authenticated HTTP 204 probes, the expected exit IP, and the chosen unauthenticated TLS fallback certificate, then compare a tunnel transfer with an SSH downlink control before retaining kernel tuning."
  verify_expected: "The official container starts healthy with the audited config, REALITY authentication and fallback both work, time synchronization satisfies max_time_difference, pre-existing services remain active, and any retained BBR/fq change has a measured throughput or retransmission benefit rather than only a lower-looking benchmark number."
  verify_failure_next: "Stop at the first failing layer and roll back the last change: fix schema or credentials before routing, fix NTP before REALITY, and if the tunnel and SSH control are both slow while VPS egress is healthy, stop changing sing-box and investigate the provider return path or local ISP."
---

## Trigger

Load when:

- provisioning a new VPS for one small, auditable VLESS REALITY Vision
  endpoint without a panel, database, or third-party installer;
- migrating the endpoint to a better server while preserving unrelated
  services on the old or new host;
- the endpoint is reachable but feels slow, and the task is to distinguish
  sing-box configuration from TCP loss, routing, or client behavior;
- an opaque service label may be shown in dashboards, while protocol details
  and credentials must remain private.

This card is a deployment and diagnosis delta, not a claim that one protocol,
SNI, congestion algorithm, or VPS route is universally best.

## Fix

Apply the following deployment, verification, and diagnosis workflow in order.
Keep host-specific credentials and volatile route evidence outside this card.

## Preflight

1. Record the target host, SSH alias, current services, listening ports, Docker
   state, default interface, clock source, and current TCP settings. Never
   assume port 443 or a service name is free.
2. Back up only the files and runtime values that may change. Do not restart or
   reconfigure unrelated services to make the new endpoint fit.
3. Confirm the host has a synchronized clock. REALITY time validation is not a
   substitute for NTP:

   ```bash
   timedatectl status
   ntpq -pn 2>/dev/null || chronyc tracking 2>/dev/null || true
   ```

4. Inspect ports and the default route without hard-coding interface names:

   ```bash
   ss -lntup
   ip route show default
   docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
   ```

5. Keep credentials in an owner-only location. Never print or publish the
   client URI, UUID, REALITY private key, or Short ID in logs, dashboards,
   cards, tickets, or command transcripts.

## Build from the official image

Use the official GHCR image and the image's own generators so credentials match
the deployed core. Lihan's default is the official `latest` tag with manual
pull-and-verify updates, not an automatic updater or third-party wrapper.

```bash
docker run --rm ghcr.io/sagernet/sing-box:latest generate uuid
docker run --rm ghcr.io/sagernet/sing-box:latest generate reality-keypair
openssl rand -hex 8
```

Capture the outputs privately. Put the private key only on the server and the
public key only in authorized client configuration.

Use an audited Compose baseline:

```yaml
services:
  tunnel:
    image: ghcr.io/sagernet/sing-box:latest
    container_name: opaque-service-name
    restart: unless-stopped
    network_mode: host
    read_only: true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    security_opt:
      - no-new-privileges:true
    volumes:
      - ./config.json:/etc/sing-box/config.json:ro
    tmpfs:
      - /tmp:rw,noexec,nosuid,size=16m
    command:
      - run
      - -c
      - /etc/sing-box/config.json
    healthcheck:
      test:
        - CMD
        - sing-box
        - check
        - -c
        - /etc/sing-box/config.json
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    logging:
      driver: json-file
      options:
        max-size: "5m"
        max-file: "3"
```

Host networking avoids an extra Docker NAT layer. The read-only filesystem,
read-only config mount, minimal capability set, bounded temporary filesystem,
and log rotation are higher-value controls than adding a management panel.

## Audited server configuration

Replace every `__...__` value before validation:

```json
{
  "log": {
    "level": "warn",
    "timestamp": true
  },
  "dns": {
    "servers": [
      {
        "type": "https",
        "tag": "doh",
        "server": "1.1.1.1",
        "server_port": 443,
        "path": "/dns-query",
        "tls": {
          "enabled": true,
          "server_name": "cloudflare-dns.com"
        }
      }
    ],
    "final": "doh",
    "strategy": "ipv4_only"
  },
  "inbounds": [
    {
      "type": "vless",
      "tag": "tunnel-in",
      "listen": "0.0.0.0",
      "listen_port": 443,
      "users": [
        {
          "uuid": "__UUID__",
          "flow": "xtls-rprx-vision"
        }
      ],
      "tls": {
        "enabled": true,
        "server_name": "__HANDSHAKE_NAME__",
        "reality": {
          "enabled": true,
          "handshake": {
            "server": "__HANDSHAKE_NAME__",
            "server_port": 443,
            "domain_resolver": {
              "server": "doh",
              "strategy": "ipv4_only"
            }
          },
          "private_key": "__REALITY_PRIVATE_KEY__",
          "short_id": [
            "__HEX_SHORT_ID__"
          ],
          "max_time_difference": "1m"
        }
      }
    }
  ],
  "outbounds": [
    {
      "type": "direct",
      "tag": "direct"
    }
  ],
  "route": {
    "rules": [
      {
        "port": [25, 465, 587, 2525],
        "action": "reject"
      },
      {
        "action": "resolve",
        "server": "doh",
        "strategy": "ipv4_only"
      },
      {
        "ip_is_private": true,
        "action": "reject"
      },
      {
        "ip_version": 6,
        "action": "reject"
      }
    ],
    "final": "direct"
  }
}
```

The unconditional resolve action makes the later private-address and IPv6
rules effective for domain requests. Do not remove it while claiming those
destinations are blocked.

## Choose and verify the REALITY handshake target

- Do not hard-code a brand from an old deployment. Re-evaluate the target with
  the exact sing-box version being deployed.
- Prefer a stable TLS 1.3 site reachable from the VPS, with a certificate and
  SNI behavior that survive repeated handshakes. A nearby CDN edge can make
  the decoy handshake more consistent, but does not reroute bulk client data.
- Avoid sensitive or operationally unstable domains. Popularity alone is not
  proof of safety, and obscurity alone is not proof of compatibility.
- Test repeated authenticated connections and unauthenticated fallback. The
  fallback must present the chosen site's legitimate certificate rather than a
  sing-box or self-signed certificate.
- Keep `max_time_difference` enabled only with working NTP. One minute is a
  practical starting window; tighten it only with evidence that every client
  clock is reliably synchronized.

Changing SNI cannot turn a poor VPS-to-client route into a low-latency route.
Use traceroute and TCP evidence against the VPS address for route diagnosis.

## Performance changes require an A/B test

Start with the distribution kernel and inspect reality before tuning:

```bash
sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc \
  net.ipv4.tcp_mtu_probing
cat /proc/sys/net/ipv4/tcp_available_congestion_control
modinfo tcp_bbr >/dev/null 2>&1 && echo bbr-module-available
```

Record a same-time baseline using both an authenticated tunnel transfer and an
SSH downlink control from the same VPS. Also record TCP retransmissions. Ping
alone is not a throughput test.

If the stock BBR module exists, a reversible runtime trial may be justified:

```bash
iface="$(ip route show default | awk '{print $5; exit}')"
modprobe tcp_bbr
sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr
tc qdisc replace dev "$iface" root fq
```

These are host-wide settings. Record the old congestion control and qdisc,
understand their effect on other traffic, and restart only the new tunnel
service so its listener inherits the trial. Keep BBR/fq only when the same
transfer materially improves; it will not reduce geographic RTT or repair the
provider route. Persist it only after the A/B result passes.

Do not install a third-party BBRv3, XanMod, TCP Brutal, or omnibus tuning script
to solve a one-off symptom. Do not raise conntrack, file, or socket-buffer
limits without saturation evidence. Test path MTU before enabling MTU probing.

## Options deliberately left off

- **Multiplex**: sing-box upstream states that Vision has no meaning when
  multiplex is enabled. On a lossy TCP path it can also share head-of-line
  blocking across unrelated streams.
- **TCP Brutal**: requires an extra kernel module and multiplex configuration,
  adds fixed-bandwidth assumptions, and expands the maintenance boundary.
- **TCP Fast Open**: needs support and configuration at both ends. Enabling one
  side or changing the host bitmask without an A/B result is not optimization.
- **MPTCP and giant buffers**: add state without evidence that path diversity or
  receive-window limits are the bottleneck.
- **Frequent SNI rotation**: can break REALITY compatibility while leaving the
  actual route untouched.
- **GUI panels and automatic updaters**: add databases, credentials, exposed
  surfaces, and third-party supply-chain code to a service that does not need
  them.

## Diagnose the slow layer

Use independent controls instead of interpreting every slow page as blocking:

1. Measure small-packet RTT, loss, and jitter to the VPS.
2. Measure VPS direct egress to a public download endpoint.
3. Measure the same endpoint through the authenticated tunnel.
4. Download a fixed byte count over SSH from the same VPS as a protocol-neutral
   return-path control.
5. During the transfer, inspect `ss -tin` for the actual connection: congestion
   algorithm, RTT, `cwnd`, retransmissions, delivery rate, receive-window
   limitation, MSS, and PMTU.
6. Use DF ping or tracepath-style tests before declaring an MTU black hole.
7. Trace the VPS-to-client return path with TCP, ICMP, or UDP and ASN lookup;
   hidden hops mean one traceroute is not proof that an advertised ASN is
   absent.

Decision rule:

- tunnel slow, SSH fast, VPS egress fast: inspect client mode, credentials,
  protocol options, DNS, and tunnel configuration;
- tunnel and SSH both slow, VPS egress fast: stop editing sing-box and inspect
  the provider return path, local ISP, time of day, and TCP retransmissions;
- SOCKS fast but TUN/browser slow: inspect client DNS interception, TUN cost,
  and HTTP/3 behavior;
- small ping stable but TCP retransmissions high: reachability is healthy while
  bulk stability is not.

## Client and monitoring boundary

- Use remote DNS through the proxy with strict routing or the client's
  supported FakeIP/FakeDNS design. Do not let local plaintext DNS bypass the
  tunnel when leak resistance is required.
- Keep uTLS fingerprint and REALITY public parameters consistent with the
  generated client profile.
- If browser pages stall while ordinary TCP tests are healthy, A/B reject
  client UDP port 443 to force HTTP/3 fallback. Do not make that global policy
  without a measured benefit.
- A five-minute external TCP monitor proves that port 443 accepts connections;
  it does not prove authentication, route quality, or usable throughput.
- Dashboards may use an opaque service name. Store protocol details and client
  credentials only in the owning secret/configuration surface.

## Verification and rollback

Before launch:

```bash
docker compose config >/dev/null 2>&1 || docker-compose config >/dev/null
docker run --rm \
  -v "$PWD/config.json:/etc/sing-box/config.json:ro" \
  ghcr.io/sagernet/sing-box:latest \
  check -c /etc/sing-box/config.json
```

After launch, require all of the following:

- container is running and healthy;
- expected port is listening once;
- pre-existing services retain their original active state and listeners;
- NTP has an active synchronized peer;
- unauthenticated TLS shows the selected fallback certificate;
- five authenticated probes succeed and the public exit IP is the VPS;
- container logs contain no schema, REALITY, DNS, or route initialization
  errors;
- temporary client processes, listeners, and secret-bearing test files are
  removed.

Rollback the last layer, not the whole host. Restore the previous config or
Compose file for a schema/runtime failure. Restore the previous congestion
control and qdisc if the A/B trial fails, then restart only the tunnel service.
If independent SSH and tunnel transfers share the same loss, preserve the
working endpoint and pursue a route repair or server migration.

## Reuse Rule

- **Load when**: building or migrating Lihan's minimal official-container
  sing-box REALITY endpoint, reviewing its security boundary, or diagnosing
  high RTT/low throughput without disturbing other VPS services.
- **Do not load when**: the target uses a managed panel or a different
  transport whose official deployment model is materially different; the task
  is only IP reputation/unlocking; or the user has authorized a broader relay,
  QUIC, kernel, or provider migration architecture that needs its own review.
- Re-check the current sing-box schema and handshake target on every new VPS.
  Keep stable method in this card and volatile host state in the execution or
  incident record.
