MoaV Monitoring Stack¶
Real-time observability for your MoaV deployment with Grafana dashboards.
A tour of the ten dashboards — 22s, 668 KB.
Overview¶
The monitoring profile provides:
- Prometheus - Time-series database for metrics storage (15-day retention)
- Grafana - Beautiful dashboards for visualization
- Node Exporter - System metrics (CPU, RAM, disk, network)
- cAdvisor - Container metrics per service
- Clash Exporter - sing-box proxy metrics via Clash API
- sing-box Exporter - Per-user connections, protocol breakdown, GeoIP country stats
- Xray Exporter - Per-user connections and traffic (upload/download), GeoIP country stats
- WireGuard Exporter - VPN peer and traffic metrics, GeoIP country labels
- AmneziaWG Exporter - Per-peer traffic metrics, GeoIP country labels
- Telemt Exporter - MTProxy pool health, DC availability, upstream quality
- Snowflake Exporter - Tor donation metrics (people served, bandwidth donated)
- GeoIP Database - DB-IP Lite country database for offline IP-to-country lookups
Quick Start¶
# Start with monitoring profile
moav start monitoring proxy admin
# Or add to existing deployment
moav start monitoring
Access¶
Grafana is on port 9444 over HTTPS: https://your-domain:9444 (or the server IP). Log in as admin with your ADMIN_PASSWORD — the same one as the admin dashboard.
For a faster, CDN-fronted route see Cloudflare CDN for Grafana below.
Pre-built dashboards¶
Ten dashboards are provisioned automatically — nothing to import or build. Grafana groups them under the MoaV folder.
| Dashboard | What it shows |
|---|---|
| System | CPU, memory, disk, network throughput for the host |
| Containers | Per-container CPU, memory, restarts (via cAdvisor) |
| sing-box | Per-user connections and traffic for Reality, Trojan, AnyTLS, Hysteria2, Shadowsocks, CDN |
| WireGuard | Peer handshakes, transfer per peer, last-seen |
| AmneziaWG | The same, for the obfuscated interface |
| Xray (XHTTP) | XHTTP / XDNS connections and throughput |
| Telegram MTProxy | telemt connections, pool health, config-change activity |
| DNS Tunnels & GooseRelay | Traffic across dnstt, Slipstream, MasterDNS, XDNS and GooseRelay. dnstt has no protocol-level metrics API, so its panels show container and network telemetry only |
| Conduit | Bandwidth donated to Psiphon users, lifetime totals |
| Snowflake | Bandwidth donated to Tor users, client connections |
Per-user series come from the Clash API via clash-exporter; WireGuard and AmneziaWG read interface state published by their containers, so the exporters never need the Docker socket.
Conduit lifetime bandwidth¶
Conduit's live bandwidth gauges reset every time the container restarts. The Lifetime Download / Lifetime Upload panels work around that by adding a persistent offset back — so your cumulative donation totals keep growing across restarts.
This is automatic once monitoring and Conduit are both running. A systemd watcher banks the pre-restart total and reloads Prometheus the moment Conduit comes back up. Manage it with moav conduit-offsets; set CONDUIT_OFFSETS_AUTOUPDATE=false in .env to opt out. Hosts without systemd can run scripts/update-conduit-offsets.sh from cron.
GeoIP Country Distribution¶
All four protocol dashboards (sing-box, Xray, WireGuard, AmneziaWG) include a "Geographic Distribution" row showing user connections by country.
How it works¶
- A shared DB-IP Lite Country database (~5MB) provides offline IP-to-country lookups — no external API calls at runtime
- sing-box: polls the Clash API (
/connections) for source IPs of active connections - Xray: extracts source IPs from Xray access logs
- WireGuard / AmneziaWG: reads endpoint IPs from
wg show/awg show - Country codes are ISO 3166-1 alpha-2 (e.g.,
IR,DE,US)
Setup¶
The GeoIP database must be downloaded once before country metrics appear:
# Download the GeoIP database (run once, or monthly to refresh)
docker compose --profile setup run --rm geoip-updater
The database is stored in a Docker volume (moav_geoip) and shared read-only with all exporters.
Refreshing the database¶
DB-IP Lite is updated monthly. To refresh:
docker compose --profile setup run --rm geoip-updater
docker compose restart singbox-exporter xray-exporter wireguard-exporter amneziawg-exporter
Using MaxMind GeoLite2 instead¶
The database format is MMDB — compatible with both DB-IP and MaxMind. To use MaxMind GeoLite2-Country instead:
# Download GeoLite2-Country.mmdb from maxmind.com (requires free account)
# Copy into the volume:
docker run --rm -v moav_geoip:/geoip -v /path/to/GeoLite2-Country.mmdb:/src/db.mmdb alpine \
cp /src/db.mmdb /geoip/dbip-country-lite.mmdb
Graceful degradation¶
If the GeoIP database is not downloaded, all country lookups return "XX" (unknown) and existing metrics continue to work normally. The exporters log a warning on startup:
Configuration¶
Port Configuration¶
Cloudflare CDN for Faster Grafana (Recommended)¶
Grafana can be slow to load over high-latency connections due to large JS/CSS assets. You can use Cloudflare's CDN to cache static assets for much faster loading.
Step 1: Add DNS Record
In Cloudflare Dashboard, add:
| Type | Name | Content | Proxy |
|---|---|---|---|
| A | grafana | YOUR_SERVER_IP | Proxied (orange cloud) |
Step 2: Configure Environment
Add to your .env file:
Step 3: Restart Services
Step 4: Access Grafana
Access via https://grafana.yourdomain.xyz:2083 instead of :9444.
Note: Port 2083 is used because Cloudflare only proxies specific HTTPS ports (443, 2053, 2083, 2087, 2096, 8443). The
grafana-proxyservice handles SSL termination and caching headers.
Benefits:
- Static assets (JS, CSS, images) cached at Cloudflare edge
- Gzip compression
- Faster global access
- WebSocket support for live dashboard updates
Retention¶
Prometheus retains data for 15 days by default. To change this, modify the --storage.tsdb.retention.time flag in docker-compose.yml:
Enabling/Disabling¶
Resource Usage¶
Warning: The monitoring stack nearly doubles MoaV's resource requirements. While MoaV alone runs on 1 vCPU / 1GB RAM, adding monitoring requires at least 2 vCPU / 2GB RAM for stable operation.
Approximate additional resources when monitoring is enabled:
| Component | CPU | RAM | Disk |
|---|---|---|---|
| Prometheus | 0.1-0.3 cores | 200-500 MB | ~50 MB/day |
| Grafana | 0.1-0.2 cores | 100-200 MB | ~50 MB |
| Node Exporter | <0.1 cores | ~20 MB | - |
| cAdvisor | 0.1-0.3 cores | 50-150 MB | - |
| Clash Exporter | <0.1 cores | ~30 MB | - |
| WireGuard Exporter | <0.1 cores | ~10 MB | - |
| Snowflake Exporter | <0.1 cores | ~10 MB | - |
| Total | ~0.5-1 cores | ~400-900 MB | ~1 GB/15 days |
Minimum Requirements¶
| Configuration | vCPU | RAM | Notes |
|---|---|---|---|
| MoaV only | 1 | 1 GB | Bare minimum |
| MoaV + Monitoring | 2 | 2 GB | Recommended (for stats & monitoring) |
| MoaV + Monitoring + All Profiles | 2 | 4 GB | Production recommended |
If you have only 1GB RAM, do not enable monitoring - it could cause hangs and crashes.
Security¶
- Prometheus is internal only (no external port exposed)
- Grafana requires authentication via
ADMIN_PASSWORD - All exporters expose metrics only to the internal Docker network
What's Not Included¶
The following services do not currently expose metrics:
| Service | Reason |
|---|---|
| TrustTunnel | No metrics API available |
| dnstt | No metrics API available |
Container-level metrics (CPU, memory, network) are still available for these services via cAdvisor.
Troubleshooting¶
Run moav doctor services to quickly check if monitoring services are running. For detailed issues, see TROUBLESHOOTING.md.
Common issues covered:
- System hangs after starting monitoring (RAM issues)
- Grafana shows "No Data"
- High memory usage from cAdvisor
- Snowflake metrics showing zeros
- WireGuard exporter not starting
Blocked Registries (Iran, Russia, etc.)¶
If you're in a region where container registries are blocked (gcr.io, ghcr.io, Docker Hub), you can build images locally:
# Build commonly blocked images (cAdvisor from gcr.io, clash-exporter from ghcr.io)
moav build --local
# Build a specific image
moav build --local prometheus
# Build EVERYTHING locally (all services + all external images, no registry pulls)
moav build --local all
Available images for local build:
cadvisor- Container metrics (gcr.io) ← commonly blockedclash-exporter- Clash API exporter (ghcr.io) ← commonly blockedprometheus- Time-series database (Docker Hub)grafana- Visualization dashboards (Docker Hub)node-exporter- System metrics (Docker Hub)nginx- Web server (Docker Hub)certbot- Let's Encrypt client (Docker Hub)
The build command automatically updates your .env to use the local images.
Alternatively, configure mirror registries manually in .env:
IMAGE_CADVISOR=mirror.example.com/cadvisor/cadvisor:latest
IMAGE_PROMETHEUS=mirror.example.com/prom/prometheus:latest
CLI Commands¶
# Start monitoring only
moav start monitoring
# Start with other profiles
moav start monitoring proxy admin
# Build images locally (for blocked registries)
moav build --local
# View monitoring logs
moav logs prometheus
moav logs grafana
# Stop monitoring
moav stop prometheus grafana node-exporter cadvisor clash-exporter
Customization¶
Adding Custom Dashboards¶
Place JSON dashboard files in:
Grafana automatically loads new dashboards within 30 seconds.
Custom Prometheus Scrape Targets¶
Edit configs/monitoring/prometheus.yml to add new targets:
Then reload Prometheus:
Architecture¶
┌─────────────────┐
│ Cloudflare CDN │ (optional, for faster loading)
└────────┬────────┘
│
▼
┌─────────────┐ ┌─────────────┐
│grafana-proxy│ │ Grafana │
│ :2083 (CDN) │ │ :9444 direct│
└──────┬──────┘ └──────┬──────┘
│ │
└───────┬────────┘
│
┌──────▼──────┐
│ Prometheus │ :9091 (internal)
│ (time-series│
│ storage) │
└──────┬──────┘
┌─────────┬──┴─────┬─────────┬─────────┐
│ │ │ │ │
┌──▼────┐ ┌──▼───┐ ┌──▼────┐ ┌──▼────┐ ┌──▼──────┐
│ node │ │cAdvsr│ │ clash │ │ wg │ │snowflke │
│export │ │(cntr)│ │(sngbx)│ │(vpn) │ │ (tor) │
└───────┘ └──────┘ └───────┘ └───────┘ └─────────┘