ocserv-exporter

Prometheus exporter for OpenConnect VPN Server (ocserv). Collects metrics from systemd journal logs and occtl status.
Features
- Real-time log parsing from journald
- Multi-server support (e.g.,
ocserv, ocserv-ru)
- Per-user session tracking
- Traffic statistics (rx/tx bytes)
- Reconnect detection (login within 5 min of disconnect)
- Problematic session tracking (< 60s with error)
- Failed authentication attempts
- GeoIP support (optional)
- occtl integration (optional) - real-time server stats, VPN client types
- Ready-to-use Grafana dashboard
Metrics
| Metric |
Type |
Labels |
Description |
ocserv_active_sessions |
Gauge |
server, username |
Current active VPN sessions |
ocserv_connections_total |
Counter |
server, username, client_ip |
Total connections |
ocserv_disconnections_total |
Counter |
server, username, reason |
Total disconnections by reason |
ocserv_received_bytes_total |
Counter |
server, username |
Bytes received from clients |
ocserv_sent_bytes_total |
Counter |
server, username |
Bytes sent to clients |
ocserv_session_duration_seconds |
Histogram |
server, username |
Session duration distribution |
ocserv_reconnects_total |
Counter |
server, username |
Rapid reconnections (< 5 min) |
ocserv_problematic_sessions_total |
Counter |
server, username, reason |
Short sessions with errors |
ocserv_session_info |
Gauge |
server, username, vpn_ip, country, client_type |
Active session details (value is start timestamp) |
ocserv_auth_failed_total |
Counter |
server, username, client_ip, country, country_code |
Failed authentication attempts |
ocserv_connections_by_country_total |
Counter |
server, username, country, country_code |
Connections by country (GeoIP) |
ocserv_last_event_timestamp_seconds |
Gauge |
- |
Last processed log event timestamp |
ocserv_exporter_info |
Gauge |
version |
Exporter information |
occtl metrics (optional)
| Metric |
Type |
Labels |
Description |
ocserv_server_rx_bytes |
Gauge |
server |
Bytes received by server (may reset on occtl reset stats) |
ocserv_server_tx_bytes |
Gauge |
server |
Bytes sent by server (may reset on occtl reset stats) |
ocserv_server_active_sessions |
Gauge |
server |
Active sessions from occtl |
ocserv_server_total_sessions |
Gauge |
server |
Total sessions since stats reset |
ocserv_server_latency_median_seconds |
Gauge |
server |
Median server latency |
ocserv_server_latency_stdev_seconds |
Gauge |
server |
Latency standard deviation |
ocserv_server_uptime_seconds |
Gauge |
server |
Server uptime |
ocserv_server_avg_session_time_seconds |
Gauge |
server |
Average session time |
ocserv_sessions_by_client_type |
Gauge |
server, client_type |
Sessions by VPN client type |
ocserv_user_concurrent_sessions |
Gauge |
server, username |
Current concurrent sessions per user |
ocserv_session_active_seconds_total |
Counter |
server, username, vpn_ip, country, client_type |
Cumulative seconds the session was observed active by occtl polling |
Breaking change: ocserv_server_rx_bytes_total and ocserv_server_tx_bytes_total were renamed to drop the _total suffix — they are gauges (the underlying occtl show status counter can drop on occtl reset stats or process restart), and Prometheus convention reserves _total for monotonic counters. Update any saved queries / dashboards that referenced the old names.
Installation
From dist package
# Copy dist/ folder to server
scp -r dist/* user@vpn-server:/tmp/ocserv-exporter/
# Run install script
ssh user@vpn-server "cd /tmp/ocserv-exporter && ./install.sh"
Manual installation
# Copy binary
sudo cp ocserv-exporter /usr/local/bin/
sudo chmod +x /usr/local/bin/ocserv-exporter
# Create service user
sudo useradd -r -s /sbin/nologin -G systemd-journal ocserv-exporter
# Copy systemd service
sudo cp ocserv-exporter.service /etc/systemd/system/
# Start service
sudo systemctl daemon-reload
sudo systemctl enable --now ocserv-exporter
Configuration
Command-line flags
--web.listen-address=":9617" HTTP endpoint (default: :9617)
--web.telemetry-path="/metrics" Metrics path (default: /metrics)
--journal.unit="ocserv" systemd unit to read (can be repeated)
--journal.since="24h" Initial lookback period (default: 24h)
--geoip.db="" Path to GeoLite2-Country.mmdb (optional)
--log.file="" Read from file instead of journald (for testing)
--occtl.enabled Enable occtl polling for real-time server stats
--occtl.socket="name:path" occtl socket (can be repeated, see below)
--occtl.interval="30s" Polling interval (default: 30s)
Systemd service
Edit /etc/systemd/system/ocserv-exporter.service:
ExecStart=/usr/local/bin/ocserv-exporter \
--web.listen-address=:9617 \
--journal.unit=ocserv \
--journal.unit=ocserv-ru \
--journal.since=24h
# --geoip.db=/etc/ocserv-exporter/GeoLite2-Country.mmdb
Multiple servers
If you have multiple ocserv instances, add SyslogIdentifier to each systemd service:
# /etc/systemd/system/ocserv-ru.service
[Service]
SyslogIdentifier=ocserv-ru
Then add both units to exporter:
--journal.unit=ocserv --journal.unit=ocserv-ru
Prometheus configuration
Add to prometheus.yml:
scrape_configs:
- job_name: 'ocserv'
static_configs:
- targets: ['vpn-server:9617']
Grafana dashboard
Import grafana/dashboard.json to Grafana.
Dashboard includes:
- Active sessions overview
- Connections/disconnections over time
- Traffic by user
- Disconnect reasons pie chart
- Reconnects & problematic sessions
- Top users by traffic
- Failed authentication attempts
- Connections by country (GeoIP)
GeoIP support
- Register at https://www.maxmind.com/en/geolite2/signup
- Download GeoLite2-Country.mmdb
- Place it at
/etc/ocserv-exporter/GeoLite2-Country.mmdb
- Uncomment
--geoip.db line in systemd service
- Restart:
sudo systemctl restart ocserv-exporter
occtl integration (optional)
The exporter can poll occtl for real-time server statistics that are not available in logs:
- Server RX/TX traffic - total bytes in real-time (no spikes on disconnect)
- Active sessions - accurate count from server
- Server latency - median and stdev
- VPN client types - AnyConnect, OpenConnect-GUI, mobile clients, etc.
Configuration
Socket format: name (for default socket) or name:path (for custom socket path).
ExecStart=/usr/local/bin/ocserv-exporter \
--journal.unit=ocserv \
--journal.unit=ocserv-ru \
--occtl.enabled \
--occtl.socket=ocserv \
--occtl.socket=ocserv-ru:/var/run/ocserv-ru.socket \
--occtl.interval=30s
Permissions setup
The exporter uses sudo to run occtl (socket access requires root). Configure passwordless sudo for the service user:
# Find occtl path
which occtl # typically /usr/local/bin/occtl
# Create sudoers file
sudo visudo -f /etc/sudoers.d/ocserv-exporter
Add this line (adjust path if needed):
ocserv-exporter ALL=(root) NOPASSWD: /usr/local/bin/occtl *
Verify it works:
sudo -u ocserv-exporter sudo -n occtl show status
Note on traffic metrics
Per-user traffic (ocserv_received_bytes_total, ocserv_sent_bytes_total) is only available at disconnect time - this is a limitation of ocserv logging, not the exporter. The occtl integration provides server-level traffic via ocserv_server_rx_bytes and ocserv_server_tx_bytes (gauges, updated each --occtl.interval).
Building
Requires Docker for cross-compilation (builds Linux amd64 binary):
./build.sh
Output in dist/ folder.
Verify installation
# Check service status
sudo systemctl status ocserv-exporter
# View logs
sudo journalctl -u ocserv-exporter -f
# Test metrics endpoint
curl localhost:9617/metrics | grep ocserv_
License
MIT