Skip to content

Repository files navigation

whitebox
whitebox

whitebox for Prometheus provides availability monitoring of external VPN services powered by VMESS, VLESS, TROJAN, WG, AWG, SS and HYSTERIA.

Go Report Card Docker Image Image Size License

The features that distinguish whitebox:

  • Multi-protocol VPN Probing: Supports probing of external VPN services including VMESS, VLESS, Trojan, Wireguard, AmneziaWG, Shadowsocks and Hysteria2.
  • RESTful API Service: Exposes HTTP endpoints for on-demand or scheduled connectivity checks.
  • Custom Probe Configuration: Accepts probe parameters such as connection details, target URLs, response validation rules, and configurable timeouts.
  • Prometheus Metrics Integration: Exposes key probe results as Prometheus metrics.

Getting Started

whitebox ships a ready-to-run image on Docker Hub: rxyvea/whitebox.

docker run --rm -d --name whitebox -p 9116:9116 rxyvea/whitebox:latest

That is a complete deployment. Whitebox starts with a built-in default scope and listens on :9116.

Verify

Probe any target through a VPN connection URI. ctx is the URL-encoded connection URI, target is what to fetch through the resulting tunnel:

curl -sG http://localhost:9116/probe \
  --data-urlencode 'ctx=vless://c9f5228c-8870-47bd-a92f-9b38c7c02b08@1.2.3.4:443?type=tcp&encryption=none&security=reality&pbk=DF-3KL2W4RuNB2HgsEDmLqHLvvTTN4_QfwUCUn8Uhy0&fp=firefox&sni=ce-cdn.icloud-content.com&sid=620352b7&spx=%2F&flow=xtls-rprx-vision' \
  --data-urlencode 'target=https://google.com'

A Prometheus-style metrics page describing that single probe comes back. Refer for Reading a probe response below.

The endpoints:

Endpoint Purpose
/probe?ctx=…&target=…&scope=… Probe one tunnel. scope is optional and defaults to default.
/metrics Exporter's own process, Go runtime and xray cache metrics.

With a configuration file

Custom scopes, timeouts, request bodies and fail_if validation rules live in a YAML file mounted into the container. Grab the annotated example and mount it:

curl -O https://raw.githubusercontent.com/quyxishi/whitebox/main/whitebox.yml

docker run --rm -d --name whitebox -p 9116:9116 \
  -v "$PWD/whitebox.yml:/etc/whitebox/whitebox.yml:ro" \
  rxyvea/whitebox:latest --config.file=/etc/whitebox/whitebox.yml

Select a scope per probe with &scope=<scope_name>. See Whitebox Configuration for what the file can express.

Tip

Config is reloaded in place on SIGHUP: docker kill -s HUP whitebox.

With Docker Compose

Drop this into a docker-compose.yml next to your whitebox.yml:

services:
  whitebox:
    image: rxyvea/whitebox:latest
    container_name: whitebox
    restart: unless-stopped
    ports:
      - ${WHITEBOX_PORT:-9116}:9116
    command:
      - '--config.file=/etc/whitebox/whitebox.yml'
      - '--log.level=info'
    volumes:
      - ./whitebox.yml:/etc/whitebox/whitebox.yml

The whole stack

examples/ holds a working whitebox + blackbox + Prometheus + Grafana deployment, already wired together.

git clone -b main https://github.com/quyxishi/whitebox
cd whitebox/examples
docker compose up -d

Grafana lands on http://localhost:3000 (admin/admin by default), Prometheus on http://localhost:9090 — check its /targets page for the whitebox job. Point whitebox-sd-config.yml at your own connection URIs to get real data, then import hemera-dashboard.json into Grafana to visualise it.

Building from source

Requires Go 1.26+, or just Docker for the image build:

git clone -b main https://github.com/quyxishi/whitebox
cd whitebox

docker build --tag whitebox .
docker run --rm -d -p 9116:9116 whitebox

See Development for the local (non-Docker) loop.

Logging

Verbosity is controlled by the --log.level flag (-l) or the WHITEBOX_LOG_LEVEL environment variable, one of debug, info (default), warn, error:

docker run --rm -d -p 9116:9116 -e WHITEBOX_LOG_LEVEL=warn rxyvea/whitebox:latest

On debug, per-request access logs and verbose xray-core tunnel logs are emitted as well; on any higher level both are suppressed.

Listen address

By default whitebox binds to :9116. Use the --web.listen-address flag or the WHITEBOX_LISTEN_ADDRESS environment variable to change the port (or the interface), accepting either [host]:port or a bare port:

sudo docker run --rm -d -p 9200:9200 -e WHITEBOX_LISTEN_ADDRESS=:9200 whitebox

With docker-compose.yaml the published host port can be overridden through the WHITEBOX_PORT variable, e.g. WHITEBOX_PORT=9200 sudo docker compose up -d.

Reading a probe response

Every probe returns the outcome as Prometheus metrics:

