fix: multiply node run db issue |
||
|---|---|---|
| .github | ||
| .run | ||
| cmd | ||
| deploy/helm | ||
| docs | ||
| frontend | ||
| internal | ||
| scripts | ||
| .DS_Store | ||
| .dockerignore | ||
| .gitignore | ||
| Dockerfile | ||
| LICENSE.txt | ||
| Makefile | ||
| README.md | ||
| SECURITY.md | ||
| config.yml.sample | ||
| ct.yaml | ||
| docker-compose.yml | ||
| go.mod | ||
| go.sum | ||
| mkdocs.yml | ||
| package-lock.json | ||
| release.config.js | ||
README.md
wg-portal-2
Recent Enhancements
This fork includes several important improvements:
🚀 Cluster Mode Support
Run multiple instances with a shared database. Synchronization is handled by the fanout module for seamless peer and interface updates across all nodes.
🔧 Configuration Enhancements
New core configuration options for fine-grained control over DNS, routing, and peer lifecycle management.
Introduction
WireGuard Portal is a simple, web-based configuration portal for WireGuard server management. The portal uses the WireGuard wgctrl library to manage existing VPN interfaces. This allows for the seamless activation or deactivation of new users without disturbing existing VPN connections.
The configuration portal supports using a database (SQLite, MySQL, MsSQL, or Postgres), OAuth or LDAP (Active Directory or OpenLDAP) as a user source for authentication and profile data.
Features
- Self-hosted - the whole application is a single binary
- Responsive multi-language web UI written in Vue.js
- Automatically selects IP from the network pool assigned to the client
- QR-Code for convenient mobile client configuration
- Sends email to the client with QR-code and client config
- Enable / Disable clients seamlessly
- Generation of wg-quick configuration file (
wgX.conf) if required - User authentication (database, OAuth, or LDAP), Passkey support
- IPv6 ready
- Docker ready
- Can be used with existing WireGuard setups
- Support for multiple WireGuard interfaces
- Supports multiple WireGuard backends (wgctrl or MikroTik [BETA])
- Peer Expiry Feature
- Handles route and DNS settings like wg-quick does
- Exposes Prometheus metrics for monitoring and alerting
- REST API for management and client deployment
- Webhook for custom actions on peer, interface, or user updates
Configuration Guide
Core Options
The latest version introduces several new core configuration options for production deployments:
| Option | Type | Default | Description |
|---|---|---|---|
admin_user |
string | - | Administrator username/email for initial setup |
admin_password |
string | - | Administrator password (hashed in database) |
admin_api_token |
string | - | API token for admin operations and cluster auth |
restore_state |
boolean | false |
Restore WireGuard interface state from database on startup |
import_existing |
boolean | false |
Import existing WireGuard configuration |
manage_dns |
boolean | true |
Enable/disable automatic DNS management (resolv.conf). Set to false if managing DNS externally. |
ignore_main_default_route |
boolean | false |
When true, the main route table won't be modified. Useful in container or complex networking setups. |
delete_expired_peers |
boolean | false |
Automatically remove peers after their TTL expires. |
default_user_ttl |
string/integer | 0 |
Default Time-To-Live for peers (e.g., 30d, 1 for 1 day). 0 means no expiration. |
sync_on_startup |
boolean | false |
Synchronize interface state with database on startup |
force_client_ip_as_allowed_ip |
boolean | false |
Use client IP as allowed IP instead of calculating from pool |
master |
boolean | false |
Mark this instance as master (for failover scenarios) |
Cluster Mode with Fanout Module
The Fanout module enables synchronization across multiple WireGuard Portal instances sharing a single database. This is essential for high-availability deployments.
How It Works
- Each instance publishes events (peer updates, deletions, interface changes) to other instances
- All instances listen for updates and apply changes locally
- Debouncing prevents thundering herd problems
- Automatic peer kicking on startup ensures consistency
Fanout Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
Enable cluster synchronization |
self_url |
string | - | This instance's URL (supports {{ external_url }} template) |
peers |
array | - | List of peer node URLs for cluster communication |
auth_header |
string | Authorization |
HTTP header name for authentication |
auth_value |
string | - | Auth value (e.g., Basic base64{user:token}) |
timeout |
duration | 2s |
Request timeout for peer communication |
debounce |
duration | 300ms |
Time to wait before batching events |
origin |
string | wg-portal |
Optional identifier for event origin |
kick_on_start |
boolean | true |
Remove stale entries on startup |
tls_skip_verify |
boolean | false |
Skip TLS verification for peer URLs (NOT for production!) |
topics |
array | all | Event topics to synchronize |
Configuration Example
core:
admin_user: username@example.com
admin_password: ...
restore_state: false
import_existing: false
admin_api_token: ...
manage_dns: false
ignore_main_default_route: true
delete_expired_peers: true
default_user_ttl: 1d # 1 days default expiration
sync_on_startup: true
force_client_ip_as_allowed_ip: true
master: true
fanout:
enabled: true
self_url: "https://wg-portal-1.example.com" # Use template variable: {{ external_url }}
# List of other cluster nodes
peers:
- "https://wg-portal-2.example.com"
- "https://wg-portal-3.example.com"
# Communication settings
auth_header: "Authorization"
auth_value: "Basic base64{admin_user:admin_api_token}"
timeout: 2s
debounce: 300ms # Wait 300ms before processing event batch
origin: "wg-portal" # Optional origin identifier
kick_on_start: true # Remove stale peer entries on startup
# Event topics to synchronize (all shown by default)
topics:
- "peers.updated" # When peer list is updated
- "peer.save" # Individual peer saved
- "peer.delete" # Peer deleted
- "interface.save" # Interface configuration changed
- "interface.updated"# Interface state changed
tls_skip_verify: true
advanced:
config_storage_path: /etc/wireguard/
rule_prio_offset: 32000
log_level: info
log_pretty: true
log_json: false
expiry_check_interval: 5h
web:
listening_address: :8888
external_url: https://wg-portal-1.example.com:4848
request_logging: true
csrf_secret: ...
session_secret: ...
statistics:
use_ping_checks: false
ping_check_workers: 3
ping_unprivileged: false
ping_check_interval: 5m
data_collection_interval: 60s
collect_interface_data: true
collect_peer_data: true
collect_audit_data: true
store_audit_data: false
listening_address: ":8787"
export_detailed_peer_metrics: false
only_export_connected_peers: true
Deployment Architecture
┌───────────────────────────┐
│ Shared Database │
│ MySQL/PostgreSQL │
└─────────────┬─────────────┘
│
┌─────────────────┼──────────────────┐
│ │ │
┌───▼────┐ ┌───▼────┐ ┌────▼───┐
│ Node 1 │◄──────►│ Node 2 │◄──────►│ Node 3 │
│ Portal │ │ Portal │ │ Portal │
└───┬────┘ └───┬────┘ └────┬───┘
│ │ │
[wg0] [wg0] [wg0]
│ │ │
┌───┴─────────────────┼──────────────────┴───┐
│ Network / Load Balancer (HAProxy/nginx) │
└────────────────────────────────────────────┘
DNS Management
When manage_dns: false, the portal won't modify /etc/resolv.conf. This is useful when:
- Running in containers with custom DNS configuration
- Using external DNS management tools
- DNS is configured via cloud orchestration (Kubernetes, etc.)
Route Management
Setting ignore_main_default_route: true prevents modification of the main route table. This is necessary for:
- Complex multi-interface hosts
- Containers with restricted capabilities
- Environments where routing is managed externally
Peer Expiration
Configure automatic peer cleanup:
core:
delete_expired_peers: true
default_user_ttl: 30d # 30 days expiration
Individual peers can also have custom TTL values. After expiration:
- Peer status changes to "expired"
- Peer is automatically removed if
delete_expired_peers: true - WireGuard configuration is updated automatically
Advanced Options
Fine-tune WireGuard configuration and logging:
| Option | Type | Default | Description |
|---|---|---|---|
config_storage_path |
string | /etc/wireguard/ |
Directory where WireGuard config files are stored |
rule_prio_offset |
integer | 32000 |
Base priority offset for iptables rules |
log_level |
string | info |
Logging level: debug, info, warn, error |
log_pretty |
boolean | false |
Pretty-print logs (human-readable format) |
log_json |
boolean | false |
Output logs as JSON for log aggregation |
expiry_check_interval |
duration | 5h |
How often to check for expired peers |
Web Interface Options
Configure the web portal:
| Option | Type | Default | Description |
|---|---|---|---|
listening_address |
string | :8080 |
HTTP listening address |
external_url |
string | - | External URL for QR codes and API responses |
request_logging |
boolean | true |
Log HTTP requests |
csrf_secret |
string | - | Secret key for CSRF protection (auto-generated if not set) |
session_secret |
string | - | Secret key for session encryption (auto-generated if not set) |
Statistics & Monitoring Options
Configure metrics collection and reporting:
| Option | Type | Default | Description |
|---|---|---|---|
use_ping_checks |
boolean | false |
Enable ping-based peer availability checks |
ping_check_workers |
integer | 3 |
Number of concurrent ping workers |
ping_unprivileged |
boolean | false |
Use unprivileged ping (requires ICMP capabilities) |
ping_check_interval |
duration | 5m |
Interval between ping checks |
data_collection_interval |
duration | 60s |
How often to collect statistics |
collect_interface_data |
boolean | true |
Collect interface metrics (traffic, handshakes) |
collect_peer_data |
boolean | true |
Collect peer-level metrics |
collect_audit_data |
boolean | false |
Collect audit trail data |
store_audit_data |
boolean | false |
Persist audit data to database |
listening_address |
string | :8787 |
Prometheus metrics listening address |
export_detailed_peer_metrics |
boolean | false |
Export per-peer metrics (can be expensive) |
only_export_connected_peers |
boolean | true |
Only export metrics for connected peers |
Documentation
For the complete documentation visit wgportal.org.
What is out of scope
- Automatic generation or application of any
iptablesornftablesrules. - Support for operating systems other than linux.
- Automatic import of private keys of an existing WireGuard setup.
Quick Start with Cluster Mode
Prerequisites
- MySQL 5.7+ or PostgreSQL 10+ (shared database)
- Linux kernel with WireGuard support
- 3+ instances for HA setup (optional, 1 instance works fine for single-node)
Docker Cluster Deployment
# Create shared network
docker network create wg-portal
# Start database
docker run -d \
--name mysql-wg \
--network wg-portal \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8.0
# Wait for DB to be ready
sleep 10
# Start first portal node
docker run -d \
--name wg-portal-1 \
--network wg-portal \
--cap-add=NET_ADMIN \
-e PORTAL_LISTEN_ADDRESS=0.0.0.0:8080 \
-e PORTAL_EXTERNAL_URL=https://wg-portal-1.example.com \
wgportal/wg-portal
# Start second portal node
docker run -d \
--name wg-portal-2 \
--network wg-portal \
--cap-add=NET_ADMIN \
-e PORTAL_LISTEN_ADDRESS=0.0.0.0:8080 \
-e PORTAL_EXTERNAL_URL=https://wg-portal-2.example.com \
wgportal/wg-portal
Verify Cluster Communication
# Check logs for fanout initialization
docker logs wg-portal-1 | grep -i fanout
# Test peer synchronization
# Create/modify peer on node 1, should appear on node 2 within debounce time
Testing & Validation
The cluster mode has been tested with:
- ✅ MySQL 5.7, 8.0+
- ✅ PostgreSQL 12+
- ✅ Up to 5 concurrent nodes
- ✅ Load balancer (HAProxy, nginx)
- ✅ Automatic failover scenarios
Application stack
- wgctrl-go and netlink for interface handling
- Bootstrap, for the HTML templates
- Vue.js, for the frontend
License
- MIT License. MIT or https://opensource.org/licenses/MIT
[!IMPORTANT] Since the project was accepted by the Docker-Sponsored Open Source Program, the Docker image location has moved to wgportal/wg-portal. Please update the Docker image from fedor-git/wg-portal-2 to wgportal/wg-portal.
