Detailed documentation for all features. For the complete feature overview, see README.md.
- Layer 4 TCP/UDP Proxy
- Route Authentication
- SMTP Configuration
- Caddy Config View
- Custom Branding for Route Auth
- IP Access Control / Geo-Blocking
- Uptime Monitoring
- Email Alert System
- Per-Peer Traffic Graphs
- Account Lockout
- Password Complexity Enforcement
- Configurable Operational Timeouts
- Peer Expiry
- Peer Access Control (ACL)
- Automatic Backups
- Log Export
- API Tokens
- Gzip/Zstd Compression
- Custom Request/Response Headers
- Per-Route Rate Limiting
- Retry with Backoff
- Multiple Backends / Load Balancing
- Sticky Sessions
- Prometheus Metrics Export
- Circuit Breaker
- Batch Operations
- Peer Groups
- Request Mirroring
- Mobile Sidebar
- Themes
Raw TCP and UDP port forwarding via the caddy-l4 plugin. Reach RDP, SSH, databases, or game servers without a VPN tunnel.
- When creating a route, select Route Type: Layer 4
- Choose protocol (TCP or UDP) and a listen port or port range
- Select a TLS mode:
- None — port-based routing only
- Passthrough — TLS-SNI routing, TLS negotiated with backend
- Terminate — Caddy handles TLS with automatic Let's Encrypt certificate
- GateControl configures Caddy's L4 module to forward traffic directly to the backend
| Setting | Options | Default |
|---|---|---|
| Protocol | TCP, UDP | TCP |
| Listen Port | Single port or range (e.g., 5000-5010) |
— |
| TLS Mode | None, Passthrough, Terminate | None |
| Domain | Required for Passthrough/Terminate | — |
| Max Port Range | GC_L4_MAX_PORT_RANGE env var |
100 |
| Blocked Ports | GC_L4_BLOCKED_PORTS env var |
80, 443, 2019, 3000, 51820 |
# Create L4 route (RDP forwarding)
curl -X POST https://gate.example.com/api/v1/routes \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"domain": "rdp.example.com",
"route_type": "l4",
"l4_protocol": "tcp",
"l4_listen_port": "3389",
"l4_tls_mode": "passthrough",
"target_ip": "192.168.1.10",
"target_port": 3389
}'
# Create L4 route with port range
curl -X POST https://gate.example.com/api/v1/routes \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"domain": "game.example.com",
"route_type": "l4",
"l4_protocol": "udp",
"l4_listen_port": "27015-27020",
"l4_tls_mode": "none",
"target_ip": "10.8.0.5",
"target_port": 27015
}'- Host networking required (
network_mode: hostin docker-compose.yml) - Blocked ports prevent conflicts with GateControl services (Caddy, Node.js, WireGuard)
- TLS termination generates Let's Encrypt certificates per domain
- Monitoring uses TCP connects for L4 routes (not HTTP)
- HTTP-specific features (compression, headers, rate limiting) do not apply to L4 routes
Custom login page per route with multiple auth methods, optional 2FA, and configurable session duration.
| Method | Description |
|---|---|
| Email & Password | Credentials stored as bcrypt hashes |
| Email & Code (OTP) | 6-digit code sent via SMTP, 5-minute expiry |
| TOTP | QR code setup, standard Authenticator apps (Google, Authy, etc.) |
- Enable route auth on any HTTP route
- Caddy's
forward_authmechanism intercepts requests and redirects to a login page - After successful login, a session cookie is set with the configured TTL
- Subsequent requests pass through without re-authentication until the session expires
- Optional 2FA adds a second authentication step (Email Code or TOTP)
| Setting | Options | Default |
|---|---|---|
| Auth Method | None, Email/Password, Email/Code, TOTP | None |
| 2FA | Off, Email Code, TOTP | Off |
| Session Duration | 1h, 24h, 7d, 30d, custom days | 24h |
# Create route auth (Email & Password)
curl -X POST https://gate.example.com/api/v1/route-auth \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"route_id": 1,
"auth_method": "password",
"session_duration_hours": 24,
"two_factor_enabled": false
}'
# Setup TOTP (returns QR code)
curl -X POST https://gate.example.com/api/v1/route-auth/totp-setup \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"route_id": 1}'
# Verify TOTP code
curl -X POST https://gate.example.com/api/v1/route-auth/totp-verify \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"route_id": 1, "token": "123456"}'
# Delete route auth
curl -X DELETE "https://gate.example.com/api/v1/route-auth?route_id=1" \
-H "Authorization: Bearer gc_your_token"- SMTP configuration required for Email & Code and email alerts
- TOTP codes tracked in-memory to prevent replay (90s expiry)
- Route-auth CSRF key derived from app secret via HMAC (domain-bound)
- Lockout is email-based (not IP-based) to prevent IP rotation bypass
- Static assets (CSS, JS) bypass forward auth on route-auth domains
Built-in SMTP settings for sending verification codes and alert emails.
- Navigate to Settings > Email & SMTP
- Enter SMTP host, port, credentials, and from address
- Port 587 auto-enables STARTTLS, port 465 uses implicit TLS
- Test with the Send Test Email button
- Password is encrypted at rest (AES-256-GCM)
# Get SMTP settings (password masked)
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/smtp
# Update SMTP settings
curl -X PUT https://gate.example.com/api/v1/smtp \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"smtp_host": "smtp.gmail.com",
"smtp_port": "587",
"smtp_user": "bot@example.com",
"smtp_password": "app_password",
"smtp_from": "noreply@example.com"
}'
# Test SMTP connection
curl -X POST https://gate.example.com/api/v1/smtp/test \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"recipient": "admin@example.com"}'- Minimum config: host, port, from address (credentials optional for some servers)
- Password not readable via API after saving
- Transporter cached in memory, reset on settings change
- All email template values HTML-escaped (XSS protection)
Live view of Caddy's JSON configuration with syntax highlighting and export capability.
- Navigate to the Caddy Config page
- Shows the current live Caddy configuration as pretty-printed JSON
- Config updates automatically after route create/update/delete
- Useful for debugging routing, ACL, headers, and compression settings
# Get live Caddy config
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/caddy/status
# Force Caddy reload
curl -X POST https://gate.example.com/api/v1/caddy/reload \
-H "Authorization: Bearer gc_your_token"- Read-only view — editing requires changing routes via UI or API
- Caddy admin API timeout configurable via
GC_CADDY_API_TIMEOUT(default 10s)
Per-route custom branding for the login page: logo, title, welcome text, accent color, and background image.
- Open a route's edit modal and navigate to the Branding section
- Upload a logo (PNG, JPG, GIF, SVG — max 2 MB)
- Set title, welcome text, accent color, and background image URL
- The login page renders with your custom branding via CSS custom properties
| Setting | Limit |
|---|---|
| Logo | 2 MB, image/* MIME types |
| Title | 255 characters |
| Welcome Text | 2000 characters |
| Accent Color | Hex color (e.g., #007bff) |
| Background Image | External URL |
# Upload logo
curl -X POST https://gate.example.com/api/v1/routes/1/branding/logo \
-H "Authorization: Bearer gc_your_token" \
-F "file=@logo.png"
# Delete logo
curl -X DELETE https://gate.example.com/api/v1/routes/1/branding/logo \
-H "Authorization: Bearer gc_your_token"
# Update branding text fields
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"branding_title": "Customer Portal",
"branding_text": "Welcome to our secure login",
"branding_color": "#007bff",
"branding_bg": "https://example.com/bg.jpg"
}'- Logo stored in
/data/branding/(persisted via Docker volume) - Hex color validated via regex (prevents CSS injection)
- All text fields HTML-escaped on render
- Branding included in backup/restore
Per-route IP filtering with whitelist/blacklist modes. Supports single IPs, CIDR ranges, and country codes via ip2location.io.
- Open a route's edit modal and enable IP Access Control
- Select mode: Whitelist (allow only listed) or Blacklist (block listed)
- Add rules by type:
- IP — exact match (e.g.,
203.0.113.50) - CIDR — range match (e.g.,
10.0.0.0/8) - Country — country code (e.g.,
CN,US,DE) via ip2location.io API
- IP — exact match (e.g.,
- Caddy's forward-auth checks each request against the rules
| Setting | Description |
|---|---|
| Mode | Whitelist or Blacklist |
| Rules | Array of {type, value} objects |
| ip2location API Key | Required for country rules (Settings > Advanced) |
# Enable IP whitelist with CIDR
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"ip_filter_enabled": true,
"ip_filter_mode": "whitelist",
"ip_filter_rules": [
{"type": "cidr", "value": "185.10.20.0/24"},
{"type": "ip", "value": "203.0.113.50"}
]
}'
# Enable country-based blacklist
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"ip_filter_enabled": true,
"ip_filter_mode": "blacklist",
"ip_filter_rules": [
{"type": "country", "value": "CN"},
{"type": "country", "value": "RU"}
]
}'- Country rules require an ip2location.io API key
- GeoIP cache: 24-hour TTL, max 10,000 entries (LRU eviction)
- Empty whitelist blocks all traffic; empty blacklist allows all
- Uses
req.ip(Express-resolved) — not raw X-Forwarded-For header - IPv6-mapped IPv4 addresses automatically stripped (
::ffff:prefix)
Periodic health checks (HTTP or TCP) per route with dashboard display, response time tracking, and email alerts on status change.
- Enable monitoring on any route
- A background poller checks each monitored route at the configured interval (default 60s)
- HTTP routes:
GETrequest, expects status 200–399 - L4/TCP routes: TCP connect test
- Status changes (up/down) trigger webhooks and email alerts
- Dashboard shows monitoring status with response time for each route
| Setting | Options | Default |
|---|---|---|
| Enable | Per-route toggle | Off |
| Interval | Settings > Monitoring | 60s |
| HTTP Timeout | GC_MONITOR_HTTP_TIMEOUT |
10s |
| TCP Timeout | GC_MONITOR_TCP_TIMEOUT |
5s |
| Email Alerts | Settings > Email | Off |
# Enable monitoring
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"monitoring_enabled": true}'
# Manual health check
curl -X POST https://gate.example.com/api/v1/routes/1/check \
-H "Authorization: Bearer gc_your_token"- First check runs 10 seconds after startup
- Checks target backend IP + port (not public domain)
- Self-signed HTTPS backends accepted (
rejectUnauthorized: false) - Max 10 simultaneous checks per cycle
- Webhook events:
route_down,route_up - Prerequisite for Circuit Breaker feature
Event-based email notifications for security, peers, routes, and system events.
| Group | Events |
|---|---|
| Security | Account lockout, failed auth attempts |
| Peers | Peer expiring soon (7 days), peer expired, peer offline |
| Routes | Route monitoring down/up, route auto-disabled |
| System | Backup overdue, CPU/RAM threshold exceeded |
Navigate to Settings > Email & SMTP:
- Enable/disable alerts per event group
- Set alert recipient email address
- Configure CPU/RAM thresholds
- Configure backup reminder frequency
- Requires SMTP fully configured
- Alerts sent once per status change (not on every check)
- All email template values HTML-escaped
- CPU/RAM thresholds configurable via
GC_ALERT_CPU_THRESHOLD/GC_ALERT_RAM_THRESHOLD
Interactive traffic history charts (24h, 7d, 30d) with persistent upload/download totals per peer.
- Traffic snapshots collected every 60 seconds (configurable via
GC_TRAFFIC_INTERVAL) - WireGuard stats extracted via
wg show(transfer RX/TX) - Snapshots stored as deltas in the database
- Charts display bandwidth over time with selectable periods
| Period | Granularity |
|---|---|
| 24h | Per-minute snapshots |
| 7d | Hourly aggregated |
| 30d | Daily aggregated |
# Get peer traffic history
curl -H "Authorization: Bearer gc_your_token" \
"https://gate.example.com/api/v1/peers/5/traffic?period=24h"
# Response:
# {
# "ok": true,
# "traffic": {
# "total_rx": 1024000,
# "total_tx": 512000,
# "chart": [
# {"timestamp": "2026-03-25T10:00:00Z", "rx": 100, "tx": 50},
# ...
# ]
# }
# }- Rates calculated from deltas (bytes/second)
- Older snapshots automatically aggregated to save storage
- Traffic data included in peer detail view
Configurable account lockout after N failed login attempts for both admin and route-auth logins.
- Each failed login increments a counter per email (route-auth) or username (admin)
- After reaching the threshold, the account is locked for the configured duration
- Lockout is email-based (not IP-based) to prevent IP rotation bypass
- Manual unlock available in Settings > Security
| Setting | Options | Default |
|---|---|---|
| Enable | On/Off toggle | On |
| Threshold | Failed attempts before lockout | 5 |
| Duration | Lock duration | 30 minutes |
# Get lockout settings
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/settings/security
# Update lockout settings
curl -X PUT https://gate.example.com/api/v1/settings/security \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"lockout": {"enabled": true, "threshold": 5}}'
# Manual unlock (admin)
curl -X POST https://gate.example.com/api/v1/settings/unlock-admin \
-H "Authorization: Bearer gc_your_token"
# Manual unlock (route-auth)
curl -X POST "https://gate.example.com/api/v1/settings/unlock-route-auth?email=user@example.com" \
-H "Authorization: Bearer gc_your_token"- Counter resets after successful login
- Auto-unlock after duration expires (
locked_untiltimestamp) - Lockout triggers email alert (if configured)
Configurable password rules for minimum length, uppercase letters, numbers, and special characters.
Navigate to Settings > Security:
| Rule | Default |
|---|---|
| Minimum Length | 8 characters |
| Require Uppercase | Off |
| Require Numbers | Off |
| Require Special Characters | Off |
# Get password rules
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/settings/security
# Update rules
curl -X PUT https://gate.example.com/api/v1/settings/security \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"password": {
"min_length": 12,
"require_uppercase": true,
"require_numbers": true,
"require_special": true
}
}'- Rules applied when changing admin password or setting route-auth passwords
- All enabled rules must pass (AND logic)
- Special characters:
!@#$%^&* - Error messages localized (EN + DE)
Environment variables for tuning all operational timeouts and background task intervals.
| Variable | Description | Default |
|---|---|---|
GC_WG_COMMAND_TIMEOUT |
WireGuard CLI timeout | 10000 ms |
GC_CADDY_API_TIMEOUT |
Caddy admin API timeout | 10000 ms |
GC_WEBHOOK_TIMEOUT |
Webhook delivery timeout | 10000 ms |
GC_MONITOR_HTTP_TIMEOUT |
HTTP health check timeout | 10000 ms |
GC_MONITOR_TCP_TIMEOUT |
TCP health check timeout | 5000 ms |
GC_TRAFFIC_INTERVAL |
Traffic snapshot interval | 60000 ms |
GC_PEER_POLL_INTERVAL |
Peer status poll interval | 30000 ms |
GC_CADDY_SYNC_DELAY |
Delay before initial Caddy sync | 5000 ms |
GC_SHUTDOWN_TIMEOUT |
Graceful shutdown timeout | 10000 ms |
Additional tuning available via Settings > Advanced:
- Monitoring interval (seconds)
- Data retention (days)
- Peer timeout (days)
Automatically disable peers after a configurable time period. Useful for temporary guest access, contractor VPN, or time-limited demo environments.
-
When creating or editing a peer, set an expiration date:
- Never (default) — peer stays active indefinitely
- 1 day / 7 days / 30 days / 90 days — relative to creation/edit time
- Custom date — pick any future date via date picker
-
A background task checks every 60 seconds for expired peers. When found:
- The peer is automatically disabled (
enabled = 0) - WireGuard config is resynced (peer removed from active config)
- An activity event
peer_expiredis logged - If email alerts are configured for peer events, a notification is sent
- The peer is automatically disabled (
-
Visual indicators in the peer list:
- Expired — red tag, peer is disabled
- Expires soon — orange tag, expires within 7 days
- Expires on [date] — grey tag for future dates
# Create peer with 30-day expiry
curl -X POST https://gate.example.com/api/v1/peers \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "guest-access",
"description": "Temporary guest",
"expires_at": "2026-04-21T00:00:00.000Z"
}'
# Update expiry (set to never)
curl -X PUT https://gate.example.com/api/v1/peers/5 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"expires_at": null}'
# Update expiry (extend by 30 days from now)
curl -X PUT https://gate.example.com/api/v1/peers/5 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"expires_at": "2026-05-22T00:00:00.000Z"}'Peer expiry dates are included in backups and restored automatically.
Restrict which WireGuard peers can access specific routes. By default, any peer can reach any route's backend through the VPN tunnel. ACL adds per-route access restrictions enforced at the Caddy reverse proxy level.
- Open a route's settings (create or edit) and enable "Peer Access Control"
- A checklist of all peers appears — select which peers should have access
- GateControl generates a Caddy
remote_ipmatcher with the WireGuard IPs of the selected peers - Caddy only allows requests from those IPs — all others receive a 403 Forbidden
| Peer | WireGuard IP |
|---|---|
| Alice Laptop | 10.8.0.2 |
| Bob Phone | 10.8.0.3 |
| Guest WiFi | 10.8.0.4 |
Route: nas.example.com — ACL enabled, only Alice + Bob allowed:
Alice (10.8.0.2) → nas.example.com → Allowed
Bob (10.8.0.3) → nas.example.com → Allowed
Guest (10.8.0.4) → nas.example.com → 403 Forbidden
When ACL is enabled, GateControl adds a remote_ip matcher to the route:
{
"match": [{
"host": ["nas.example.com"],
"remote_ip": {
"ranges": ["10.8.0.2/32", "10.8.0.3/32"]
}
}],
"handle": [{ "handler": "reverse_proxy", "upstreams": [{"dial": "10.8.0.5:5001"}] }]
}- ACL off (default) = all peers can access the route
- ACL on, no peers selected = all traffic is blocked (warning shown in UI)
- ACL only affects traffic through the WireGuard tunnel (Caddy checks peer VPN IPs)
- ACL changes are synced to Caddy immediately
- ACL rules are included in backup/restore (peers referenced by name for portability)
# Create route with ACL
curl -X POST https://gate.example.com/api/v1/routes \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"domain": "nas.example.com",
"target_ip": "10.8.0.5",
"target_port": 5001,
"acl_enabled": true,
"acl_peers": [3, 4]
}'
# Update ACL on existing route
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"acl_enabled": true,
"acl_peers": [3, 4, 5]
}'
# Disable ACL
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"acl_enabled": false}'Schedule automatic backups at configurable intervals with retention management. Backup files are stored locally and can be downloaded or deleted via the Settings UI.
Navigate to Settings > Automatic Backups:
| Setting | Options | Default |
|---|---|---|
| Enable | On/Off toggle | Off |
| Schedule | Every 6 hours, Every 12 hours, Daily, Every 3 days, Weekly | Daily |
| Retention | Number of backups to keep (oldest deleted first) | 5 |
- Enable automatic backups and choose a schedule
- GateControl runs the backup at the configured interval using the same backup engine as manual backups
- Files are saved to
/data/backups/asgatecontrol-YYYYMMDD-HHmmss.json - After each backup, files exceeding the retention limit are automatically deleted (oldest first)
- On backup failure, an email alert is sent (if email alerts are configured)
The Settings page shows all existing backup files with:
- Filename and file size
- Download button — download the backup file
- Delete button — remove individual backup files
- Run Now button — trigger an immediate backup regardless of schedule
Backups are stored in /data/backups/ inside the Docker container. Since /data/ is mounted as a Docker volume (gatecontrol-data), backups persist across container restarts and updates.
# Get auto-backup settings
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/settings/autobackup
# Enable daily backups, keep 10
curl -X PUT https://gate.example.com/api/v1/settings/autobackup \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"enabled": true, "schedule": "daily", "retention": 10}'
# Trigger immediate backup
curl -X POST https://gate.example.com/api/v1/settings/autobackup/run \
-H "Authorization: Bearer gc_your_token"
# List backup files
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/settings/autobackup/list
# Download a specific backup
curl -H "Authorization: Bearer gc_your_token" \
-o backup.json \
https://gate.example.com/api/v1/settings/autobackup/download/gatecontrol-20260322-120000.json
# Delete a backup file
curl -X DELETE -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/settings/autobackup/gatecontrol-20260322-120000.jsonFor off-site backup storage, use API tokens and a cron job:
# /usr/local/bin/gatecontrol-offsite-backup.sh
#!/bin/bash
TOKEN="gc_your_token"
URL="https://gate.example.com"
# Trigger a fresh backup
curl -sf -X POST -H "Authorization: Bearer $TOKEN" "$URL/api/v1/settings/autobackup/run"
# Get latest filename
LATEST=$(curl -sf -H "Authorization: Bearer $TOKEN" "$URL/api/v1/settings/autobackup/list" \
| jq -r '.files[0].name')
# Download and copy to NAS/S3/etc.
curl -sf -H "Authorization: Bearer $TOKEN" \
-o "/mnt/nas/backups/$LATEST" \
"$URL/api/v1/settings/autobackup/download/$LATEST"Export activity logs and access logs as CSV or JSON files for compliance, external analysis, or archival purposes.
On the Logs page, export buttons appear next to the log tabs:
- Export CSV — downloads a CSV file with header row
- Export JSON — downloads a pretty-printed JSON array
Export respects the currently active filters (e.g., status code filter for access logs).
Activity Log CSV:
timestamp,event,severity,message,details
2026-03-22T10:30:00Z,peer_created,info,Peer created: my-laptop,{"peer_id":5}
2026-03-22T10:31:00Z,route_created,info,Route created: app.example.com,{"route_id":3}Access Log CSV:
timestamp,domain,method,path,status,remote_ip,user_agent
2026-03-22T10:30:00Z,app.example.com,GET,/,200,10.8.0.2,Mozilla/5.0...
2026-03-22T10:30:01Z,app.example.com,POST,/api/data,201,10.8.0.2,curl/8.0JSON format contains the same fields as an array of objects.
gatecontrol-activity-YYYYMMDD.csv/.jsongatecontrol-access-YYYYMMDD.csv/.json
# Export activity log as CSV
curl -H "Authorization: Bearer gc_your_token" \
-o activity.csv \
"https://gate.example.com/api/v1/logs/activity/export?format=csv"
# Export activity log as JSON
curl -H "Authorization: Bearer gc_your_token" \
-o activity.json \
"https://gate.example.com/api/v1/logs/activity/export?format=json"
# Export access log as CSV (with status filter)
curl -H "Authorization: Bearer gc_your_token" \
-o access-errors.csv \
"https://gate.example.com/api/v1/logs/access/export?format=csv&status=500"
# Export access log filtered by domain
curl -H "Authorization: Bearer gc_your_token" \
-o access.json \
"https://gate.example.com/api/v1/logs/access/export?format=json&domain=app.example.com"Log export endpoints require the logs token scope.
Stateless token authentication for automation, CI/CD pipelines, scripts, and external integrations. See API_GUIDE.md for complete integration examples with Home Assistant, Python, Node.js, Bash, and more.
- Navigate to Settings > API Tokens
- Enter a token name (e.g., "Home Assistant", "Backup Script")
- Select scopes (permissions)
- Optionally set an expiry date
- Click Create — the token (
gc_...) is shown once. Copy it immediately.
| Scope | Access |
|---|---|
full-access |
All endpoints (read + write) |
read-only |
GET requests on all endpoints |
peers |
Full access to peer endpoints |
routes |
Full access to route endpoints |
settings |
Settings + SMTP endpoints |
webhooks |
Webhook endpoints |
logs |
Log endpoints + export |
system |
System, WireGuard, Caddy endpoints |
backup |
Backup/restore endpoints |
# Create token
curl -X POST https://gate.example.com/api/v1/tokens \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "CI/CD Deploy",
"scopes": ["routes"],
"expires_at": "2026-12-25"
}'
# List tokens (masked)
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/tokens
# Revoke token
curl -X DELETE https://gate.example.com/api/v1/tokens/3 \
-H "Authorization: Bearer gc_your_token"
# Use token (two methods)
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/peers
curl -H "X-API-Token: gc_your_token" \
https://gate.example.com/api/v1/peers- Only the SHA-256 hash is stored in the database — the raw token cannot be retrieved after creation
- Tokens use the
gc_prefix (48 random bytes, hex-encoded) for easy identification - Token-authenticated requests bypass CSRF (stateless, no session)
- Tokens cannot create or delete other tokens (prevents privilege escalation)
- Each token has its own rate limit counter (1000 requests / 15 min)
Per-route response compression via Caddy's encode handler. Reduces bandwidth 60–80% for text content.
- Enable compression on any HTTP route
- Caddy applies Zstd (preferred) or Gzip based on the client's
Accept-Encodingheader - Compression is transparent — clients decompress automatically
- Bodies smaller than ~100 bytes are not compressed (overhead)
# Enable compression
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"compress_enabled": true}'{
"handler": "encode",
"encodings": {
"zstd": {},
"gzip": {}
}
}- Browser support: Gzip (all browsers), Zstd (Chrome 123+, Firefox 112+)
- Typical savings: HTML 75–80%, CSS 82–86%, JSON 85–89%
- Mirror targets receive uncompressed data (compression applied after mirroring)
- Only for HTTP routes, not L4
Per-route key-value editor for custom HTTP headers with CORS and security header presets.
- Open a route's edit modal and navigate to the Headers tab
- Add request headers (sent to backend) or response headers (sent to client)
- Use presets for common CORS or security headers
| Setting | Limit |
|---|---|
| Header Name | Alphanumeric + hyphen, max 256 chars |
| Header Value | Max 4096 chars, no Caddy placeholders |
| Presets | CORS headers, Security headers |
# Set custom headers
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"custom_headers": {
"request": [
{"name": "X-Custom", "value": "my-value"},
{"name": "X-Forwarded-Custom", "value": "test"}
],
"response": [
{"name": "X-Powered-By", "value": "GateControl"},
{"name": "Cache-Control", "value": "max-age=3600"}
]
}
}'- Header name validation:
^[a-zA-Z0-9\-]+$(prevents injection) - Caddy placeholders (e.g.,
{http.request.header.user}) are rejected - Request headers added before reverse proxy; response headers set after backend response
Configurable requests-per-IP-per-window via the caddy-ratelimit plugin.
- Enable rate limiting on any HTTP route
- Set max requests and time window
- Each client IP gets a separate quota
- HTTP 429 returned when limit exceeded, with
Retry-Afterheader
| Setting | Options |
|---|---|
| Max Requests | Number per window |
| Window | 1s, 1m, 5m, 1h |
# Enable rate limiting (100 requests per minute)
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"rate_limit_enabled": true,
"rate_limit_requests": 100,
"rate_limit_window": "1m"
}'{
"handler": "rate_limit",
"rate_limits": {
"static": {
"key": "{http.request.remote.host}",
"window": "1m",
"max_events": 100
}
}
}- Per-IP rate limiting — clients behind NAT share the same limit
- Window validation: accepts
1s,1m,5m,1h; invalid values default to1m - Only for HTTP routes, not L4
- Rate limiting counts only primary requests, not mirror targets
Automatic retries on backend connection failure via Caddy's load balancing retries.
- Enable retry on any HTTP route
- Set retry count (1–10)
- On connection error, Caddy retries the request up to N times
- With multiple backends, retries rotate across available upstreams
# Enable retry (5 attempts)
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"retry_enabled": true,
"retry_count": 5
}'- Retries are immediate (no exponential backoff or jitter)
- All HTTP methods retried (idempotency not enforced)
- Single backend: retries go to the same server
- Multiple backends: retries provide failover behavior
- Only for HTTP routes, not L4
Weighted round-robin across multiple backend targets per route. Backend targets use peer dropdowns — IPs are resolved at Caddy config build time.
- Open a route's edit modal and add multiple backends
- Select peers from a dropdown and assign ports and weights
- GateControl resolves peer IPs when building Caddy config
- Disabled peers are automatically skipped
- When a peer's IP changes, the next config rebuild picks up the new IP
# Set multiple backends with weights
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"backends": [
{"peer_id": 1, "port": 8080, "weight": 50},
{"peer_id": 2, "port": 8080, "weight": 50},
{"peer_id": 3, "port": 8080, "weight": 100}
]
}'{
"handler": "reverse_proxy",
"upstreams": [
{"dial": "10.8.0.3:8080"},
{"dial": "10.8.0.4:8080"},
{"dial": "10.8.0.5:8080"}
],
"load_balancing": {
"selection_policy": {
"policy": "weighted_round_robin",
"weights": [50, 50, 100]
}
}
}- Weight ratio is proportional: 50:50:100 distributes ~25%/25%/50%
- Disabled peers filtered out before building upstreams
- Direct IP targets (
target_ipwithoutpeer_id) still supported for backward compatibility - Pair with Retry for failover behavior
- Pair with Sticky Sessions for session affinity
Cookie-based session affinity for multi-backend routes. The same client is always routed to the same backend.
- Enable sticky sessions on a route with multiple backends
- Caddy sets a cookie on the first request
- Subsequent requests from the same client are routed to the same backend
- Cookie expires after the configured TTL
| Setting | Default |
|---|---|
| Cookie Name | gc_sticky |
| Cookie TTL | 3600 seconds |
# Enable sticky sessions
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"sticky_enabled": true,
"sticky_cookie_name": "gc_sticky",
"sticky_cookie_ttl": "3600"
}'- Requires multiple backends (replaces round-robin selection policy)
- Cookie name validation:
^[a-zA-Z0-9_\-]+$ - TTL converted to Caddy duration (e.g.,
"3600s") - Client must accept cookies for affinity to work
/metrics endpoint with Prometheus text format for Grafana integration.
| Metric | Type | Description |
|---|---|---|
gatecontrol_peers_total |
gauge | Total peers |
gatecontrol_peers_online |
gauge | Online peers |
gatecontrol_peers_enabled |
gauge | Enabled peers |
gatecontrol_peer_status |
gauge | Per-peer online (1/0), labels: name, ip |
gatecontrol_peer_transfer_rx_bytes |
gauge | Per-peer RX bytes |
gatecontrol_peer_transfer_tx_bytes |
gauge | Per-peer TX bytes |
gatecontrol_routes_total |
gauge | Total routes |
gatecontrol_routes_active |
gauge | Enabled routes |
gatecontrol_route_monitoring_status |
gauge | Per-route UP (1/0), label: domain |
gatecontrol_cpu_usage_percent |
gauge | CPU usage % |
gatecontrol_memory_usage_percent |
gauge | RAM usage % |
gatecontrol_uptime_seconds |
gauge | App uptime |
- Navigate to Settings > API and enable Prometheus
- Create an API token with
read-onlyorsystemscope - Configure Prometheus scrape config:
scrape_configs:
- job_name: gatecontrol
scheme: https
authorization:
credentials: gc_your_token
static_configs:
- targets: ['gate.example.com']# Get metrics (header-only auth, no query params)
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/metrics- Header-only authentication (query parameter
?token=removed for security) - Metrics collected on-demand per request (not cached)
- Label values properly escaped (backslash, quote, newline)
Automatic response blocking (HTTP 503) when backends fail repeatedly. Three-state machine with auto-recovery.
CLOSED (normal) ──[N failures]──→ OPEN (returns 503)
↑ │
└─[success in half-open]──← HALF-OPEN (testing)
[failure]──→ OPEN
- Closed — normal operation, monitoring counts consecutive failures
- Open — after N failures, Caddy returns 503 with
Retry-Afterheader - Half-Open — after timeout, the next monitoring check tests recovery
- If the check succeeds, state returns to Closed; if it fails, back to Open
| Setting | Options | Default |
|---|---|---|
| Threshold | Consecutive failures to trigger | 5 |
| Timeout | Seconds before testing recovery | 30 |
# Enable circuit breaker
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"monitoring_enabled": true,
"circuit_breaker_enabled": true,
"circuit_breaker_threshold": 5,
"circuit_breaker_timeout": 30
}'
# Manual reset to closed
curl -X PATCH https://gate.example.com/api/v1/routes/1/circuit-breaker \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"status": "closed"}'- Requires Uptime Monitoring enabled (no monitoring = no state changes)
- In-memory counters reset on app restart (DB persists status)
- Threshold counts consecutive failures only (success resets counter)
- Manual reset clears counter and timestamp
Multi-select peers and routes for bulk enable/disable/delete with a floating action bar.
- On the Peers or Routes page, checkboxes appear next to each item
- Select multiple items — a floating action bar shows the count and available actions
- Choose Enable, Disable, or Delete
- All selected items are processed in one request
# Batch enable peers
curl -X POST https://gate.example.com/api/v1/peers/batch \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"action": "enable", "ids": [1, 2, 5]}'
# Batch delete routes
curl -X POST https://gate.example.com/api/v1/routes/batch \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"action": "delete", "ids": [10, 15, 20]}'
# Response: {"ok": true, "affected": 3}- Actions:
enable,disable,delete - Activity logged per batch operation (e.g.,
peers_batch_enabled) - Caddy/WireGuard sync triggered automatically after batch changes
Organize peers by team or location with colored badges and filter dropdown.
- Create peer groups with a name, description, and color
- Assign peers to groups when creating or editing
- Filter the peer list by group
- Colored badges appear on peer cards
# Create group
curl -X POST https://gate.example.com/api/v1/peer-groups \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "Office Servers",
"description": "Servers in main office",
"color": "#007bff"
}'
# List groups
curl -H "Authorization: Bearer gc_your_token" \
https://gate.example.com/api/v1/peer-groups
# Update group
curl -X PUT https://gate.example.com/api/v1/peer-groups/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"name": "Office Servers v2", "color": "#28a745"}'
# Delete group (peers are ungrouped)
curl -X DELETE https://gate.example.com/api/v1/peer-groups/1 \
-H "Authorization: Bearer gc_your_token"
# Assign peer to group
curl -X PUT https://gate.example.com/api/v1/peers/5 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{"group_id": 1}'- Color validated via hex regex (prevents CSS injection)
- Deleting a group unsets
group_idon all member peers (peers are not deleted) - Groups included in backup/restore (backup format v3)
Asynchronous request duplication to up to 5 mirror targets for shadow deployments, debugging, or load testing. The primary response is never affected.
- Enable request mirroring on any HTTP route
- Add mirror targets by selecting peers and ports (up to 5)
- Caddy's custom
mirrorhandler duplicates each request asynchronously - Mirror targets receive an exact copy (method, URI, headers, body)
- Mirror failures are silently logged — they never affect the client response
| Setting | Limit |
|---|---|
| Max Targets | 5 per route |
| Max Body Size | 10 MB (larger requests mirrored without body) |
| Per-Target Timeout | 10 seconds |
# Enable mirroring with 2 targets
curl -X PUT https://gate.example.com/api/v1/routes/1 \
-H "Authorization: Bearer gc_your_token" \
-H "Content-Type: application/json" \
-d '{
"mirror_enabled": true,
"mirror_targets": [
{"peer_id": 2, "port": 8080},
{"peer_id": 3, "port": 8080}
]
}'ACL / Forward Auth → Custom Headers → Rate Limiting → Mirroring → Compression → Reverse Proxy
- Mirror targets receive uncompressed data (mirroring before compression)
- WebSocket upgrades automatically skipped
- Cannot mirror to the primary backend IP (validation)
- Disabled peers skipped at config generation time
- Rate limiting counts only the primary request, not mirrors
- Activity event:
route_mirror_changed
Responsive sidebar for phones and tablets. The navigation sidebar collapses into a hamburger menu on screens smaller than 1024px.
| Screen | Sidebar |
|---|---|
| Desktop (>= 1024px) | Always visible, no changes |
| Mobile/Tablet (< 1024px) | Hidden by default, hamburger button in topbar |
- Tap hamburger — sidebar slides in from left
- Tap overlay — sidebar closes
- Tap nav item — sidebar closes, navigates
- Press Escape — sidebar closes
- Resize to desktop — sidebar auto-shows, hamburger hidden
- Hamburger button has
aria-labelandaria-expanded - Focus trap when sidebar is open (Tab cycles within sidebar)
- 44px minimum touch targets
GateControl supports multiple UI themes:
- Classic (
default) — Original design with warm tones (Outfit font, teal accent) - Pro (
pro) — Clean corporate design (Inter font, Royal Blue accent, Stripe/Linear aesthetic)
Set the default theme via environment variable:
GC_DEFAULT_THEME=pro
Users can switch themes individually via Profile → Theme.