# HELP tun_probe_duration_seconds Returns how long the probe took to complete in seconds
# TYPE tun_probe_duration_seconds gauge
tun_probe_duration_seconds 1.554994
# HELP tun_probe_http_content_length_bytes Length of HTTP content response in bytes
# TYPE tun_probe_http_content_length_bytes gauge
tun_probe_http_content_length_bytes -1
# HELP tun_probe_http_duration_seconds Duration of HTTP request by phase, summed over all traces
# TYPE tun_probe_http_duration_seconds gauge
tun_probe_http_duration_seconds{phase="connect"} 0.1998826
tun_probe_http_duration_seconds{phase="processing"} 0.3262475
tun_probe_http_duration_seconds{phase="resolve"} 0
tun_probe_http_duration_seconds{phase="tls"} 1.2223279
tun_probe_http_duration_seconds{phase="transfer"} 0.0064185
# HELP tun_probe_http_redirects The number of redirects
# TYPE tun_probe_http_redirects gauge
tun_probe_http_redirects 1
# HELP tun_probe_http_ssl Indicates if SSL was used for the final trace
# TYPE tun_probe_http_ssl gauge
tun_probe_http_ssl 1
# HELP tun_probe_http_status_code Response HTTP status code
# TYPE tun_probe_http_status_code gauge
tun_probe_http_status_code 200
# HELP tun_probe_http_uncompressed_body_length_bytes Length of uncompressed response body in bytes
# TYPE tun_probe_http_uncompressed_body_length_bytes gauge
tun_probe_http_uncompressed_body_length_bytes 17650
# HELP tun_probe_instance_cached Indicates if the probe reused an already-built xray instance
# TYPE tun_probe_instance_cached gauge
tun_probe_instance_cached 1
# HELP tun_probe_success Displays whether or not the probe over tunnel was a success
# TYPE tun_probe_success gauge
tun_probe_success 1

Exporter metrics

/probe describes a single tunnel. The exporter process itself is exposed separately:

http://localhost:9116/metrics

This serves the Go runtime and process collectors (go_memstats_heap_inuse_bytes, go_goroutines, ...) alongside xray instance cache statistics:

Metric Description
whitebox_xray_instances Instances currently held by the cache
whitebox_xray_instances_in_use Instances currently leased by an in-flight probe
whitebox_xray_cache_hits_total / _misses_total Probes that reused vs. built an instance
whitebox_xray_cache_evictions_total{reason} Evictions by ttl, lru, reload or shutdown
whitebox_xray_cache_overflow_total Times max_entries was exceeded because every instance was in use
whitebox_xray_instance_build_duration_seconds Cost of constructing and starting an instance
whitebox_xray_instance_build_failures_total Failures to construct or start an instance

In steady state whitebox_xray_instances should settle at the number of distinct connection URIs scraped, and whitebox_xray_cache_misses_total should stop increasing.

Whitebox Configuration

Refer to the annotated example configuration and code reference for implementation details.

Xray instance reuse

Whitebox reuses started xray instances across probes, keyed by the generated xray config, rather than building one per request.

This is required for bounded memory, not merely an optimisation: xray-core's xhttp, grpc and hysteria dialers cache per-connection state in package-level maps keyed by the *internet.MemoryStreamConfig pointer that core.New allocates, and never delete from them. One instance per probe therefore leaks one permanent entry per probe, which is issue #10.

Consequences worth knowing:

  • For xhttp, whitebox injects xmux.hMaxRequestTimes: 1 into generated configs so each probe still establishes a fresh transport connection to the VPN server. An explicit xmux in the connection URI is always left untouched.
  • For grpc, hysteria2 and wireguard there is no equivalent knob, so consecutive probes may share the underlying connection. A dead server still fails the probe; what is not re-exercised every probe is the handshake itself. Set instance_cache.enabled: false if per-probe handshake coverage on those transports matters more than flat memory.
  • raw/tcp, ws, httpupgrade and kcp are unaffected — they have no dialer-level cache and reconnect per probe regardless.

Use tun_probe_instance_cached to tell cold probes from warm ones when reading tun_probe_http_duration_seconds.

Prometheus Configuration

Whitebox follows the multi-target exporter pattern.

Example Prometheus configuration:
scrape_configs:
  - job_name: 'whitebox'
    metrics_path: /probe
    file_sd_configs:
      - files: [ '/etc/prometheus/whitebox-sd-config.yml' ]  # File service discovery configurations (targets).
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target  # 'target' -> '?target=...'.
      - source_labels: [ctx]
        target_label: __param_ctx     # 'ctx' -> '?ctx=...'.
      - source_labels: [client]
        target_label: client          # Label all probe's with client id.
      - source_labels: [protocol]
        target_label: protocol        # Label all probe's with used protocol.
      - source_labels: [__param_target]
        target_label: target          # Label all probe's with target.
      - target_label: __address__
        replacement: 127.0.0.1:9116   # The whitebox real hostname:port.
Example file service discovery configuration (targets):
- targets: [ "https://google.com" ]
  labels:
    ctx: "vless://c9f5228c-8870-47bd-a92f-9b38c7c02b08@1.2.3.4:443?type=tcp&encryption=none&security=reality&pbk=DF-3KL2W4RuNB2HgsEDmLqHLvvTTN4_QfwUCUn8Uhy0&fp=firefox&sni=ce-cdn.icloud-content.com&sid=dc8wq0b47450f9&spx=%2F&flow=xtls-rprx-vision#ring0-raii-idx0"
    client: "ring0-raii-idx0"  # Client unique identifier
    protocol: "vless"          # VPN protocol
    # You can also add additional labels here:
    #   sni: "web.max.ru"
    # And then, update relabel_configs in prometheus.yml job's config:
    #   - source_labels: [sni]
    #     target_label: sni
- targets: [ "https://cloudflare.com" ]
  labels:
    # Wireguard connection must be supplied as: base64-encoded peer .ini config prefixed with 'wireguard://'
    ctx: "wireguard://W0ludGVyZmFjZV0KUHJpdmF0ZUtleSA9IFNObk5ON0l4YzV0ekNYS2FJNGZXNnEyOFYzbnhGS2YxcmNoYWt4bWdBbHM9CkFkZHJlc3MgPSAxMC4wLjAuMi8zMgpETlMgPSAxLjEuMS4xLCAxLjAuMC4xCk1UVSA9IDE0MjAKCiMgLTEKW1BlZXJdClB1YmxpY0tleSA9IHk2MTdkQ2dNM1g2bEtEanBkdDVhQ3dIWmROWW5OT0FwMFMyanFUbGpmZzA9CkFsbG93ZWRJUHMgPSAwLjAuMC4wLzAsIDo6LzAKRW5kcG9pbnQgPSAxLjIuMy40OjQ0Mw=="
    client: "wg-raii-idx0"
    protocol: "wireguard"
- targets: [ "https://google.com" ]
  labels:
    # AmneziaWG connection with obfuscation parameters (Jc, Jmin, Jmax, S1, S2, H1-H4)
    # Must be supplied as: base64-encoded peer .ini config prefixed with 'awg://'
    ctx: "awg://W0ludGVyZmFjZV0KUHJpdmF0ZUtleSA9IFNObk5ON0l4YzN0emxYS2FJNGY4NnEyOFYzbnhGS2YzcmNoYWt4bWdCbHM9CkFkZHJlc3MgPSAxMC4wLjAuMi8zMgpETlMgPSAxLjEuMS4xLCAxLjAuMC4xCk1UVSA9IDE0MjAKSmMgPSAzCkptaW4gPSA1MApKbWF4ID0gMTAwMApTMSA9IDIwClMyID0gNzgKSDEgPSAzOTEzMTI3OApIMiA9IDgzMjEzODE4NQpIMyA9IDE0MzY5NTc4NTcKSDQgPSAxNjM1ODc3NzQ2CgpbUGVlcl0KUHVibGljS2V5ID0geTYxN2RDZ00zWDZsS0RqcGR0NWFHY0FaZE5Zbk5PQXAwUzNqYVRsamZnMD0KQWxsb3dlZElQcyA9IDAuMC4wLjAvMCwgOjovMApFbmRwb2ludCA9IDEuMi4zLjQ6Mjc3ODkK"
    client: "awg-raii-idx0"
    protocol: "amneziawg"

Tip

After all of that, reload Prometheus, visit http://localhost:9090/targets and check for your whitebox job.

See in Action

Example Grafana dashboard 'jarvis' powered by whitebox and blackbox

jarvis-dashboard

Prometheus-less Approach

While whitebox is designed with Prometheus integration in mind, it can also be used independently as a lightweight VPN probing tool.

You can configure periodic probes using a simple curl command in a cron job or any task scheduler of your choice.

Example Curl probe cron job
*/5 * * * * curl -s "http://localhost:9116/probe?ctx=<urlencoded_vpn_uri>&target=google.com" >> /var/log/whitebox-probe.log 2>&1

Roadmap

  • VLESS XHTTP transport protocol support (#1).
  • CI/CD for basic build/test workflow (#2).
  • JSON Subscriptions VPN-url's support (#3).
  • HTTP Roundtrip tracing w/ duration metrics (#4).
  • AmneziaWG protocol support, thanks to @nsvk13 (#6).
  • Whitebox YAML configuration w/ auto-reload by SIGHUP (#7).
  • Response status/body validation.
  • Custom HTTP-headers qualify support.
  • Configuration environment variables interpolation support (#8).
  • Hysteria2 + XHTTP extra settings support.
  • Authorization/OAuth 2.0 support.
  • Configuration for TLS protocol of HTTP probe support.
  • More advanced metrics.

Development

Build
make build
Running
make run
Live-reload
make watch
Tests
make test
make test-race
Profiling

Set WHITEBOX_EXPOSE_PPROF=1 to mount net/http/pprof under /debug/pprof:

WHITEBOX_EXPOSE_PPROF=1 make run
go tool pprof http://localhost:9116/debug/pprof/heap

License

MIT License, see LICENSE.

About

whitebox is a Prometheus exporter that provides availability monitoring of external VPN services powered by VMESS, VLESS, TROJAN, WireGuard, AmneziaWG, Shadowsocks and Hysteria2.

Topics

Resources

Stars

109 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages