Skip to content

Repository files navigation

HAProxy OpenManager

Modern, web-based management interface for HAProxy load balancers with multi-cluster support, agent-based pull architecture, and automated deployment capabilities.

HAProxy OpenManager Showcase

Table of Contents

  1. Project Overview
  2. Screenshots
  3. Key Capabilities
  4. Architecture
  5. Features & User Interface
  6. Getting Started - First-Time Usage
  7. Installation
  8. Configuration
  9. API Reference
  10. Development
  11. Troubleshooting
  12. Contributing
  13. Use Cases
  14. License
  15. Author
  16. Support

Project Overview

HAProxy OpenManager is a comprehensive management platform designed to simplify HAProxy administration across multiple environments. It provides a unified interface for managing remote HAProxy instances through an agent-based pull architecture, supporting multi-cluster configurations with centralized control.

Agent Pull Architecture

This project implements an agent pull architecture where management operations are executed through a lightweight agent service (haproxy-agent) installed on each target HAProxy server:

Conceptual Hierarchy

Pool (Logical Grouping)
  └── Cluster (HAProxy Cluster Definition)
       └── Agent 1 → HAProxy Server 1
       └── Agent 2 → HAProxy Server 2
       └── Agent 3 → HAProxy Server 3

Setup Flow

  1. Create Agent Pool: First, create a pool to logically group agents (e.g., "Production Pool")
  2. Create Cluster: Select the pool and create a cluster (e.g., "Production Cluster with 3 nodes")
  3. Deploy Agents: Generate installation scripts for each HAProxy server, selecting the same pool
  4. Result: Multiple agents in one pool, all managing one cluster

How It Works

  1. Automatic Polling: Agents periodically poll the backend for configuration tasks (every 30 seconds)
  2. Local Execution: Agents apply changes to local haproxy.cfg and reload HAProxy service
  3. No Direct Push: Backend never directly pushes changes - agents pull on their schedule
  4. Unified Management: All agents in a pool receive same cluster configuration

This architecture provides better security (no inbound connections to HAProxy servers), resilience (agents can retry on failure), and scalability (backend doesn't track agent state).

Features at a Glance

Agent-Based Pull Architecture - Secure, scalable management without inbound connections
Multi-Cluster & Pool Management - Organize and manage multiple HAProxy clusters from one interface
Frontend/Backend/Server CRUD - Complete entity management with visual UI
New Site Wizard - Step-by-step guided setup for a complete proxied host (frontend + backend + servers + SSL/ACME) in one consolidated atomic apply
Bulk Config Import - Import existing haproxy.cfg files with smart SSL auto-assignment
Version Control & Rollback - Every change versioned with one-click restore capability
Real-Time Monitoring - Live stats, health checks, and performance dashboards
SSL Certificate Management - Centralized SSL with expiration tracking
CSR Creation (v1.9.0) - Generate a private key + CSR in-app (RSA 2048/4096, ECDSA P-256/P-384, full subject + SANs), have it signed by any external CA, then import the signed certificate — the key never leaves the server
ACME Auto SSL (Let's Encrypt) - Automated certificate issuance, renewal, and deployment via ACME protocol
ACME DNS-01 Challenge (v1.8.0) - TXT-record validation for internal/isolated clusters (no public port 80) and wildcard certificates; pluggable DNS providers (Manual + Cloudflare + GoDaddy (v1.10.0)), opt-in, HTTP-01 unchanged
ACME Certificate Diagnostic Panel - Automated preflight that checks agent readiness, DNS resolution, port 80 reachability, and ACME challenge ACL before issuing certificates
WAF Rules - Web Application Firewall management and deployment
Agent Script Versioning - Update agents via UI (Monaco editor) with auto-upgrade
Token-Based Agent Auth - Secure token management with revoke/renew
IP Inventory - Cross-cluster IP search to identify agents, VIPs, and backend servers by IP
HA / VIP (Keepalived) Management - Create virtual IPs from the UI; the agent installs & configures Keepalived (unicast VRRP) with a HAProxy health-check so the VIP fails over automatically; live MASTER/BACKUP detection per node
Role-Based User Management - Admin and user roles with granular permissions and access control
User Activity Audit Logs - Complete audit trail of all system events
Request/Response Log (v1.11.0) - Every inbound API call (GETs and errors included) and every outbound HTTP call the backend makes (ACME, Cloudflare, GoDaddy, agents) in one filterable timeline, with redacted, size-capped bodies and operator-configurable retention
REST API - Full programmatic access for automation and CI/CD integration

Screenshots

Multi-Cluster & Agent Setup

Multi-Cluster Management - Cluster selector dropdown showing multiple clusters with agent pools and online agent counts - switch between different clusters Multi-Cluster Management - Cluster Selector

Agent Management - Agent setup wizard with platform selection (Linux/macOS) and architecture (x86_64/ARM64) Agent Management - Setup Wizard

Agent Management - Generated installation script ready for deployment to target servers Agent Management - Script Generation

Dashboard - Real-time Monitoring

Dashboard - Overview tab showing system metrics, active servers, SSL certificates, and cluster agent health Dashboard - Overview

Dashboard - Performance Trends with request rate and response time heatmap (24h view) Dashboard - Performance Trends

Dashboard - Health Matrix showing backend servers with detailed health check status and response times Dashboard - Health Matrix

Dashboard - Response Time Heatmap (24h) with color-coded latency visualization per backend Dashboard - Response Time Heatmap

HAProxy Configuration

Frontend Management - List view with SSL/TLS bindings, sync status, and config status Frontend Management - List View

Frontend Management - Edit modal for configuring bind address, protocol mode, default backend, SSL/TLS, ACLs, and advanced options Frontend Management - Edit Modal

Backend Configuration - Backend server and pool configuration page Backend Configuration

Backend Management - Backends & Servers tab with 165 servers across 56 backends, health status overview Backend Management - Backends & Servers

Configuration Import & Viewer

Bulk Config Import - Paste existing haproxy.cfg and parse configuration Bulk Config Import - Input

Bulk Config Import - Parsed entities showing 56 frontends, 55 backends, 165 servers with SSL and configuration recommendations Bulk Config Import - Results

Configuration Viewer - Real-time configuration viewer to pull haproxy.cfg from live agents Configuration Viewer

Apply Management & Version Control

Apply Management - Pending changes and deployment history with version tracking Apply Management

Version History - Configuration diff viewer showing added/removed lines with summary Version History - Diff Viewer

Version History - Complete version history with restore capabilities and deployment status Version History - Timeline

Security & Certificate Management

Security Management - Agent API token management with active tokens, expiration tracking, and revoke capabilities Security Management - API Tokens

ACME Automation - Automated SSL Certificates

ACME Automation - Setup guide with prerequisites checklist, certificate statistics, and order management ACME Automation - Setup Guide

ACME Automation - Active certificate orders with PENDING status tracking and renewal schedule ACME Automation - Certificate Orders

ACME Settings - ACME/SSL Automation configuration with provider selection, staging mode, and auto-renewal settings ACME Settings

Site Wizard - Guided Multi-Step Host Setup

New Site Wizard - Multi-step guided form for creating a complete proxied host (domains + backend pool + servers + frontend + SSL/ACME) with live HAProxy config validation and atomic apply New Site Wizard

WAF Management

WAF Management - WAF rule configuration with request filtering, rate limiting, and advanced options WAF Management

IP Inventory - Cross-Cluster IP Search

IP Inventory - Unified view of all IPs across clusters with search capability to identify which cluster, agent, or backend server an IP belongs to IP Inventory

Key Capabilities

Configuration & Entity Management

  • Frontend Management: Create, edit, update, and delete frontend configurations with SSL bindings, ACL rules, and routing
  • Backend Management: Full CRUD operations for backend pools with load balancing algorithms and health checks
  • Server Management: Add, update, remove, and configure backend servers with weights, maintenance mode, and connection limits
  • Bulk Configuration Import: Import existing haproxy.cfg files to instantly manage all frontends, backends, and servers without manual recreation
    • Smart SSL Auto-Assignment: Automatically matches and assigns SSL certificates during bulk import if they exist in SSL Management with matching names and SYNCED status - eliminates manual SSL configuration after import
  • Configuration Synchronization: All entities (backends, frontends, servers, SSL certificates, WAF rules) auto-sync to agents
  • Configuration Viewer: View live HAProxy configuration pulled from agents via Configuration Management page

Agent & Cluster Management

  • Agent-Based Management: Lightweight agent service for secure, pull-based HAProxy management
  • Pool Management: Organize agents into logical pools for better cluster organization
  • Multi-Cluster Support: Manage multiple HAProxy clusters from a single interface with cluster selector
  • Agent Token Management: Secure token-based agent installation with token revoke and renew capabilities
  • Agent Script Editor: Monaco code editor for updating agent scripts via UI (not binary) - create new versions, rollback, and auto-upgrade all agents
  • Platform-Specific Scripts: Auto-generated installation scripts for Linux and macOS (x86_64/ARM64)
  • HA / VIP (Keepalived) Management: Create a virtual IP from the UI and the agents install & configure Keepalived (VRRP) with a HAProxy health-check so the VIP fails over automatically when HAProxy drops — opt-in, with live MASTER/BACKUP per node, multi-distro install, cluster-driven config path, and Apply/Reject staging

Version Control & Change Management

  • Apply Management: Centralized change tracking and deployment status monitoring across all agents
  • Version Control: Every configuration change creates a versioned snapshot with complete history
  • Configuration Restore: Restore any previous configuration version with one-click rollback capability
  • Diff Visualization: See exactly what changed between versions with line-by-line comparison
  • Change Tracking: Track who made changes, when, and what entities were affected

Monitoring & Security

  • Real-time Monitoring: Live statistics, health checks, and performance metrics from HAProxy stats socket
  • SSL Management: Centralized certificate management with expiration tracking and zero-downtime updates
  • WAF Rules: Web Application Firewall rule management and deployment for security protection
  • User Management: Role-based access control with admin and user roles
  • User Activity Logs: Complete audit trail of all user actions, configuration changes, and system events

ACME Auto SSL / Let's Encrypt

  • Automated Certificate Issuance: Request SSL certificates from Let's Encrypt or any ACME-compatible CA directly from the UI
  • Automatic Renewal: Hourly background task monitors certificate expiry and creates renewal orders before expiration
  • Auto-Completion of Validated Orders (v1.4.0): Independent 60-second background task drives CA-validated orders through finalize -> download -> save without requiring user intervention; multi-replica safe via PostgreSQL FOR UPDATE SKIP LOCKED atomic claim, plus per-order session-level pg_advisory_lock to serialize concurrent completion attempts (UI "Complete" + auto-task race protection)
  • Zero-Touch Deployment: Renewed certificates are automatically applied through the same PENDING -> APPLIED pipeline as manual SSL updates, with agent notification
  • Stuck Order Detection (v1.4.0): Setup wizard surfaces orders that the CA has validated but not yet downloaded, with one-click Complete action and automatic 60-second retry
  • Multi-Provider Support: Configurable ACME directory URL supports Let's Encrypt, ZeroSSL, Google Trust Services, Buypass, and custom CAs
  • HTTP-01 Challenge: Built-in challenge responder with automatic HAProxy routing injection; reserved backend name _acme_challenge_backend is auto-managed and protected from manual edits / agent sync collisions
  • DNS-01 Challenge (v1.8.0 — Issue #35): Validate via a DNS TXT record instead of HTTP on port 80, for internal/isolated clusters with no public ingress and for wildcard certificates (*.example.com). Pluggable per-account DNS provider (Manual + Cloudflare + GoDaddy (v1.10.0); credentials encrypted at rest and verified on save), same PENDING → APPLIED pipeline, bounded automatic retry on propagation lag, and a DNS-01 event timeline. Opt-in via a global setting; HTTP-01 behaviour is unchanged. (See the DNS-01 Challenge subsection under ACME Auto SSL below.)
  • ACME Account Management: Register, view, and deactivate ACME accounts from the UI
  • Staging Mode: Test certificate issuance with Let's Encrypt staging environment before production
  • Custom Staging Endpoint (v1.4.0): Optional staging_url_override setting lets you point staging mode at a private ACME test CA (e.g. Pebble) without touching the production directory URL
  • External Account Binding (EAB): Support for CAs that require EAB (ZeroSSL, Google Trust Services). Enter the EAB Key ID and HMAC Key globally in Settings, or per-account in the Register Account dialog (a per-account value overrides the global setting; leave it blank to use the global one)
  • Structured Error Diagnostics (v1.4.0): All ACME failures (challenge, finalize, download) persist structured JSON to letsencrypt_orders.error_detail for clear post-mortem analysis
  • Audit Logging (v1.4.0): Every ACME operation (request, revoke, CA-chain import, account ops) is captured in user_activity_logs for compliance review
  • ACME Diagnostic Panel (v1.5.0 — Issue #13): Live pre-flight + post-failure diagnostics (DNS / port-80 / routing / account / agents) and merged event timeline (acme_order_events + correlated user_activity_logs) accessible from the ACME Automation page; humanized error rendering for 11+ RFC8555 problem types with backwards-compatible fallback for legacy plain-string error_detail; per-user 5/min rate-limit
  • New Site Setup Wizard (v1.5.0 — Issue #14): Single guided flow that creates a Backend + Servers + HTTP Frontend (and optional HTTPS Frontend with chosen SSL mode: ACME / Upload / Existing / None) in one atomic transaction, with diff preview, draft persistence (PEM stripped), and ACME-staged order completion gated by agent confirmation. Per-mode reject path cleanly rolls back including any wizard-staged ACME orders.
  • Backward Compatible: ACME-managed and manually uploaded certificates coexist seamlessly; existing SSL workflows are completely unaffected

Integration & API

  • REST API: Complete API for programmatic access and integration with CI/CD pipelines
  • Agent Pull Architecture: Secure polling model - no inbound connections required to HAProxy servers
  • Redis Cache: High-performance metrics caching for dashboard with configurable retention

Architecture

System Architecture with Agent Pull Model

%%{init: {'theme':'base', 'themeVariables': { 'fontSize':'16px'}}}%%
graph TB
    subgraph UI_LAYER["🌐 User Interface Layer"]
        USER["👤 Admin/User"] --> WEB["React UI<br/>Nginx Proxy<br/>:8080"]
    end
    
    subgraph BACKEND_LAYER["⚙️ Backend Layer"]
        WEB <--> API["FastAPI<br/>Backend<br/>:8000"]
        API <--> DB[("PostgreSQL<br/>Configs & History")]
        API <--> CACHE[("Redis<br/>Metrics Cache")]
    end
    
    subgraph AGENT_LAYER["📦 Agent Pool: Production Cluster"]
        direction LR
        
        AGENT1["🤖 Agent 1<br/>10.0.1.101<br/>(Poll Every 30s)"]
        AGENT2["🤖 Agent 2<br/>10.0.1.102<br/>(Poll Every 30s)"]
        AGENT3["🤖 Agent 3<br/>10.0.1.103<br/>(Poll Every 30s)"]
        
        HAP1["⚡ HAProxy<br/>+ Stats Socket<br/>+ haproxy.cfg"]
        HAP2["⚡ HAProxy<br/>+ Stats Socket<br/>+ haproxy.cfg"]
        HAP3["⚡ HAProxy<br/>+ Stats Socket<br/>+ haproxy.cfg"]
        
        AGENT1 --> HAP1
        AGENT2 --> HAP2
        AGENT3 --> HAP3
    end
    
    API <-.->|"🔄 Pull Config<br/>📤 Push Stats<br/>⬇️ SSL/WAF"| AGENT1
    API <-.->|"🔄 Pull Config<br/>📤 Push Stats<br/>⬇️ SSL/WAF"| AGENT2
    API <-.->|"🔄 Pull Config<br/>📤 Push Stats<br/>⬇️ SSL/WAF"| AGENT3
    
    classDef uiStyle fill:#3a5a8a,stroke:#6a9eff,stroke-width:3px,color:#fff
    classDef backendStyle fill:#5a3a8a,stroke:#b39ddb,stroke-width:2px,color:#fff
    classDef dbStyle fill:#3a6a3a,stroke:#66bb6a,stroke-width:2px,color:#fff
    classDef agentStyle fill:#8a6a30,stroke:#ffb347,stroke-width:2px,color:#fff
    classDef haproxyStyle fill:#2a6a5a,stroke:#4db6ac,stroke-width:3px,color:#fff
    
    class USER,WEB uiStyle
    class API backendStyle
    class DB,CACHE dbStyle
    class AGENT1,AGENT2,AGENT3 agentStyle
    class HAP1,HAP2,HAP3 haproxyStyle
Loading

Component Details

Component Technology Purpose Port
Frontend React.js 18 + Ant Design Web interface, dashboards, configuration UI 3000
Backend FastAPI + Python REST API, task queue, agent coordination 8000
Database PostgreSQL 15 Configuration storage, user management, agent tasks 5432
Cache Redis 7 Session storage, performance metrics caching 6379
Reverse Proxy Nginx Frontend/backend routing, SSL termination, ACME challenge routing 8080
HAProxy Agent Bash Service Polls backend, applies configs, manages HAProxy N/A
HAProxy Instances HAProxy 2.8+ Load balancer instances being managed by agents 8404+
ACME Service Built-in (Python) ACME protocol client for automated certificate management -

Agent Pull Communication Flow - Detailed Entity Management

This detailed sequence diagram shows the complete lifecycle of configuration management, including entity creation, agent polling, configuration application, SSL management, WAF rules, and agent upgrade process.

sequenceDiagram
    autonumber
    participant U as 👤 User Browser
    participant B as ⚙️ Backend API
    participant D as 💿 PostgreSQL DB
    participant R as 💾 Redis Cache
    participant A as 🤖 Agent
    participant CFG as 📄 haproxy.cfg
    participant H as ⚡ HAProxy Service
    participant S as 🔌 Stats Socket

    Note over U,B: ═══ PHASE 1: USER CREATES ENTITIES ═══
    
    U->>B: Create Backend "api-backend"
    B->>D: INSERT backends + servers
    B->>D: CREATE version v1.0.124
    B-->>U: ✅ Backend Created
    
    U->>B: Create Frontend "web-frontend"
    B->>D: INSERT frontend + SSL binding
    B->>D: UPDATE version v1.0.125
    B-->>U: ✅ Frontend Created
    
    U->>B: Upload SSL Certificate
    B->>D: INSERT ssl_certificates (global)
    B-->>U: ✅ SSL Ready for all clusters
    
    U->>B: Create WAF Rule
    B->>D: INSERT waf_rules (rate limit)
    B-->>U: ✅ WAF Rule Created
    
    U->>B: 🚀 Click "Apply Changes"
    B->>D: Mark tasks READY_FOR_DEPLOYMENT
    B-->>U: ✅ Queued for deployment

    Note over A,R: ═══ PHASE 2: AGENT POLLING CYCLE (30s) ═══
    
    loop Every 30 seconds
        A->>B: GET /api/agent/tasks
        B->>D: Query pending tasks
        B-->>A: Tasks + Version v1.0.125
        
        A->>S: socat show stat
        S-->>A: CSV Stats
        A->>B: POST /api/stats/upload
        B->>R: Store metrics (5min TTL)
        
        A->>B: Check agent version
        B->>D: SELECT latest_agent_version
        B-->>A: Current: 1.0.10, Latest: 1.0.11
    end

    Note over A,CFG: ═══ PHASE 3: AGENT APPLIES CONFIG ═══
    
    A->>A: Parse task bundle
    A->>A: Generate backend config
    A->>A: Generate frontend config
    
    A->>B: Download SSL certificate
    B-->>A: PEM file (encrypted)
    A->>A: Write /etc/haproxy/certs/*.pem
    A->>A: chmod 600 (secure)
    
    A->>A: Generate WAF stick-table
    
    A->>CFG: Backup haproxy.cfg
    A->>CFG: Write new configuration
    A->>H: haproxy -c -f haproxy.cfg
    
    alt Config Valid
        A->>H: systemctl reload haproxy
        H-->>A: ✅ Reload successful
        A->>CFG: Keep new config
    else Config Invalid
        A->>CFG: Restore backup
        A->>A: ❌ Rollback complete
    end

    Note over A,B: ═══ PHASE 4: AGENT REPORTS STATUS ═══
    
    A->>B: POST /api/agent/tasks/complete
    B->>D: UPDATE task status = COMPLETED
    B->>D: INSERT activity_logs (JSON)
    B-->>A: ✅ Acknowledged

    Note over U,B: ═══ PHASE 5: USER MONITORS STATUS ═══
    
    U->>B: GET /api/apply-changes/status
    B->>D: Query all agent statuses
    B-->>U: 3/3 agents ✅ v1.0.125

    Note over U,R: ═══ PHASE 6: DASHBOARD METRICS ═══
    
    U->>B: GET /api/dashboard/stats
    B->>R: GET haproxy:cluster:5:stats
    R-->>B: Latest metrics (30s fresh)
    B-->>U: Real-time dashboard data

    Note over A,B: ═══ PHASE 7: AGENT AUTO-UPGRADE ═══
    
    A->>B: Check version (in polling)
    B-->>A: New version 1.0.11 available
    
    A->>B: GET /api/agent/download/1.0.11
    B->>D: SELECT agent_script WHERE version=1.0.11
    B-->>A: Download script (bash/python)
    
    A->>A: Write new script to /tmp/agent-upgrade.sh
    A->>A: chmod +x agent-upgrade.sh
    A->>A: Execute: /tmp/agent-upgrade.sh
    
    Note over A: Agent Self-Upgrade Process
    A->>A: Stop current agent service
    A->>A: Backup old agent binary
    A->>A: Install new agent version
    A->>A: Update systemd service
    A->>A: Start agent with new version
    
    A->>B: POST /api/agent/upgrade/complete
    B->>D: UPDATE agent version = 1.0.11
    B->>D: LOG upgrade activity
    B-->>A: ✅ Upgrade confirmed
    
    A->>CFG: Read haproxy.cfg (verify integrity)
    A->>H: Check HAProxy service status
    H-->>A: Service running normally
    
    Note over A,B: Agent now running v1.0.11<br/>Continues normal polling cycle

    Note over B,D: ═══ PHASE 8: ACME AUTO-RENEWAL (Hourly) ═══

    B->>D: Query expiring ACME certs (within N days)
    D-->>B: Cert "example.com" expires in 25 days

    B->>B: Create ACME renewal order
    B->>B: Respond to HTTP-01 challenges
    
    Note over B: Next cycle: poll order status
    B->>B: Order valid - download certificate
    B->>D: Update cert content (PENDING)
    B->>D: Create config versions (PENDING)
    B->>D: Auto-apply: apply_ssl_related_configs()
    B->>D: Create consolidated version (APPLIED)
    B->>R: Notify agents via Redis
    
    A->>B: Poll detects new config version
    A->>B: Download renewed certificate
    A->>A: Write updated cert to disk
    A->>H: Reload HAProxy (zero downtime)
    A->>B: Report success
Loading

Key Flow Features:

  • 🔄 Continuous Polling: Agents poll every 30s for tasks, stats collection, and version checks
  • 📄 Config Management: haproxy.cfg backup → validate → apply → rollback if needed
  • 🔐 SSL Handling: Secure download, file permissions (chmod 600), automatic frontend binding
  • 🤖 ACME Auto-Renewal: Automatic certificate renewal via ACME protocol with same Apply pipeline as manual SSL
  • 🛡️ WAF Integration: Stick-tables generated and integrated into config
  • 📊 Stats Collection: Parallel stats upload using socat + stats socket
  • 🔄 Auto-Upgrade: Agents detect new versions and self-upgrade with zero-touch deployment
  • Validation: Every config change is validated before HAProxy reload
  • 📝 Activity Logging: All operations logged in JSON format for audit trail

Features & User Interface

Pool Management - Agent Pool Organization

  • Pool Creation: Create logical pools to group HAProxy agents
  • Pool Assignment: Associate clusters with specific pools
  • Pool Overview: View all agents within a pool
  • Multi-Pool Support: Manage multiple pools for different environments (prod, staging, etc.)
  • Pool-based Filtering: Filter agents and clusters by pool

Cluster Management - Multi-Cluster HAProxy Administration

  • Cluster Selector: Top navigation cluster selector for context switching
  • Cluster Creation: Define clusters and associate with agent pools
  • Cluster Status: Monitor cluster health and connectivity
  • Default Cluster: Set preferred default cluster
  • Cross-Cluster View: Compare metrics across multiple clusters
  • Cluster-Scoped Operations: All configurations are cluster-specific

Agent Management - HAProxy Agent Deployment & Monitoring

Agent Script Management

Agent scripts are not hardcoded in the project. They are stored in the database and can be updated via the UI:

  • Script Editor: Edit agent installation scripts directly in the UI
  • Platform Support: Separate scripts for Linux and macOS (x86_64/ARM64)
  • Version Control: Create new agent versions with changelog
  • Automatic Upgrade: Deploy new agent versions to all agents with one click
  • Upgrade Process: Agents check backend version → Download new script → Self-upgrade → Restart

Agent Version Update Flow:

%%{init: {'theme':'base', 'themeVariables': { 'fontSize':'14px'}}}%%
graph LR
    subgraph USER["👤 User Actions"]
        A["Edit Script<br/>+ Create v1.0.5"] --> B["Mark as<br/>Latest"]
    end
    
    subgraph AUTO["🤖 Automatic Process"]
        C["Agents Poll"] --> D["Detect New<br/>Version"] --> E["Download"] --> F["Self-Upgrade"] --> G["✅ Running<br/>v1.0.5"]
    end
    
    B -.->|Trigger| C
    
    style USER fill:#2a4a6a,stroke:#4a9eff,stroke-width:2px,color:#fff
    style AUTO fill:#3a5a3a,stroke:#66bb6a,stroke-width:2px,color:#fff
    style A fill:#3a5a8a,stroke:#6a9eff,stroke-width:2px,color:#fff
    style B fill:#3a5a8a,stroke:#6a9eff,stroke-width:2px,color:#fff
    style C fill:#4a6a4a,stroke:#66bb6a,stroke-width:2px,color:#fff
    style D fill:#4a6a4a,stroke:#66bb6a,stroke-width:2px,color:#fff
    style E fill:#4a6a4a,stroke:#66bb6a,stroke-width:2px,color:#fff
    style F fill:#4a6a4a,stroke:#66bb6a,stroke-width:2px,color:#fff
    style G fill:#2a5a2a,stroke:#66bb6a,stroke-width:3px,color:#fff
Loading

Agent Operations

  • Agent Registration: Automatic agent registration after installation
  • Installation Scripts: Generate platform-specific scripts (Linux/macOS ARM/x86)
  • Script Download: One-click download of customized installation scripts
  • Agent Status: Real-time agent connectivity and health monitoring
  • Platform Detection: Automatic detection of OS and architecture
  • Last Seen: Track when agents last connected to backend (updated every 30s)
  • Batch Operations: Upgrade multiple agents simultaneously

Agent Logs & Monitoring

  • Log Location: /var/log/haproxy-agent/agent.log on each agent server
  • Log Format: Structured JSON for easy parsing and log aggregation
  • Log Fields: timestamp, level, message, agent_id, cluster_id, task_id
  • Activity Tracking: All agent activities (config updates, SSL changes, task execution)
  • Log Integration: JSON format allows easy integration with ELK, Splunk, Datadog
  • Log Rotation: Automatic rotation with configurable retention

Example Log Entry (JSON):

{
  "timestamp": "2025-01-15T10:30:45Z",
  "level": "INFO",
  "agent_id": "prod-haproxy-01",
  "cluster_id": 5,
  "task_id": 123,
  "message": "SSL certificate updated successfully",
  "details": {
    "cert_name": "wildcard.example.com",
    "action": "update",
    "haproxy_reload": "success"
  }
}

Dashboard - Main Overview & Real-time Monitoring

The dashboard displays comprehensive real-time metrics collected by agents from HAProxy stats socket. Agents use socat to query the stats socket every 30 seconds and send CSV data to the backend, which parses and stores it in Redis for high-performance access.

Tab-Based Navigation:

  1. Overview Tab:

    • System-wide metrics: Total requests, active sessions, error rate
    • Frontend/Backend/Server counts
    • SSL certificate status and expiration warnings
    • Quick health status indicators
  2. Frontends Tab:

    • Request rate per frontend with sparkline charts
    • Session distribution across frontends
    • Status (UP/DOWN) monitoring
    • Filter by specific frontends
  3. Backends Tab:

    • Backend health matrix (all servers at a glance)
    • Request distribution percentages
    • Response time averages
    • Active/backup server status
  4. Performance Trends Tab:

    • Response time trends (1h, 6h, 24h, 7d, 30d views)
    • Request rate trends with interactive zoom
    • Error rate analysis over time
    • Session trends and capacity planning
  5. Capacity & Load Tab:

    • Current vs max connections
    • Queue depth monitoring
    • CPU/Memory utilization (if available)
    • Load distribution heatmaps
  6. Health Matrix Tab:

    • Detailed server health status
    • Last status check timestamps
    • Check duration and latency
    • Failure reasons and diagnostics

Data Collection Flow:

  • Agents → Stats Socket (show stat) → CSV → Backend API → Redis → Dashboard UI
  • Agent Log Path: /var/log/haproxy-agent/agent.log (JSON format for easy log aggregation)
  • Update Interval: 30 seconds (configurable)
  • Data Retention: 30 days in Redis (configurable)

Frontend Management - Virtual Host & Routing Configuration

  • Frontend CRUD: Create, edit, delete, and duplicate frontend configurations
  • Protocol Support: HTTP, TCP, and health check mode configurations
  • Binding Configuration: IP address, port, and SSL binding options
  • Backend Assignment: Default backend selection and routing rules
  • SSL Configuration: Certificate assignment and SSL protocol settings
  • ACL Rules: Access control rules with pattern matching
  • Redirect Rules: HTTP to HTTPS redirects and custom redirections
  • Advanced Options: Connection limits, timeouts, and performance tuning
  • Search & Filter: Real-time search and status-based filtering

New Site Wizard - Guided Multi-Step Host Setup

The New Site Wizard is the recommended entry point for creating a complete proxied host. It collects every piece of configuration a host needs across a five-step guided form and creates the matching frontend + backend + servers + SSL binding as a single atomic apply.

  • 5-step guided flow:
    1. Domains - host header / SNI / wildcard validation, IDN/Punycode normalization, port + bind-address collision check
    2. Backend - pool name, load-balance algorithm, health check method/URI, cookie-based persistence (RFC 6265 and HAProxy-parser-safe character validation)
    3. Servers - per-server address/port/weight, optional CA bundle reference (cluster-RBAC enforced), backup-server + cookie-value validators
    4. SSL - three modes: existing certificate (cluster-RBAC), PEM upload (chain validation + SAN/CN match), or ACME order (account binding + preflight)
    5. Review + Dry-Run - live POST /api/sites/preview runs the proposed config through haproxy -c and surfaces every WARNING/ERROR with a marker comment that pinpoints the offending block before anything touches the database
  • Draft persistence: every step auto-saves to a server-side draft (30-day TTL, 50 drafts per user cap, cluster-scoped, PEM material stripped at rest)
  • Resume across sessions: drafts can be reopened from a different browser; cluster swap mid-wizard surfaces a confirmation prompt to prevent cross-cluster contamination
  • Atomic apply: frontend + backend + servers + SSL binding land as a single PENDING change reviewable from Apply Management - accept and apply, or reject as a whole
  • Cluster RBAC: every step honours user_pool_access and cluster RBAC; SSL certificate references are validated against the target cluster at both preview AND create time
  • Hardening: IDN/Punycode safe slug generation, cookie/header injection guards, path-traversal protection on SSL filenames, per-user-per-minute rate limit on create + preview, advisory locks against concurrent creation
  • Backwards compatible URLs: legacy /proxied-hosts/new route continues to resolve to the new wizard

Backend Servers - Server Pool Management

  • Server Management: Add, edit, remove, and configure backend servers
  • Health Checks: HTTP/TCP health check configuration and monitoring
  • Load Balancing: Round-robin, least-conn, source, and URI algorithms
  • Server Weights: Dynamic weight adjustment for load distribution
  • Maintenance Mode: Enable/disable servers without removing configuration
  • Backup Servers: Failover server configuration for high availability
  • Connection Limits: Per-server connection and rate limiting
  • Monitoring Dashboard: Real-time server status and performance metrics

Configuration - HAProxy Config File Management

  • Syntax Editor: Monaco editor with HAProxy syntax highlighting
  • Real-time Validation: Configuration syntax checking and error detection
  • Configuration Templates: Pre-built templates for common setups
  • Version Control: Configuration versioning and rollback capabilities
  • Backup & Restore: Automatic backups and manual restore functionality
  • Direct Editing: Raw HAProxy configuration file editing
  • Deployment: Apply configuration changes with validation
  • Help System: Integrated HAProxy directive documentation
  • Real-time Config Pull: Pull current haproxy.cfg from live HAProxy servers
    • View actual running configuration
    • Compare with managed configuration
    • Import existing configurations
    • Useful for migrating from manual to managed setup

Apply Management - Change Tracking & Deployment

Change Tracking & Versioning

Every configuration change creates a new version with complete tracking:

  • Automatic Versioning: Each apply creates a new version number (e.g., v1.0.123)
  • Version Snapshots: Complete configuration state saved for each version
  • Diff Visualization: See exactly what changed between versions
    • Green: Added lines
    • Red: Removed lines
    • Yellow: Modified lines
  • Change Summary: High-level summary of changes (added frontends, modified backends, etc.)
  • Version Metadata: Timestamp, user, cluster, affected entities

Deployment & Status

  • Change Tracking: Track all pending configuration changes before apply
  • Apply Status: Real-time deployment status per agent
  • Bulk Apply: Deploy changes to all agents in a cluster simultaneously
  • Success/Failure Tracking: Monitor which agents successfully applied changes
  • Retry Mechanism: Retry failed deployments per agent
  • Apply Logs: Detailed logs for each deployment operation with error messages

Version History & Restore

Version History Viewer:

  • View all historical configuration versions
  • Compare any two versions side-by-side
  • See who made changes and when
  • Filter by date, user, or cluster

Configuration Restore:

  • Restore any previous configuration version
  • One-click rollback to last known good configuration
  • Restore process:
    1. Select previous version from history
    2. Review diff (current vs target version)
    3. Confirm restore
    4. Agents pull restored configuration
    5. HAProxy reloaded with previous config

Use Cases:

  • Rollback after problematic deployment
  • Audit configuration changes
  • Compare production vs staging configurations
  • Disaster recovery

SSL Certificates - Centralized TLS/SSL Certificate Management

HAProxy OpenManager provides powerful centralized SSL/TLS certificate management with both global and cluster-specific scopes:

SSL Certificate Scopes

1. Global SSL Certificates:

  • Defined once, distributed to all clusters and all agents
  • Perfect for wildcard certificates or shared certificates
  • When updated, all agents across all clusters automatically receive the new certificate
  • Use case: *.example.com certificate used across all environments

2. Cluster-Specific SSL Certificates:

  • Scoped to a specific cluster only
  • Only agents in that cluster receive these certificates
  • Use case: Environment-specific certificates (prod, staging, dev)

Certificate Operations

Upload & Distribution:

  1. User uploads certificate via UI (PEM format: cert + private key)
  2. Backend stores certificate in database
  3. Agents poll backend and detect new/updated certificates
  4. Agents download certificates to local disk (/etc/haproxy/certs/)
  5. Agents update haproxy.cfg frontend bindings
  6. HAProxy is safely reloaded with zero downtime

Binding to Frontends:

  • SSL certificates are automatically bound to frontends based on configuration
  • Frontend definition specifies which certificate to use
  • Format: bind *:443 ssl crt /etc/haproxy/certs/cert-name.pem

Centralized SSL Update Process:

User Updates SSL in UI → All Agents Poll Backend (30s) 
  → Download New Certificate → Update Frontend Bindings 
  → Validate Config → Reload HAProxy (zero downtime)

CSR Workflow (external / corporate CAs) — v1.9.0

For certificates signed by an external or corporate CA, the CSR tab on the SSL Certificates page covers the whole flow without the private key ever leaving the server:

  1. Create CSR: pick a name (becomes the certificate name / on-agent file path), Common Name, optional SANs and subject fields (O/OU/L/ST/C/email), and a key algorithm (RSA 2048/4096 or ECDSA P-256/P-384). The backend generates the key + CSR; only the CSR PEM is shown (copy or download as .csr).
  2. Get it signed: submit the CSR to your Certificate Authority.
  3. Import: paste the signed certificate (+ optional chain), choose Global or cluster-specific scope and usage type. The backend verifies the certificate matches the stored key, rejects expired certs, warns on SAN drift, and creates a normal SSL certificate entry (source: CSR).
  4. Deploy: the imported certificate goes through the standard PENDING → Apply Management → agent pull pipeline like any other certificate.

Key Features

  • Certificate Upload: PEM format certificate and private key upload
  • ACME Automation: Automatic certificate issuance and renewal via Let's Encrypt / ACME protocol (see ACME Auto SSL)
  • Certificate Store: Centralized repository with encryption at rest
  • Expiration Monitoring: Automatic expiration tracking with alerts (30, 15, 7 days)
  • Global Update: Update one certificate, deploy to all clusters simultaneously
  • Domain Binding: Associate certificates with specific domains/frontends
  • Certificate Validation: Syntax and format validation before deployment
  • Renewal Tracking: Certificate renewal status and history
  • Source Tracking: Certificates display their source ("Manual" or "Auto (ACME)") for clear management
  • ACME Protection: ACME-managed certificates are protected from manual content edits to prevent accidental overwrites
  • Security Profiles: SSL/TLS protocol and cipher suite configuration
  • Zero Downtime: Safe HAProxy reload ensures no dropped connections

ACME Auto SSL - Automated Certificate Management

HAProxy OpenManager includes a built-in ACME client for automated SSL/TLS certificate management using the ACME protocol (RFC 8555). This enables zero-touch certificate issuance and renewal from Let's Encrypt and other ACME-compatible Certificate Authorities.

How ACME Auto SSL Works

sequenceDiagram
    autonumber
    participant U as Admin UI
    participant B as Backend API
    participant CA as ACME CA<br/>(Let's Encrypt)
    participant H as HAProxy<br/>(via Agent)

    Note over U,CA: === Certificate Request ===
    U->>B: Request certificate for example.com
    B->>CA: Create ACME order
    CA-->>B: Order + HTTP-01 challenge token
    B->>B: Store challenge token in DB

    Note over CA,H: === Domain Validation ===
    CA->>H: GET /.well-known/acme-challenge/{token}
    H->>B: Proxy challenge request (ACME ACL)
    B-->>H: Challenge response (key authorization)
    H-->>CA: Challenge response
    CA->>CA: Validate domain ownership

    Note over B,CA: === Certificate Issuance ===
    B->>CA: Finalize order (submit CSR)
    CA-->>B: Signed certificate
    B->>B: Store cert, status = PENDING
    B->>B: Create config version (PENDING)

    Note over U,B: === Manual Apply (New Certs) ===
    U->>B: Click "Apply Changes"
    B->>B: Mark APPLIED + consolidated config
    B->>H: Notify agents
    H->>H: Pull cert + reload HAProxy
Loading

Auto-Renewal Flow

Certificates are automatically renewed before expiration through a background task that runs hourly:

sequenceDiagram
    autonumber
    participant BG as Background Task<br/>(Hourly)
    participant B as Backend
    participant CA as ACME CA
    participant DB as Database
    participant H as HAProxy Agents

    BG->>DB: Find certs expiring within N days
    BG->>CA: Create renewal order
    CA-->>BG: Challenge token
    BG->>DB: Store challenge

    Note over BG,CA: Next cycle (or same)
    BG->>CA: Check order status
    CA-->>BG: Status: valid
    BG->>CA: Download renewed certificate

    Note over BG,DB: Auto-Apply (same as manual Apply)
    BG->>DB: Update cert (PENDING)
    BG->>DB: Create config versions (PENDING)
    BG->>DB: apply_ssl_related_configs()
    BG->>DB: Create consolidated version (APPLIED)
    BG->>DB: Mark cert APPLIED
    BG->>H: Notify agents via Redis
    H->>H: Pull updated cert + reload
Loading

Key behavior: Auto-renewed certificates follow the exact same Apply pipeline as manual SSL updates. The cert transitions PENDING -> APPLIED automatically, agents are notified, and cross-cluster propagation works identically to manual Apply. No separate deployment mechanism is used.

ACME Certificate Diagnostic Panel

Available from the ACME Automation list (Diagnose button on each order, or by clicking the order's status tag), the Diagnostic Panel runs a full preflight checklist before a certificate is issued or renewed and surfaces every blocker in a single operator-friendly view:

  • Agent reachability - verifies that at least one agent in each target cluster is online and within the last-seen window
  • DNS resolution - resolves every domain on the order (A / AAAA / CNAME) and flags wildcards and non-resolvable names with the exact upstream resolver error
  • Port 80 reachability - checks that the HTTP-01 challenge port is reachable from the public internet via the agent's egress path
  • ACME challenge ACL preview - renders the acl1 !acl1 + http-request return block that will be injected at apply time, so the operator can see exactly what HAProxy will receive
  • HSTS / rate-limit policy collision check - warns if existing frontend rules would short-circuit the challenge route
  • Account binding check - confirms a valid ACME account exists for the selected provider + cluster combination
  • Event Log timeline - merged view of typed acme_order_events rows and correlated user_activity_logs entries; auto-tails every 5s while the order is in pending/processing
  • Humanized error display - covers 11+ RFC8555 problem types (badNonce, caa, connection, rateLimited, unauthorized, ...) with full backwards compatibility for legacy plain-string error details
  • Operator-friendly error envelope - every failure returns { correlation_id, field, cause, remediation } so root-causing is one API call away

ACME Features

Feature Description
Certificate Request Wizard Step-by-step UI wizard: select domains, account, target clusters
Multi-Provider Support Let's Encrypt, ZeroSSL, Google Trust Services, Buypass, or custom ACME CA
Staging Mode Test with LE staging environment (no rate limits) before production
Account Management Register, view details, and deactivate ACME accounts
Order Tracking View all orders with status, domains, and challenge details
Auto-Renewal Configurable renewal window (default: 30 days before expiry)
Renewal Schedule Dashboard showing all ACME certs with expiry dates and renewal status
HTTP-01 Challenges Built-in challenge responder with automatic HAProxy ACL injection
EAB Support External Account Binding for CAs that require it
Certificate Protection ACME-managed certs are read-only in SSL Management (content cannot be manually edited)
CA Chain Import One-click import of CA intermediate certificates
Certificate Revocation Revoke compromised certificates through the ACME CA

ACME Configuration

ACME settings are managed through Settings > ACME / SSL Automation tab:

Setting Description Default
acme.provider ACME CA provider selection letsencrypt
acme.directory_url ACME directory endpoint URL LE production URL
acme.staging_mode Use staging environment false
acme.auto_renew_enabled Enable automatic certificate renewal false
acme.renew_before_days Days before expiry to trigger renewal 30
acme.eab_kid External Account Binding Key ID (if required) -
acme.eab_hmac_key External Account Binding HMAC Key (if required) -

Cluster ACME Setup

For HTTP-01 challenges to work, each cluster needs ACME challenge routing enabled:

  1. Go to Cluster Management > Edit cluster
  2. Enable ACME Challenge Routing toggle
  3. Optionally set a custom ACME backend URL
  4. Apply changes

When enabled, HAProxy configuration is automatically injected with:

  • An ACL matching /.well-known/acme-challenge/ requests
  • A use_backend rule directing challenge traffic to the management backend
  • A dedicated _acme_challenge_backend section

This injection only affects HTTP-mode frontends and is completely removed when ACME is disabled.

ACME Distributed Architecture

Understanding how HTTP-01 challenges work in a distributed HAProxy environment is critical for successful certificate issuance:

┌─────────────────┐    DNS A Record     ┌──────────────────┐
│  Let's Encrypt   │ ──────────────────► │  HAProxy Node(s) │
│  (CA Server)     │  HTTP GET :80       │  (VIP / Public)  │
└─────────────────┘  /.well-known/      └────────┬─────────┘
                      acme-challenge/             │
                      {token}                     │ ACL match →
                                                  │ use_backend
                                                  │ _acme_challenge_backend
                                                  ▼
                                         ┌──────────────────┐
                                         │  OpenManager      │
                                         │  (Backend Server) │
                                         │  Serves token     │
                                         │  response from DB │
                                         └──────────────────┘

Key Points:

  1. Let's Encrypt connects to your HAProxy, not directly to OpenManager
  2. DNS must resolve the requested domain to your HAProxy node's public IP (or VIP/NAT)
  3. Port 80 must be open from the internet to HAProxy for HTTP-01 validation
  4. HAProxy routes the /.well-known/acme-challenge/ path to OpenManager via the injected backend
  5. OpenManager serves the challenge token response stored in the database
  6. The MANAGEMENT_BASE_URL or acme.challenge_backend_url setting tells HAProxy where to find OpenManager

Network Scenarios:

Scenario HAProxy OpenManager DNS Target
Single server localhost:80 localhost:5000 Server's public IP
Separate servers 10.0.0.10:80 10.0.0.20:5000 HAProxy's public IP
Behind NAT/VIP VIP: 1.2.3.4:80 Internal:5000 VIP address
Multi-cluster Multiple HAProxy nodes Central OpenManager Each domain → respective HAProxy

DNS-01 Challenge — Internal/Isolated Clusters & Wildcards (v1.8.0 — Issue #35)

The default HTTP-01 challenge validates over port 80, so the domain must resolve publicly to an HAProxy node with ACME Challenge Routing enabled. DNS-01 validates via a DNS TXT record (_acme-challenge.<domain>) instead, so it needs no inbound port 80 and no public ingress to the HAProxy node. Use it for:

  • Internal / isolated clusters (behind a VPN/firewall, no public port 80) where you still control the domain's DNS.
  • Wildcard certificates (*.example.com) — which can only be issued via DNS-01.

DNS-01 is opt-in and fully backward compatible: it is disabled until an administrator enables it, and existing HTTP-01 certificates are completely unaffected.

  • Enable it: Settings → ACME / SSL Automation → DNS-01 Challenge (advanced) → turn on Enable DNS-01 Challenge and Save. While off, DNS-01 options are hidden and no DNS-01 orders can be created.
  • Per-account provider: in ACME Automation, create (or reconfigure) an ACME account with Challenge Method = DNS-01 and a DNS Provider. Provider credentials are verified before saving and encrypted at rest (Fernet, mirroring the VRRP/MFA secret pattern); they are never returned by the API or written to logs.
  • Supported providers: Manual (publish the TXT record yourself in any DNS — including fully internal DNS — then click Verify; works everywhere but cannot auto-renew unattended), Cloudflare (API token with Zone:DNS:Edit + Zone:Read; the TXT record is created and cleaned up automatically and renews unattended), and GoDaddy (v1.10.0) (a Production API Key + Secret pair from developer.godaddy.com/keys — the first key that dashboard issues is an OTE/test key and is rejected; the zone must be in the same GoDaddy account, which needs at least one registered domain before GoDaddy allows DNS API access at all. A Personal Access Token works too: paste it as the API Key and leave the Secret blank — that is the forward path as GoDaddy retires the sso-key scheme. TXT records are created and cleaned up automatically and renew unattended). The provider interface is pluggable — more providers can be added without changing the issuance flow.
  • Same pipeline: after validation the certificate follows the normal PENDING → APPLIED flow (assign to clusters / Apply Management) and the agent serves it — identical to HTTP-01 from finalize onward, with zero agent or rendered-config changes for DNS-01.
  • Manual flow: the order detail shows the exact _acme-challenge.<domain> record name + TXT value (copyable); publish it and click I've added the records — Verify. For Cloudflare and GoDaddy it is automatic.
  • Resilience: a propagation-lag failure is recovered by a bounded fresh-order retry chain (1 original + 3 retries with increasing backoff, kept under Let's Encrypt's rate limits); any orphaned TXT record is cleaned up by a reconcile sweep. The order detail shows a DNS-01 event timeline (publish → validation → cleanup).
  • Wildcards: *.example.com is validated at _acme-challenge.example.com; it does not cover the apex — add example.com as a separate name if you need both (the providers handle the two coexisting TXT values automatically).
  • Scope (this release): the Site Wizard remains HTTP-01-only; issue DNS-01 / wildcard certificates from ACME Automation.

ACME Quick Start Guide

Follow these steps to obtain your first Let's Encrypt certificate:

Step 1: Configure ACME Settings

  • Navigate to Settings > ACME / SSL Automation tab
  • Select your ACME provider (default: Let's Encrypt)
  • For testing, enable Staging Mode to avoid rate limits
  • Save changes

Step 2: Register an ACME Account

  • On the SSL Certificates > ACME Automation page, find the ACME Account card
  • Click Register Account and provide a valid email address
  • Accept the Terms of Service

Step 3: Enable ACME on Clusters

  • Go to Cluster Management > Edit your cluster
  • Enable ACME Challenge Routing toggle
  • Set the correct ACME Backend URL if OpenManager is on a different server
  • Save the cluster configuration

Step 4: Apply Configuration

  • Go to Apply Management and apply the pending HAProxy configuration changes
  • This injects the ACME challenge routing rules into HAProxy

Step 5: Verify Challenge Routing

Test that the challenge path is reachable through HAProxy:

curl -v http://your-haproxy-ip/.well-known/acme-challenge/test

Expected: HTTP 404 from OpenManager (not HAProxy's default 503). This confirms routing works.

Step 6: Ensure DNS Resolution

  • Your domain(s) must have DNS A/AAAA records pointing to your HAProxy node's public IP
  • Verify: dig +short yourdomain.com should return the HAProxy IP

Step 7: Request Certificate

  • Go to SSL Certificates > ACME Automation tab
  • Click New Certificate and follow the wizard
  • The UI will guide you through prerequisite checks before submission

Troubleshooting ACME

If certificate issuance fails, check the following:

Issue Diagnostic Step
Challenge 404 Check ACME-CHALLENGE log entries in OpenManager logs
DNS mismatch Verify dig +short yourdomain.com returns HAProxy IP
Port 80 blocked Test from external: curl http://yourdomain.com/.well-known/acme-challenge/test
Config not applied Check Apply Management for pending changes
ACME not enabled Verify cluster has ACME Challenge Routing enabled
Account issues Check ACME account status in ACME Automation page

Diagnostic Logging: OpenManager provides detailed ACME logging with prefixes:

  • ACME: — Order creation, challenge responses, finalization
  • ACME-CHALLENGE: — Incoming challenge token requests and responses

Review application logs to trace the complete ACME flow when troubleshooting issues.

WAF Management - Web Application Firewall

  • Rate Limiting: Request rate limiting by IP, URL, or custom patterns
  • IP Filtering: Whitelist/blacklist IP addresses and CIDR ranges
  • Geographic Blocking: Country-based access restrictions
  • DDoS Protection: Automated DDoS detection and mitigation rules
  • Custom Rules: User-defined security rules with regex patterns
  • Attack Monitoring: Real-time attack detection and logging
  • Rule Templates: Pre-configured security rule templates
  • Statistics Dashboard: WAF activity analytics and blocked request metrics

IP Inventory - Cross-Cluster IP Search & Discovery

The IP Inventory page provides a unified view of all IP addresses across every cluster, independent of the global cluster selector. It enables quick identification of which cluster, agent, or backend server a given IP belongs to.

Key Features:

  • Cross-Cluster Search: Search any IP address across all clusters simultaneously
  • IP Type Identification: Instantly determine if an IP is an agent server IP, a keepalived VIP, or a backend server address
  • Unified Results Card: Search results show matched IP type (Server IP, VIP, Backend), agent/server name, cluster association, and status
  • Agent Tab: Browse all agents with cluster name, hostname, agent IP, VIP (keepalive), VRRP state (MASTER/BACKUP), status, platform, and last seen time
  • Backend Servers Tab: Browse all backend servers with cluster, backend name, server address, status, weight, and check status
  • Cluster Color Coding: Each cluster is assigned a consistent color tag for quick visual identification
  • Auto-Refresh: Data refreshes automatically every 30 seconds
  • Filters: Filter by cluster and status within each tab

Use Cases:

  • Identify which HAProxy cluster an IP belongs to during incident response
  • Find which agent is running as MASTER or BACKUP in a keepalived pair
  • Locate a backend server IP across multiple clusters
  • Audit all IP addresses managed by the platform

HA / VIP Management - Keepalived Virtual IP Failover

The HA / VIP page manages highly-available virtual IPs backed by Keepalived (VRRP) directly from the UI — no SSHing into nodes to install/configure Keepalived by hand. It builds on the agent pull-architecture: you define the VIP centrally, click Apply, and the agents converge.

What it does:

  • VIP dashboard: lists each virtual IP with its pool, VRID, per-node interface, and a live MASTER / BACKUP / FAULT column per node (refreshed every 30s from the existing keepalive-state heartbeat pipeline).
  • Creation form: enter the virtual IP (+ prefix), pick the pool, and select which of the pool's HAProxy nodes participate — each with a network interface (from the node's reported interfaces), a VRRP role (MASTER/BACKUP) and a priority. VRID auto-allocates per pool; an optional VRRP secret can be set.
  • Hands-off automation: on Apply, each member's keepalived.conf is rendered and delivered; the agent installs Keepalived if missing, writes the config + a HAProxy health-check (vrrp_script + track_script), validates with keepalived -t, and starts the service. When HAProxy drops on the active node, the health-check lowers VRRP priority and the VIP fails over automatically to a backup.

How it works (pull-based, isolated):

  • New tables vip_instances + vip_members; the agent polls GET /api/agents/{name}/keepalived-config (key-authenticated, agent-bound) and converges. It is fully isolated from the global HAProxy apply flow — VIP changes never regenerate haproxy.cfg.
  • Cluster-driven path: the keepalived.conf location is a cluster setting (keepalived_config_path, default /etc/keepalived/keepalived.conf) delivered to the agent dynamically — just like the HAProxy paths — so non-standard installs are supported with zero per-node effort. The default is what the keepalived service loads on every distro; if you set a non-default path, ensure the keepalived unit is configured to load it.

Apply / Reject staging:

  • VIP edits are staged as PENDING and only go live on Apply, which records an applied snapshot. Reject discards pending changes and restores the previous applied state (a never-applied VIP is discarded). Editing a live VIP never disrupts it until you re-Apply.

Enterprise safety:

  • Opt-in & backward compatible — nodes/clusters without a VIP do nothing new.
  • Never clobbers a hand-managed Keepalived — if an unmanaged keepalived.conf exists, the agent reports "externally managed" and leaves it untouched.
  • Multi-distro install — Debian/Ubuntu, RHEL/CentOS/Alma/Rocky, Fedora, SUSE/openSUSE, Alpine (apt/dnf/yum/zypper/apk).
  • Reliable detection across platforms — MASTER/BACKUP is detected via systemd journal, syslog, and a portable interface-based check (exact-match, all VIPs).
  • Secure — the VRRP secret is encrypted at rest and never returned by the API; the delivery endpoint requires the node's own API key.
  • Scope — VRRP targets bare-metal / VMware / on-prem L2 networks; the UI clearly notes that AWS/Azure/GCP don't honor VRRP/gratuitous-ARP. macOS agents can't run Keepalived and are excluded (the UI warns if one is selected).

RBAC: gated by the vip permission group (vip.read/create/update/delete/apply); admins bypass.

User Management - Access Control & Authentication

  • User Accounts: Create, edit, and manage user accounts
  • Role-based Access: Admin, user, and custom role definitions
  • Permission System: Granular permissions for different system functions
  • User Activity: Login history and user action audit logs
  • Password Management: Secure password policies and reset functionality
  • Session Management: Active session monitoring and force logout
  • API Keys: User API key generation and management
  • Role Assignment: Dynamic role assignment and permission updates

Multi-Factor Authentication (MFA) — v1.6.0 (Issue #18)

MFA is optional per account and default OFF. Existing users keep their single-factor (username/password) login unless they choose to enable it. The feature is fully additive: nothing changes for accounts that don't opt in.

For end-users

  • Open Users → find your own row → click Enable MFA.
  • A wizard opens with three steps:
    1. Set up — scan the QR code with Google Authenticator / Authy / 1Password / Microsoft Authenticator, or paste the displayed secret manually.
    2. Verify — enter the current 6-digit code from your app.
    3. Backup codes — save the 10 single-use recovery codes (format XXXX-YYYY). They are shown only once. Use the Copy all / Download .txt buttons.
  • After enrollment your sign-in becomes two-step: username/password → 6-digit TOTP (or a backup code).
  • To turn MFA off again, open your row's Disable MFA action and enter a current TOTP or backup code.

For admins

  • On any user with MFA enabled, the Reset MFA action wipes the user's TOTP secret, backup codes, and pending challenges. A required reason is written to the audit log. After reset the user logs in with their password and may re-enroll.

Emergency: reset MFA for every user

Use the CLI helper when an authenticator outage / mass key loss happens. Double confirmation is required; the action is irreversible.

API_URL=https://hap.example.com ADMIN_TOKEN=eyJ... \
  ./scripts/admin-mfa-reset-all.sh
# Prompts ask for: 'yes' → 'RESET ALL MFA' → reason
# Audit log:      action='mfa.disabled.admin_bulk_reset'

Configuration

  • Backend env var MFA_ENCRYPTION_KEY — a 44-char Fernet key used to encrypt TOTP secrets at rest. Generate with:
    python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
    On Kubernetes the key lives in the backend-secret Secret (k8s/manifests/03-secrets.yaml). The shipped manifest uses the placeholder mfa_encryption_key_replace_me; replace it via your CI/CD pipeline (e.g. sed step) before kubectl apply.
  • Optional MFA_ACCOUNT_LABEL_DOMAIN — overrides the per-user otpauth label domain so QR codes show e.g. alice@hap.example.com instead of the request hostname.

API endpoints (all additive)

Method Path Notes
POST /api/auth/login Returns mfa_required:true+mfa_token for MFA users; legacy shape otherwise
POST /api/auth/login/mfa-verify Submits TOTP or backup code; returns JWT
GET /api/mfa/status Self status (enabled / method / backup codes remaining)
POST /api/mfa/enroll/start Begin TOTP enrollment
POST /api/mfa/enroll/confirm Confirm enrollment, returns 10 backup codes once
POST /api/mfa/disable Self-disable (TOTP or backup required)
POST /api/mfa/backup-codes/regenerate Issue 10 fresh backup codes (TOTP only)
GET /api/mfa/admin/status/{id} Admin: any user's MFA status
POST /api/mfa/admin-reset/{id} Admin: reset a single user's MFA
POST /api/mfa/admin-reset-all Admin: emergency reset for all users

Rate limits (per-user, ingress-aware, operationally tunable)

MFA endpoints are rate-limited via slowapi+Redis with a user-aware key function (backend/middleware/mfa_rate_limit_key.py):

  1. If the request carries a valid Bearer JWT → bucket is user:<id>. Each operator gets an isolated bucket; an org-wide MFA rollout is no longer bottlenecked by the shared ingress IP.
  2. Else, if the TCP peer is in MFA_TRUSTED_PROXY_CIDRS → bucket is the first X-Forwarded-For hop (real client IP behind the ingress).
  3. Else → bucket is the TCP peer (slowapi default).

Defaults live in backend/middleware/mfa_rate_limits.py and are sized for enterprise-scale rollouts (thousands of operators). Each one is overridable via env var; an invalid string logs a WARNING and falls back to the default without crashing.

Endpoint Env var Default Bucket
POST /api/mfa/enroll/start MFA_RATE_LIMIT_ENROLL_START 10/minute per user
POST /api/mfa/enroll/confirm MFA_RATE_LIMIT_ENROLL_CONFIRM 10/minute per user
POST /api/mfa/disable MFA_RATE_LIMIT_DISABLE 10/minute per user
POST /api/mfa/backup-codes/regenerate MFA_RATE_LIMIT_REGENERATE_BACKUP_CODES 5/hour per user
POST /api/mfa/admin-reset/{id} MFA_RATE_LIMIT_ADMIN_RESET 60/hour per admin
POST /api/mfa/admin-reset-all MFA_RATE_LIMIT_ADMIN_RESET_ALL 1/day per admin

Limit string format follows slowapi: <count>/<second|minute|hour|day>.

Trusted-proxy configuration MFA_TRUSTED_PROXY_CIDRS — comma-separated CIDR list, e.g. 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16. Empty (default) disables XFF parsing — XFF from any peer is then ignored, which is the safe choice when the topology is unknown. Set this when your backend sits behind a known ingress / load balancer so anonymous flows still get per-real-IP buckets.

The pre-existing /api/auth/login rate-limiting policy is unchanged. On Kubernetes, see commented overrides in k8s/manifests/07-configmaps.yaml::backend-config.

Settings - System Configuration

  • Theme Settings: Light/dark mode toggle and UI customization
  • ACME / SSL Automation: Configure ACME provider, directory URL, staging mode, auto-renewal, EAB credentials, and test CA connectivity
  • Notification Settings: Alert preferences and notification channels
  • System Preferences: Default timeouts, refresh intervals, and limits
  • Backup Settings: Automated backup scheduling and retention policies
  • Integration Settings: External system integrations and webhooks
  • Language Settings: Multi-language support configuration
  • Performance Tuning: System performance and optimization settings

Getting Started - First-Time Usage

Quick Start for New Users

This guide walks you through using HAProxy OpenManager for the first time, including importing your existing HAProxy configuration.

Setup Flow Diagram

graph TD
    Start([🚀 Start Setup]) --> Step1[1️⃣ Create Agent Pool<br/>HAProxy Agent Pool Management]
    Step1 --> Step2[2️⃣ Create Cluster<br/>HAProxy Cluster Management]
    Step2 --> Step3[3️⃣ Generate Agent Script<br/>Agent Management]
    Step3 --> Step4[4️⃣ Run Script on HAProxy Server<br/>SSH to server and execute]
    Step4 --> Step5[5️⃣ Copy Backup Config<br/>haproxy.cfg.initial-backup]
    Step5 --> Step6[6️⃣ Import Configuration<br/>Bulk HAProxy Config Import]
    Step6 --> Step7[7️⃣ Apply Changes<br/>Apply Management]
    Step7 --> Complete([✅ Setup Complete!])
    
    style Start fill:#2d5016,stroke:#4caf50,stroke-width:3px,color:#fff
    style Step1 fill:#7b4d1e,stroke:#ff9800,stroke-width:2px,color:#fff
    style Step2 fill:#7b4d1e,stroke:#ff9800,stroke-width:2px,color:#fff
    style Step3 fill:#1a4971,stroke:#2196f3,stroke-width:2px,color:#fff
    style Step4 fill:#4a1a4a,stroke:#9c27b0,stroke-width:2px,color:#fff
    style Step5 fill:#6b1e3f,stroke:#e91e63,stroke-width:2px,color:#fff
    style Step6 fill:#1a4d43,stroke:#009688,stroke-width:2px,color:#fff
    style Step7 fill:#6b5d1a,stroke:#fbc02d,stroke-width:2px,color:#fff
    style Complete fill:#2d5016,stroke:#4caf50,stroke-width:3px,color:#fff
Loading

Detailed Step-by-Step Instructions

Step 1: Create Agent Pool 📦

Navigate to HAProxy Agent Pool Management page.

  1. Click "Create Pool" button
  2. Fill in pool details:
    • Pool Name: Production Pool (or your preferred name)
    • Description: Production environment HAProxy servers
  3. Click Save

Purpose: Pools logically group your HAProxy agents for better organization.


Step 2: Create Cluster 🎯

Navigate to HAProxy Cluster Management page.

  1. Click "Create Cluster" button
  2. Fill in cluster details:
    • Cluster Name: Production Cluster
    • Description: Main production HAProxy cluster
    • Select Pool: Choose the pool created in Step 1 (Production Pool)
  3. Click Save

Purpose: A cluster represents a logical grouping of HAProxy configurations that will be deployed to agents.


Step 3: Generate Agent Installation Script 🤖

Navigate to Agent Management page.

  1. Click "Create Agent" or "Generate Script" button
  2. Configure agent details:
    • Agent Name: haproxy-prod-01 (unique identifier)
    • Select Pool: Choose the same pool from Step 1 (Production Pool)
    • Platform: Select Linux or macOS
    • Architecture: Select x86_64 or ARM64
  3. Click "Generate Install Script"
  4. Click "Download Script" to save the installation script

Purpose: This generates a customized installation script that will connect your HAProxy server to the management backend.


Step 4: Install Agent on HAProxy Server 🖥️

SSH into your HAProxy server and run the installation script.

# Copy the script to your HAProxy server
scp install-agent-haproxy-prod-01.sh root@your-haproxy-server:/tmp/

# SSH to the HAProxy server
ssh root@your-haproxy-server

# Make the script executable
chmod +x /tmp/install-agent-haproxy-prod-01.sh

# Run the installation script
sudo /tmp/install-agent-haproxy-prod-01.sh

What the script does:

  • Installs required dependencies (Python, socat)
  • Creates haproxy-agent systemd service
  • Starts the agent (polls backend every 30 seconds)
  • Creates initial backup: /etc/haproxy/haproxy.cfg.initial-backup
  • Registers the agent with the backend

Verify installation:

# Check agent service status
sudo systemctl status haproxy-agent

# Check agent logs
sudo tail -f /var/log/haproxy-agent/agent.log

Return to the UI and verify the agent appears in Agent Management with status "Connected ✅".


Step 5: Copy Existing HAProxy Configuration 📄

You have three options to get your existing HAProxy configuration:

Option A: Using the Initial Backup (Recommended)

On your HAProxy server, the agent created a backup of your original configuration.

# Display the backup configuration
cat /etc/haproxy/haproxy.cfg.initial-backup

Copy the entire contents of this file to your clipboard.


Option B: View Configuration via UI (Easiest)

Navigate to Configuration Management page in the UI.

  1. Select your agent from the Agent Selector dropdown
  2. Click "View Configuration" button
  3. The system will fetch the live configuration from the agent
  4. Copy the entire configuration displayed in the viewer
  5. You now have the configuration ready for import!

Benefits: No need to SSH to the server, get configuration directly from the UI.


Option C: Current Configuration File

If the backup doesn't exist, SSH to your server and copy the current configuration:

# Display current HAProxy configuration
cat /etc/haproxy/haproxy.cfg

Copy the entire contents to your clipboard.


Step 6: Import Configuration via Bulk Import 📥

Navigate to Bulk HAProxy Config Import page.

  1. Select your cluster from the Cluster Selector dropdown (top of page)
  2. Paste the configuration copied from Step 5 into the text area
  3. Click "Parse Configuration" button
  4. Review the parsed entities:
    • Frontends detected
    • Backends detected
    • Servers detected
    • SSL certificates (if any)
  5. Click "Import Configuration"

What happens:

  • The system parses your HAProxy config file
  • Extracts frontends, backends, servers, and SSL bindings
  • Creates database entities for each component
  • Prepares configuration for deployment

Result: Your existing HAProxy configuration is now managed by HAProxy OpenManager! 🎉


Step 7: Apply Changes to Agent 🚀

Navigate to Apply Management page.

  1. You'll see pending changes listed:
    • Frontends to be created
    • Backends to be created
    • Servers to be added
  2. Review the changes
  3. Click "Apply Changes" button
  4. Monitor deployment status:
    Agent: haproxy-prod-01
    Status: ✅ Configuration applied successfully
    Version: v1.0.1
    

What happens:

  • Backend marks configuration tasks as ready for deployment
  • Agent polls and detects pending tasks (within 30 seconds)
  • Agent downloads the new configuration
  • Agent validates HAProxy configuration syntax
  • Agent reloads HAProxy service (zero downtime)
  • Agent reports success back to backend

Verify deployment:

  • Check that agent status shows "Applied Successfully"
  • Check HAProxy is running: systemctl status haproxy
  • Check HAProxy stats: http://your-server:8404/stats

✅ Setup Complete!

Your HAProxy server is now fully managed by HAProxy OpenManager. You can now:

  • Manage Frontends: Add, edit, delete frontends via UI
  • 🖥️ Manage Backends: Configure backend servers and health checks
  • 🔒 Upload SSL Certificates: Centralized SSL management
  • 🛡️ Configure WAF Rules: Add security rules
  • 📊 Monitor Performance: View real-time statistics on dashboard
  • 🔄 Version Control: Track all configuration changes with rollback capability

Next Steps:

  • Add more agents to the same pool for multi-node clusters
  • Configure SSL certificates in SSL Management
  • Set up ACME Auto SSL in SSL Management > ACME Automation for automated Let's Encrypt certificates
  • Add WAF rules in WAF Management
  • Monitor your cluster in Dashboard

Installation

Choose your preferred installation method:

Method Best For Guide
Docker Quick setup, single-server, Docker Compose Docker Installation
Kubernetes/OpenShift Production, HA, enterprise Kubernetes Installation
Standalone Bare-metal, VMs, LXC, no containers Standalone Guide

Docker Installation

Prerequisites

  • Docker & Docker Compose (v20.10+)
  • Git
  • 2GB+ RAM for all services

Installation Steps

  1. Clone the Repository

    git clone https://github.com/taylanbakircioglu/haproxy-openmanager.git
    cd haproxy-openmanager
  2. Start All Services

    docker-compose up -d
  3. Verify Installation

    # Check all services are running
    docker-compose ps
    
    # Check API health
    curl http://localhost:8080/api/clusters
  4. Access the Application

Default Credentials

  • Admin User: admin / admin123
  • Regular User: user / user123

⚠️ Security Note: Change default passwords immediately in production environments.

Services Overview

The application consists of 8 Docker containers:

# Core Application Services
haproxy-openmanager-frontend    # React.js web interface
haproxy-openmanager-backend     # FastAPI backend
haproxy-openmanager-db          # PostgreSQL database  
haproxy-openmanager-redis       # Redis cache
haproxy-openmanager-nginx       # Nginx reverse proxy

# HAProxy Test Instances
haproxy-instance       # Local test instance
haproxy-remote1        # Remote test instance 1
haproxy-remote2        # Remote test instance 2

Management Commands

# Start all services
docker-compose up -d

# View logs
docker-compose logs -f [service_name]

# Stop all services
docker-compose down

# Rebuild and restart
docker-compose down && docker-compose up --build -d

# Database reset (⚠️ Destroys all data)
docker-compose down
docker volume rm haproxy-openmanager_postgres_data
docker-compose up -d

Volume Management

# List volumes
docker volume ls | grep haproxy

# Backup database
docker exec haproxy-openmanager-db pg_dump -U haproxy_user haproxy_openmanager > backup.sql

# Restore database
docker exec -i haproxy-openmanager-db psql -U haproxy_user haproxy_openmanager < backup.sql

Kubernetes/OpenShift Installation

Kubernetes Deployment

For Kubernetes environments, use the provided manifests:

# Deploy to Kubernetes
kubectl apply -f k8s/manifests/

# Check deployment status
kubectl get pods -n haproxy-openmanager

# Access via port-forward
kubectl port-forward svc/nginx-service 8080:80 -n haproxy-openmanager

See k8s/manifests/README.md for detailed Kubernetes setup instructions.

Local Development

Local Development Setup

  1. Backend Development

    cd backend
    python -m venv venv
    source venv/bin/activate  # Linux/Mac
    # or
    venv\Scripts\activate     # Windows
    
    pip install -r requirements.txt
    uvicorn main:app --reload --host 0.0.0.0 --port 8000
  2. Frontend Development

    cd frontend
    npm install
    npm start
  3. Database Setup

    # Start only database and redis
    docker-compose up -d postgres redis
    
    # Run migrations
    python backend/migration.py

Configuration

Environment Variables

All configuration is managed through environment variables for maximum flexibility across different deployment environments.

📄 Configuration Template: .env.template

# 1. Copy the template
cp .env.template .env

# 2. Edit (replace placeholders with your actual values)
nano .env

# 3. Start
docker-compose up -d

Important:

  • .env.template is only a template (committed to git)
  • .env is the actual configuration (NOT committed - in .gitignore)
  • When making changes, edit .env, not .env.template!

Backend Configuration

# Database
DATABASE_URL="postgresql://haproxy_user:haproxy_pass@postgres:5432/haproxy_openmanager"
REDIS_URL="redis://redis:6379"

# Security
SECRET_KEY="your-secret-key-change-this-in-production"
DEBUG="False"  # Set to False in production

# Public URL Configuration (IMPORTANT for agent connectivity)
# This URL is embedded in agent installation scripts
PUBLIC_URL="https://your-haproxy-openmanager.example.com"
MANAGEMENT_BASE_URL="${PUBLIC_URL}"  # Optional, defaults to PUBLIC_URL

# Examples:
#   Development:  PUBLIC_URL="http://localhost:8000"
#   Production:   PUBLIC_URL="https://haproxy-openmanager.example.com"
#   Production:   PUBLIC_URL="https://haproxy-openmanager.yourdomain.com"

# Logging
LOG_LEVEL="INFO"  # DEBUG, INFO, WARNING, ERROR, CRITICAL

# Agent Settings
AGENT_HEARTBEAT_TIMEOUT_SECONDS=15
AGENT_CONFIG_SYNC_INTERVAL_SECONDS=30

Frontend Configuration

The frontend is a Create-React-App single-page app served as a static bundle (serve -s build). It uses same-origin (window.location.host) for all /api/* calls — no env vars are needed in production. Routing is handled entirely by the nginx reverse proxy in front of the frontend pod (Kubernetes ingress + nginx-config ConfigMap, or nginx/nginx.conf for Docker Compose).

⚠️ Do NOT set REACT_APP_API_URL in your CI/CD pipeline. CRA inlines REACT_APP_* values into the bundle at build time, so any value baked in there overrides the runtime same-origin detection and breaks every deployment whose URL does not match the inlined string. Leave the variable unset; the bundle will resolve to whatever host the user is browsing.

# Optional, only when you intentionally need a cross-origin API
# (then CORS_ORIGINS on the backend must include the SPA's origin):
# REACT_APP_API_URL="https://api.example.com"

# Build settings
NODE_ENV="production"
GENERATE_SOURCEMAP="false"

Configuration Priority

  1. Development: Use .env file in project root
  2. Docker Compose: Environment variables in docker-compose.yml
  3. Kubernetes/OpenShift: ConfigMaps (k8s/manifests/07-configmaps.yaml)

Why Parametric URLs?

All URLs are configurable via environment variables for enterprise deployment flexibility:

  • No hardcoded URLs in source code
  • Deploy anywhere: Development, staging, production
  • Multi-tenant support: Each instance can have its own URL
  • Easy migration: Change deployment location without code changes

HAProxy Cluster Configuration

Add new HAProxy clusters through the web interface or directly via API:

{
  "name": "Production Cluster",
  "description": "Production HAProxy cluster",
  "host": "10.0.1.100",
  "port": 8404,
  "connection_type": "ssh",
  "ssh_username": "haproxy",
  "installation_type": "existing",
  "deployment_type": "cluster",
  "cluster_nodes": ["10.0.1.101", "10.0.1.102"],
  "keepalive_ip": "10.0.1.10"
}

Performance Tuning (v1.8.6)

The backend API defaults to a single uvicorn worker process, which uses one CPU core. Agents poll the API every 30 seconds (heartbeat, config, pending-requests, upgrade checks), so larger fleets add a constant baseline load. Two ways to scale:

  • Docker Compose — worker processes: set UVICORN_WORKERS in your .env (default 1). On a multi-core host, matching the core count (e.g. UVICORN_WORKERS=2 on a 2-core machine) lets the API use all cores:
    echo "UVICORN_WORKERS=2" >> .env && docker-compose up -d backend
  • Kubernetes/OpenShift — replicas: the shipped manifests already include an HPA for the backend (2→10 replicas, k8s/manifests/13-hpa.yaml); raise minReplicas/maxReplicas as needed.

Both are safe: all background tasks (ACME completion, renewals, agent monitoring) are multi-replica safe by design (atomic claims via FOR UPDATE SKIP LOCKED, PostgreSQL advisory locks).

Diagnosing slow requests: every API response carries an X-Response-Time header, and the backend logs Slow request detected (WARNING) for any request taking longer than 1 second — check those log lines to pinpoint slow endpoints before tuning anything else.

API Reference

Authentication

# Login and get JWT token
POST /api/auth/login
Content-Type: application/json
{
  "username": "admin",
  "password": "admin123"
}

Response:
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer"
}

# Get current user
GET /api/auth/me
Authorization: Bearer <access_token>

Pool Management

# List all pools
GET /api/pools
Authorization: Bearer <token>

# Create new pool
POST /api/pools
{
  "name": "Production Pool",
  "description": "Production environment pool"
}

# Get pool details
GET /api/pools/{pool_id}

Agent Management

# List all agents
GET /api/agents
Authorization: Bearer <token>

# Create agent and get installation script
POST /api/agents
{
  "name": "HAProxy-Prod-01",
  "pool_id": 1,
  "platform": "linux",  # linux or macos
  "architecture": "x86_64"  # x86_64 or arm64
}

# Agent polls for tasks (called by agent service)
GET /api/agents/{agent_id}/tasks
Authorization: Bearer <agent_token>

# Agent reports task completion
POST /api/agents/tasks/{task_id}/complete
{
  "status": "success",
  "message": "Configuration applied successfully",
  "logs": "HAProxy reloaded"
}

# Get agent status
GET /api/agents/{agent_id}/status

Cluster Management

# List all clusters
GET /api/clusters
Authorization: Bearer <token>

# Create new cluster
POST /api/clusters
{
  "name": "Production Cluster",
  "description": "Production HAProxy cluster",
  "pool_id": 1
}

# Get cluster details
GET /api/clusters/{cluster_id}

Statistics & Monitoring

# Get dashboard overview
GET /api/dashboard/overview

# Get HAProxy statistics
GET /api/haproxy/stats?cluster_id=1

# Get backend server status
GET /api/backends?cluster_id=1

# Get frontend configurations
GET /api/frontends?cluster_id=1

SSL CSR API (v1.9.0)

# Create a CSR (generates the private key server-side; response contains the
# CSR PEM — the private key is never returned by any endpoint)
POST /api/ssl/csrs
Authorization: Bearer <token>
{
  "name": "www-example-com",
  "common_name": "www.example.com",
  "sans": ["api.example.com"],
  "key_algorithm": "rsa-2048",   # rsa-2048 | rsa-4096 | ecdsa-p256 | ecdsa-p384
  "organization": "Example Corp",
  "country": "TR"
}

# List CSRs (metadata only, no PEM)
GET /api/ssl/csrs

# CSR detail (includes the CSR PEM)
GET /api/ssl/csrs/{csr_id}

# Import the CA-signed certificate for a pending CSR
POST /api/ssl/csrs/{csr_id}/import
{
  "certificate_content": "-----BEGIN CERTIFICATE-----...",
  "chain_content": "-----BEGIN CERTIFICATE-----...",   # optional
  "usage_type": "frontend",                            # frontend | server
  "is_global": false,
  "cluster_ids": [1, 2]
}

# Delete a CSR (pending: permanently destroys the private key;
# completed: removes history only — the imported certificate is unaffected)
DELETE /api/ssl/csrs/{csr_id}

ACME / Let's Encrypt API

# List ACME accounts
GET /api/letsencrypt/accounts
Authorization: Bearer <token>

# Register ACME account
POST /api/letsencrypt/accounts
{
  "email": "admin@example.com",
  "directory_url": "https://acme-v02.api.letsencrypt.org/directory",
  "tos_agreed": true
}

# Deactivate ACME account
DELETE /api/letsencrypt/accounts/{account_id}

# Request certificate
POST /api/letsencrypt/certificates
{
  "domains": ["example.com", "www.example.com"],
  "account_id": 1,
  "cluster_ids": [1, 2]
}

# List ACME orders
GET /api/letsencrypt/orders

# Get order details
GET /api/letsencrypt/orders/{order_id}

# Retry/resume order
POST /api/letsencrypt/orders/{order_id}/retry

# Cancel order
DELETE /api/letsencrypt/orders/{order_id}

# Renew certificate (create new order)
POST /api/letsencrypt/orders/{order_id}/renew

# Revoke certificate
POST /api/letsencrypt/certificates/{cert_id}/revoke

# Import CA chain certificates
POST /api/letsencrypt/import-ca-chain

# Get renewal schedule
GET /api/letsencrypt/renewal-schedule

# Check ACME prerequisites (setup status)
GET /api/letsencrypt/prerequisites

# ACME Settings
GET /api/settings/acme
PUT /api/settings/acme
GET /api/settings/acme/test-connection?directory_url=https://...

Configuration Management

# Get HAProxy configuration
GET /api/haproxy/config?cluster_id=1

# Update configuration
PUT /api/haproxy/config?cluster_id=1
Content-Type: text/plain

# Validate configuration
POST /api/haproxy/config/validate

Development

Project Structure

haproxy-openmanager/
├── backend/                          # FastAPI backend application
│   ├── main.py                      # Main application entry point
│   ├── config.py                    # Application configuration
│   ├── auth_middleware.py           # Authentication middleware
│   ├── agent_notifications.py       # Agent notification system
│   ├── database/                    # Database layer
│   │   ├── connection.py           # Database connection management
│   │   └── migrations.py           # Database migration scripts
│   ├── models/                      # SQLAlchemy ORM models
│   │   ├── user.py                 # User model
│   │   ├── cluster.py              # Cluster model
│   │   ├── agent.py                # Agent model
│   │   ├── frontend.py             # Frontend configuration model
│   │   ├── backend.py              # Backend configuration model
│   │   ├── ssl.py                  # SSL certificate model
│   │   └── waf.py                  # WAF rules model
│   ├── routers/                     # API route handlers
│   │   ├── auth.py                 # Authentication endpoints
│   │   ├── user.py                 # User management
│   │   ├── cluster.py              # Cluster management
│   │   ├── agent.py                # Agent management
│   │   ├── frontend.py             # Frontend configuration
│   │   ├── backend.py              # Backend configuration
│   │   ├── ssl.py                  # SSL certificate management
│   │   ├── letsencrypt.py          # ACME/Let's Encrypt endpoints
│   │   ├── settings.py             # System settings (incl. ACME config)
│   │   ├── waf.py                  # WAF rules management
│   │   ├── dashboard.py            # Dashboard statistics
│   │   ├── configuration.py        # Configuration viewer
│   │   └── security.py             # Security & token management
│   ├── services/                    # Business logic services
│   │   ├── haproxy_config.py       # HAProxy config generation
│   │   ├── acme_service.py         # ACME protocol client (RFC 8555)
│   │   └── dashboard_stats_service.py # Stats aggregation
│   ├── middleware/                  # Custom middleware
│   │   ├── activity_logger.py      # Activity logging
│   │   ├── error_handler.py        # Error handling
│   │   └── rate_limiter.py         # API rate limiting
│   ├── utils/                       # Utility functions
│   │   ├── auth.py                 # Authentication utilities
│   │   ├── haproxy_config_parser.py # Config parser
│   │   ├── haproxy_stats_parser.py  # Stats parser
│   │   ├── haproxy_validator.py     # Config validator
│   │   ├── ssl_parser.py            # SSL certificate parser
│   │   ├── activity_log.py          # Activity log utilities
│   │   ├── logging_config.py        # Logging configuration
│   │   └── agent_scripts/           # Agent installation scripts
│   │       ├── linux_install.sh     # Linux agent installer
│   │       └── macos_install.sh     # macOS agent installer
│   ├── tests/                       # Backend tests
│   │   ├── conftest.py             # Pytest configuration
│   │   └── test_*.py               # Test files
│   ├── requirements.txt             # Python dependencies
│   └── Dockerfile                   # Backend Docker image
│
├── frontend/                         # React.js frontend application
│   ├── src/
│   │   ├── components/              # React components
│   │   │   ├── Login.js            # Login page
│   │   │   ├── DashboardV2.js      # Main dashboard
│   │   │   ├── AgentManagement.js  # Agent management
│   │   │   ├── ClusterManagement.js # Cluster management
│   │   │   ├── PoolManagement.js   # Pool management
│   │   │   ├── FrontendManagement.js # Frontend config
│   │   │   ├── BackendServers.js   # Backend config
│   │   │   ├── SSLManagement.js    # SSL certificates
│   │   │   ├── ACMEAutomation.js   # ACME Auto SSL UI
│   │   │   ├── WAFManagement.js    # WAF rules
│   │   │   ├── ApplyManagement.js  # Apply changes
│   │   │   ├── Configuration.js    # Config viewer
│   │   │   ├── BulkConfigImport.js # Bulk import
│   │   │   ├── IPInventory.js     # IP inventory & search
│   │   │   ├── VersionHistory.js   # Version history
│   │   │   ├── UserManagement.js   # User management
│   │   │   ├── Security.js         # Security & tokens
│   │   │   ├── Settings.js         # Settings page
│   │   │   ├── EntitySyncStatus.js # Sync status
│   │   │   └── dashboard/          # Dashboard components
│   │   │       └── *.js            # Dashboard tabs & charts
│   │   ├── contexts/                # React contexts
│   │   │   ├── AuthContext.js      # Authentication context
│   │   │   ├── ClusterContext.js   # Cluster selection context
│   │   │   ├── ThemeContext.js     # Theme (dark/light mode)
│   │   │   └── ProgressContext.js  # Global progress indicator
│   │   ├── utils/                   # Utility functions
│   │   │   ├── api.js              # API client
│   │   │   ├── agentSync.js        # Agent sync utilities
│   │   │   ├── colors.js           # Color utilities
│   │   │   └── dashboardCache.js   # Dashboard caching
│   │   ├── App.js                   # Main app component
│   │   ├── App.css                  # Global styles
│   │   └── index.js                 # React entry point
│   ├── public/
│   │   └── index.html               # HTML template
│   ├── package.json                 # Node dependencies
│   └── Dockerfile                   # Frontend Docker image
│
├── docs/                             # Documentation
│   └── screenshots/                 # UI screenshots
│       ├── dashboard-overview.png
│       ├── frontend-management.png
│       ├── backend-configuration.png
│       ├── bulk-import.png
│       └── *.png                    # Other screenshots
│
├── k8s/                              # Kubernetes/OpenShift manifests
│   └── manifests/
│       ├── 00-namespace.yaml        # Namespace
│       ├── 01-service-accounts.yaml # Service accounts
│       ├── 02-rbac.yaml             # RBAC roles
│       ├── 03-secrets.yaml          # Secrets
│       ├── 04-storage.yaml          # Persistent volumes
│       ├── 05-postgres.yaml         # PostgreSQL deployment
│       ├── 06-redis.yaml            # Redis deployment
│       ├── 07-configmaps.yaml       # ConfigMaps
│       ├── 08-backend.yaml          # Backend deployment
│       ├── 09-frontend.yaml         # Frontend deployment
│       ├── 10-nginx.yaml            # Nginx deployment
│       ├── 11-routes.yaml           # OpenShift routes
│       ├── 12-ingress.yaml          # Kubernetes ingress
│       ├── 13-hpa.yaml              # Horizontal Pod Autoscaler
│       ├── deploy.sh                # Deployment script
│       └── cleanup.sh               # Cleanup script
│
├── nginx/                            # Nginx reverse proxy
│   └── nginx.conf                   # Nginx configuration
│
├── haproxy/                          # Sample HAProxy configs
│   ├── haproxy.cfg                  # Sample configuration
│   ├── haproxy-simple.cfg           # Simple example
│   ├── haproxy-remote1.cfg          # Remote example 1
│   └── haproxy-remote2.cfg          # Remote example 2
│
├── scripts/                          # Utility scripts
│   ├── check-agent-logs.sh          # Check agent logs
│   ├── check-agent-stats.sh         # Check agent stats
│   ├── check-haproxy-stats-socket.sh # Check stats socket
│   ├── cleanup-cluster-entities.sh  # Cleanup entities
│   ├── cleanup-soft-deleted.sh      # Cleanup soft deletes
│   ├── update-agent-version.sh      # Update agent version
│   └── test-stats-parser.py         # Test stats parser
│
├── utils/                            # Uninstall utilities
│   ├── uninstall-agent-linux.sh     # Linux uninstaller
│   └── uninstall-agent-macos.sh     # macOS uninstaller
│
├── docker-compose.yml                # Docker Compose configuration
├── docker-compose.localtest.yml      # Local development/testing overrides
├── docker-compose.test.yml           # Test environment
├── backend/version.json              # Application version metadata (single source of truth)
├── build-images.sh                   # Build Docker images
├── pytest.ini                        # Pytest configuration
├── README.md                         # This file
├── CONFIG.md                         # Configuration documentation
├── TESTING.md                        # Testing documentation
├── UPGRADE_GUIDE.md                  # Upgrade guide
└── LICENSE                           # MIT License

Adding New Features

  1. Backend API Endpoints: Add to backend/main.py
  2. Frontend Components: Add to frontend/src/components/
  3. Database Changes: Update backend/init.sql and create migration
  4. HAProxy Features: Extend backend/haproxy_client.py

Troubleshooting

Common Issues

1. Connection Refused to HAProxy

# Check if HAProxy is running
curl http://localhost:8404/stats

# Check HAProxy configuration
docker exec haproxy-instance haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg

# View HAProxy logs
docker logs haproxy-instance

2. Database Connection Issues

# Check database status
docker exec haproxy-openmanager-db pg_isready -U haproxy_user

# Check database logs
docker logs haproxy-openmanager-db

# Reset database
docker-compose down
docker volume rm haproxy-openmanager_postgres_data
docker-compose up -d

3. SSH Connection Issues

# Test SSH connectivity
ssh -i ~/.ssh/id_rsa user@remote-server

# Check SSH key permissions
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub

# Verify SSH service
systemctl status ssh  # on remote server

4. Frontend Build Issues

# Clear node modules and reinstall
cd frontend
rm -rf node_modules package-lock.json
npm install

# Check for port conflicts
lsof -i :3000

5. Phantom Pending Changes / Orphan Config Versions

Symptom: Apply Management shows pending changes that can't be applied or rejected, or shows entities from wrong cluster.

Root Cause: Orphan config versions - versions that reference entities from different clusters or deleted entities (entity ID reuse bug).

Solution (Automatic):

  • Modern versions (v1.1.0+): Orphan versions are automatically detected and cleaned when you:
    • Open Apply Management page (orphans filtered out from display)
    • Click "Apply Changes" (orphans deleted before apply)
    • Click "Reject All" (orphans deleted before reject)

Manual Cleanup (if needed):

-- Connect to database
kubectl exec -it <postgres-pod> -- psql -U haproxy_user -d haproxy_openmanager

-- Find orphan versions (example for cluster_id=2)
SELECT cv.id, cv.version_name, cv.cluster_id,
       CASE 
         WHEN cv.version_name ~ 'backend-[0-9]+-' 
         THEN (SELECT cluster_id FROM backends WHERE id = SUBSTRING(cv.version_name FROM 'backend-([0-9]+)-')::int)
         ELSE NULL
       END as entity_cluster
FROM config_versions cv
WHERE cv.cluster_id = 2 AND cv.status = 'PENDING';

-- Delete orphan versions (where entity_cluster != 2 or NULL)
DELETE FROM config_versions 
WHERE id IN (SELECT id FROM ...);

Prevention:

  • Always delete backends/frontends in the correct cluster
  • Use Apply Changes workflow (don't skip)
  • Keep backend/frontend names unique across clusters

6. Apply Changes Stuck - HAProxy Configuration Validation Errors

Symptom: Pending changes in Apply Management remain stuck and are not applied by agents, or agents report configuration failures.

Root Cause: The generated HAProxy configuration has syntax errors or validation issues that prevent HAProxy from accepting the new config.

Diagnosis - Check HAProxy Validation on Agent Server:

# SSH to the agent server where changes are stuck
ssh user@agent-server

# Check agent logs for validation errors
sudo tail -f /var/log/haproxy-agent/agent.log

# The agent writes the new config to /tmp/haproxy-new-config.cfg before applying
# Manually validate the configuration file
sudo /usr/sbin/haproxy -c -f /tmp/haproxy-new-config.cfg

# If validation fails, you'll see detailed error messages like:
# [ALERT] parsing [/tmp/haproxy-new-config.cfg:45] : 'bind' : missing address, port or path
# [ALERT] parsing [/tmp/haproxy-new-config.cfg:78] : unknown keyword 'ssl-certificate'

Common Validation Issues:

  1. Invalid SSL Certificate Reference:

    [ALERT] 'bind' : unable to load SSL certificate '/etc/haproxy/certs/missing-cert.pem'
    
    • Solution: Ensure SSL certificate was uploaded and synced to the agent
    • Check: ls -la /etc/haproxy/certs/
  2. Backend Server Not Defined:

    [ALERT] 'use_backend' : unable to find required use_backend: 'backend-name'
    
    • Solution: Ensure backend exists and is defined before the frontend that uses it
  3. Invalid ACL Syntax:

    [ALERT] parsing [config:42] : error detected while parsing ACL 'acl-name'
    
    • Solution: Fix ACL syntax in Frontend Management
  4. Port Already in Use:

    [ALERT] Starting frontend GLOBAL: cannot bind socket
    
    • Solution: Check if another service is using the same port

Recovery Steps:

  1. View Generated Config:

    # On agent server - view the new config that failed validation
    sudo cat /tmp/haproxy-new-config.cfg
    
    # Compare with current working config
    sudo cat /etc/haproxy/haproxy.cfg
  2. Check Agent Logs:

    # See full error context and rollback messages
    sudo tail -100 /var/log/haproxy-agent/agent.log | grep -A 10 "validation failed"
  3. Fix Issues in UI:

    • Go to Apply Management → View pending changes
    • Identify the problematic entity (frontend, backend, SSL certificate)
    • Edit or delete the problematic entity
    • Reject current pending changes
    • Re-apply after fixing
  4. Emergency Rollback:

    # On agent server - agent automatically keeps backup
    sudo ls -la /etc/haproxy/haproxy.cfg.backup*
    
    # Manually restore if needed (agent usually does this automatically on validation failure)
    sudo cp /etc/haproxy/haproxy.cfg.backup.TIMESTAMP /etc/haproxy/haproxy.cfg
    sudo systemctl reload haproxy

Prevention:

  • Always test configurations incrementally (don't make too many changes at once)
  • Use Configuration Viewer to preview generated HAProxy config before applying
  • Ensure SSL certificates are uploaded before creating frontends that use them
  • Keep backend names unique and descriptive
  • Test ACL syntax before saving

7. ACME Certificate Issues

Symptom: ACME certificate request stuck in "pending" or challenge validation fails.

Diagnosis:

# Check backend logs for ACME errors
kubectl logs deployment/backend -n haproxy-openmanager | grep "ACME"

# Verify ACME challenge endpoint is accessible through the managed HAProxy instance
curl http://your-domain/.well-known/acme-challenge/test

# Check if ACME is enabled on the cluster
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/clusters | jq '.[].acme_enabled'

Common Issues:

  1. Challenge validation failed: Ensure HAProxy cluster has acme_enabled=true and HTTP-mode frontends are configured
  2. DNS not pointing correctly: The domain must resolve to the IP of the HAProxy instance(s) managed by the cluster's agents
  3. Rate limited: Let's Encrypt has rate limits; use staging mode (acme.staging_mode=true) for testing
  4. Stuck orders: Orders stuck > 24h are logged as warnings; check backend logs
  5. Firewall blocking: Port 80 must be open for HTTP-01 challenges from the CA

ACME Auto-Renewal Not Working:

# Check if auto-renewal is enabled
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/settings/acme

# Verify background task is running (check backend logs)
kubectl logs deployment/backend -n haproxy-openmanager | grep "ACME RENEWAL"

Settings to verify:

  • acme.auto_renew_enabled must be true
  • acme.renew_before_days controls when renewal triggers (default: 30 days)
  • Certificate must have auto_renew=true and source=letsencrypt

Debug Mode

Enable debug logging:

# Backend debug mode
export DEBUG=True
export LOG_LEVEL=DEBUG

# Frontend development mode
export NODE_ENV=development
export REACT_APP_DEBUG=true

Log Locations

# Application logs
docker logs haproxy-openmanager-backend
docker logs haproxy-openmanager-frontend

# HAProxy logs
docker logs haproxy-instance

# Database logs
docker logs haproxy-openmanager-db

# System logs (on remote servers)
journalctl -u haproxy
journalctl -u keepalived

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit changes (git commit -m 'Add amazing feature')
  4. Push to branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

How to Contribute

  1. Report Bugs: Open an issue with detailed reproduction steps
  2. Suggest Features: Share your ideas for new features or improvements
  3. Submit PRs: Fix bugs, add features, or improve documentation
  4. Improve Docs: Help make the documentation clearer and more comprehensive
  5. Share: Star the project and share it with others

Development Guidelines

  • Follow PEP 8 for Python code
  • Use ESLint/Prettier for JavaScript code
  • Add tests for new features
  • Update documentation for API changes
  • Ensure Docker builds work correctly
  • Follow existing code style and conventions
  • Write clear commit messages
  • Keep PRs focused on a single change

Use Cases

  1. Enterprise HAProxy Management: Manage multiple HAProxy clusters across different environments
  2. DevOps Automation: Automated configuration deployment with version control
  3. Multi-Tenant Setups: Separate pools and clusters for different teams/projects
  4. Monitoring & Analytics: Real-time metrics and performance tracking
  5. Security Management: Centralized SSL certificate and WAF rule management

License

This project is licensed under the GNU Affero General Public License v3.0 (or later) — see the LICENSE file for the full text.

A short summary:

  • You are free to use, modify, and distribute this software.
  • If you run a modified version as a network service (e.g., SaaS), you must make the modified source available to its users (AGPL §13).
  • Any redistribution or derivative work must remain under AGPL-3.0-or-later.

Author

Taylan Bakırcıoğlu
Burgan Bank - DevOps / Product Group Manager
LinkedIn: linkedin.com/in/taylanbakircioglu

Developed with ❤️ for the HAProxy community

Support

  • Documentation: This README and inline code documentation
  • Issues: GitHub Issues
  • Pull Requests: Contributions are welcome!

Related Projects


Release Notes

  • v1.11.1 (2026-08-15) — A node can no longer be hidden from adoption for good, and Config Import reaches a freshly installed agent: the agent posts an unmanaged keepalived.conf for adoption and caches the hash of what it sent, so the file (which carries the VRRP password) is re-posted only when it changes. Delivery was judged by curl's exit code, which is 0 for 5xx as well, so a report the server rejected was recorded as delivered — and because a hand-maintained config does not change on its own, that node dropped out of Unmanaged keepalived detected permanently, curable only by deleting a cache file on the node by hand. Now: the report is cached only on a 2xx; GET /agents/{name}/keepalived-config tells the agent whether the server actually holds a discovery for it, so nodes stuck from earlier releases recover by themselves on the next poll; a 400/413/422 records the refusal so identical bytes are not re-posted forever (4xx and 5xx are never sampled out of the request log, so an unattended loop would write a row carrying the whole config every cycle), while 401 and 404 keep retrying because in this system they mean a token rotation or an agent row briefly absent, not a bad payload. The same exit-code mistake in the clear path is fixed too, where it left a managed node still being offered for adoption. Separately, Config Import silently did nothing on any agent that had never self-upgraded: check_config_requests was defined in the installer and in the self-upgrade daemon but not in the body a fresh install writes, and its call site is guarded by type, so the operator asked a node for its haproxy.cfg and nothing arrived, with no error anywhere. And the cluster whose keepalived_config_path is handed to an agent is now resolved deterministically — a pool may hold several clusters, and the unordered join could return a different one between polls, pointing the agent at a file that does not exist. Agent-script change: sync the script from Agent Management and let the agents upgrade. No schema change.
  • v1.11.0 (2026-08-14) — Unified request/response log with configurable retention: until now the only record of what happened was user_activity_logs, which stores non-GET 2xx operations with no bodies — so when something failed you could see that the count went up, never what was sent or what came back. This release adds one queryable timeline covering both directions: every inbound API call (including GETs and including 4xx/5xx) with the user, client IP, status, duration and — redacted and size-capped — the request and response bodies; and every outbound HTTP call the backend makes, tagged with who it went to (ACME/Let's Encrypt, Cloudflare, GoDaddy, HAProxy stats, agents, the ACME diagnostics probe). Outbound rows inherit the inbound request's id, so one operator action and the CA/DNS calls it triggered read as a single trace — opening a failed Request Certificate shows the exact POST /acme/new-order and the CA's 429 body underneath it. Capture is a pure-ASGI middleware that tees the request and response streams rather than draining them, so no downstream handler is affected (notably the raw-body agent heartbeat), and rows are written by a batching background writer with a bounded queue, so the request path never waits on the database and a saturated logger drops rows visibly instead of blocking. Secrets never land: headers are an allowlist (Authorization/Cookie reduced to a presence marker), body keys and value shapes are redacted (passwords, tokens, API keys, private-key PEMs, JWTs), the ACME JWS request body is never stored (a stored protected+signature pair is a replayable credential — a summary is logged instead), DNS-provider errors record only the exception type, and the ACME HTTP-01 challenge endpoint is excluded so key_authorization is never captured. Retention is operator-configurable in Settings → Request Log: separate day counts for successful and failed rows (defaults 7 and 30) plus a hard row cap (500 000), whichever is reached first, pruned in batches under a Postgres advisory lock so a multi-million-row table cannot time out the delete or have every replica scan it at once. New Request Log page (requestlog.read) and retention/purge permission (requestlog.manage); super_admin and security_admin get both, operator gets read, viewer gets neither. Successful agent polls are not logged (capture_agent_success, default off; failures always are), which is what keeps the table's size a function of operator activity rather than of node count: measured at 2 424 bytes/row, a 200-node fleet would otherwise write 2.0M rows/day and reach the row cap in six hours, silently reducing the configured 7-day/30-day retention to a few hours for everything in the table. Cost is measured, not estimated: 27.7 µs per request on the hot path, 18.8 µs per row on the writer task, 0.096 % of one core at 500 nodes. Adds one new table (request_logs) and its settings seed — SCHEMA_VERSION 11 → 12 (not 11: that number was taken by v1.10.4 while this was in review, and the version gate would have skipped the migration entirely on every existing install), auto-migrated, no existing table altered, no agent or rendered-config change. Kill switches: REQUEST_LOG_ENABLED=false (environment — the middleware is then never registered and costs nothing) or the enabled toggle in Settings (no restart).
  • v1.10.14 (2026-08-14) — A converged node keeps acknowledging: the deploy report is the server's only evidence that a member node applied its keepalived.conf, and it was sent on the write path alone. Once the rendered config was on disk the agent took the idempotency early return on every cycle and never reported again, so a single lost report — a backend restart, a 5xx, a network blip — left the VIP reading SYNCING (0/n) with an empty Last ack forever, while the node was demonstrably running the right config. Nothing would ever reconcile the two: the node was correct, the page was not, and the only way out was to change the rendered config so the agent wrote it again. The agent now re-asserts its state on the idempotent path too, which costs one request per node per ~2.5 minutes and touches nothing on the node — keepalived is not reloaded and the file is not rewritten. This is a long-standing gap from the original HA/VIP work, surfaced when acknowledgements were dropped for an unrelated reason in v1.10.12. Agent-script change: sync the script from Agent Management and let the agents upgrade. No schema or API change.
  • v1.10.13 (2026-08-14) — Agent deploy acknowledgements were silently dropped (regression in v1.10.12, fix it before or with that release): the takeover-retirement clause added to POST /agents/{name}/keepalived-status in v1.10.12 reused one query placeholder for both the assignment last_deploy_hash=$n and the comparison inside its CASE. PostgreSQL deduces a type per use, so the same placeholder came out as text in one and character varying in the other, and asyncpg rejected the statement with AmbiguousParameterError. The failure was not partial: the whole UPDATE never ran, so no member ever recorded an acknowledgement. Every VIP sat at SYNCING (0/n) with an empty Last ack, even after the nodes had deployed the config successfully, and teardown acknowledgements were lost the same way. The hash is now bound to its own placeholder, which is only ever compared against the column and therefore unambiguous. Verified against a real PostgreSQL: both statements execute, a matching hash retires the takeover authorisation, a non-matching hash and a NULL applied_config_hash both leave it in place, and every case records the acknowledgement. A test now asserts every $n in these statements is bound exactly once and that the count matches the arguments passed. Backend only: no schema, agent or API-shape change.
  • v1.10.12 (2026-08-14) — A valid keepalived config is no longer rejected by its own warning: before writing a rendered keepalived.conf the agent validates it with keepalived -t and, on failure, keeps the running config and does not restart keepalived. That fail-safe is right, but it treated any non-zero exit as invalid, and keepalived's config-test exit code does not separate fatal from benign. Measured on 2.2.8: a clean config exits 0, but Truncating auth_pass to 8 characters exits 5 and so does a missing } or an Unknown keyword. A VRRP password longer than eight characters was therefore enough to make every apply fail, including on nodes whose own running config produces the same warning and has been serving the VIP for weeks. The gate now judges the output: messages known to be benign are dropped and anything that remains still fails, so it fails closed and an unrecognised message is treated as fatal. Verified against real keepalived: a truncation warning passes while a missing brace, an unknown keyword and a SECURITY VIOLATION are all still refused. The agent also reports what keepalived said now, in the log and in the status the HA/VIP page shows; discarding it left a correct refusal with no way to act on it. Agent-script change: sync the script from Agent Management and let the agents upgrade for it to take effect. No schema or API change.
  • v1.10.11 (2026-08-14) — The Adoptable tag names the problem that actually blocks adoption: the tag and the disabled Adopt button were computed separately and could disagree. A pair blocked because its peer's keepalived.conf could not be parsed was labelled MASTER missing — technically true, since the unreadable node's state MASTER had not been counted, but it pointed the operator at the wrong node while the real reason sat in the button's own tooltip. Both now come from one ordered decision, so the label, its colour and the tooltip always describe the condition that stops the adoption; a group held up by an unreadable or unreachable peer reads blocked by peer, and two MASTERs is now distinct from none. Display only: what the endpoint accepts or refuses is unchanged. On the public repo this is the first artifact carrying v1.10.4 through v1.10.10: none was released separately, because VIP adoption did not work end to end until these fixes landed.
  • v1.10.10 (2026-08-14) — Adoption blockers are listed once per instance: with the instance-based panel a two-node pair reported the same problems about the same shared config twice, once per member, and the line numbers differ between the two files so plain de-duplication did not collapse them. Four issues on a pair read as eight, in both the Adoptable tooltip and the adopt dialog. They are now merged on the message text with the leading line N: ignored, so each distinct problem appears once. Display only: the endpoint already evaluated the combined set and its refusals are unchanged.
  • v1.10.9 (2026-08-14) — Adoption refuses to strand a node or silently normalise a peer's settings: v1.10.8 adopted the whole VRRP instance, but it could only match a node it was able to read, that was enabled, and that sat in the same pool. Each of those was a door a real member of the group left through silently — the nodes that remained were rewritten while the one that left kept serving the same address from an unmanaged config. Seen on a live pool: one node of a pair had an unclosed vrrp_instance block, so it parsed to nothing while its partner parsed cleanly. Instead of guarding each door, adoption now asks the question directly — is there any reported keepalived.conf that mentions this virtual address and is not among the nodes being taken over — and refuses naming the node and the reason (unparseable, agent disabled, different pool). Separately, four VIP-level fields (prefix_length, unicast/multicast mode, HAProxy tracking and the VRRP password) are stored once and re-rendered onto every member, so taking them from whichever node was clicked imposed its settings on the others; the prefix length is the sharpest, because the design refuses to guess a netmask for a live VIP and copying one node's netmask onto another is that same change by another name. Adoption now requires the nodes to agree on all four, and compares the VRRP secret by decrypting each node's token (Fernet is non-deterministic, so the ciphertexts cannot be compared). Finally, the takeover authorisation is genuinely one-shot: takeover_expected_hash was written at adoption and never cleared, so it stayed valid for that file content indefinitely — it is now retired the moment a member acknowledges our rendered config, gated on the acked hash matching so a failed deploy never drops it. No schema change, no agent change.
  • v1.10.8 (2026-08-13) — VIP adoption takes the whole VRRP instance: adoption used to take only the node whose row was clicked, which broke the exact case the feature exists for, a running HA pair. Adopting the BACKUP alone produced a VIP that could never be applied (exactly one member must be MASTER); adopting the MASTER alone left the peer unmanaged, and adopting it afterwards hit the VRID-collision guard with 409, so the pair could not be completed from the panel at all. Most serious, on a unicast instance the single-member render dropped the unicast block entirely — the renderer emits it only when it has peer addresses — so keepalived fell back to multicast on the adopted node while its peer stayed unicast: they stop seeing each other and both claim the VIP. Adoption now resolves the whole instance, keyed on (virtual_router_id, virtual address) exactly as keepalived groups nodes, and every participating node becomes a member with the role, priority and interface its own file declares and its own one-shot takeover hash. It refuses, with the reason, when the group does not have exactly one MASTER, when the nodes disagree on advert_int, when a declared unicast peer is not among the nodes being adopted, or when a node already belongs to a live VIP — a rule create/edit enforced and adoption did not. The panel now lists one row per instance instead of per node. Two further fixes: the Apply Management View Change diff did not recognise the adopt action, so it fell through to the generic HAProxy diff and rendered the cluster's entire haproxy.cfg as removed; and rejecting an adoption hid the node from the panel permanently, because adopted_vip_id is write-once, a VIP is only ever soft-deleted (so the column's ON DELETE SET NULL never fires) and the agent does not re-report an unchanged file — adoptability is now derived from whether the linked VIP is still active, which self-heals reject, undo-reject and approved teardown alike. No schema change, no agent change.
  • v1.10.7 (2026-08-13) — HA / VIP follows the selected cluster: the page ignored the cluster picker in the header. On a multi-cluster install both the VIP table and the new Unmanaged keepalived detected panel listed every cluster's nodes at once and did not change when the selection did, so the panel appeared to be stuck on one cluster's keepalived. Both lists now send cluster_id, resolved to that cluster's pool exactly as the Apply Management view already did. The API parameter is optional: a caller that omits it still receives the whole fleet, so nothing outside the page changes. This is a deliberate behaviour change for the VIP table, which was fleet-wide before. Backend and frontend only: no schema, no agent change.
  • v1.10.6 (2026-08-13) — VIP adoption panel was unreachable: v1.10.4's Unmanaged keepalived detected panel never appeared, even on a fleet where the agents had reported their configs correctly. GET /discoveries was declared after GET /{vip_id} in routers/vip.py, and FastAPI matches routes in declaration order, so every request for the discovery list was answered by the get-one-VIP handler, which takes vip_id: int and rejected "discoveries" with 422 before the real handler ran. Nothing surfaced the failure: the agents reported normally, the rows landed in vip_discoveries, and the HA/VIP page treats any non-OK response as "nothing to show" — so the whole feature was invisible with no error anywhere. The route is moved above the parameterised ones, and a static source scan now asserts that no literal path in any router is shadowed by an earlier parameterised one, so the class of bug cannot come back silently. Data reported under v1.10.4 is not lost: existing vip_discoveries rows appear as soon as the fixed backend is deployed, with no agent action needed. Backend-only fix. No schema, API-shape or agent change.
  • v1.10.5 (2026-08-09) — HTTP-01 challenge backend on split deployments: on a deployment where the HAProxy nodes and the management stack are on different hosts, HTTP-01 issuance could fail silently for weeks while DNS-01 kept working — the rendered config pointed server _acme_mgmt at an address that resolves on the HAProxy node, defaulting to loopback, and every diagnostic still reported success. The per-cluster acme_backend_url now has a UI field, changing it actually mints a config version, and the value is validated where it is written. Three adjacent bugs are fixed with it: a config-generation failure was returned as # Error ... text and then stored as an APPLIED version and pushed to agents as the cluster's whole haproxy.cfg (both call sites now refuse with 422); a nullable frontends.mode was interpolated raw and emitted mode None, which HAProxy rejects and which takes down the entire cluster config; and cluster creation silently dropped the ACME fields. docker-compose.yml now interpolates PUBLIC_URL / MANAGEMENT_BASE_URL instead of hardcoding them, with the old literals as defaults. Diagnostics read the response body so an SPA answering 200 is no longer counted as healthy, and every new condition is a warning rather than a failure so no install is locked on upgrade. No schema, API-shape or agent change.
  • v1.10.4 (2026-08-08) — Adopt an existing keepalived VIP (Issue #27 follow-up): on a fleet that already runs keepalived, the HA / VIP page came up empty, because the flow was one-way — VIPs were declared in OpenManager and pushed to the node, and nothing ever read what was already there. Agents now report the keepalived.conf they find and do not own (strictly read-only; the node is never touched), the page lists those nodes under Unmanaged keepalived detected, and Adopt turns one vrrp_instance into a managed VIP with the values from the file instead of retyping them. The heartbeat could not drive this: it carries the VIP address and a best-effort MASTER/BACKUP, while rendering a node's config needs eleven fields, and guessing them is not cosmetic — a wrong virtual_router_id puts the nodes in separate VRRP domains and a wrong auth_pass makes them reject each other, so both would claim the VIP. Because adoption replaces the operator's file with OpenManager's render, the parser reports every directive it cannot reproduce — a notify_master hook, an LVS virtual_server section, a vrrp_sync_group, a second address in one instance, a custom track_script — and refuses while any remain; the operator can waive that class explicitly, but a value that is simply unknown (an absent VRID or prefix length) can never be waived, only supplied. keepalived's own documented defaults (state BACKUP, priority 100, advert_int 1) are applied and shown as assumed. The agent's ownership guard is not weakened: adoption authorises exactly one takeover of exactly the file that was analysed, pinned to its hash, so a config edited between adoption and Apply is still refused. The adopted VIP is created PENDING like any other, so nothing reaches the node until it is applied from Apply Management. VRRP passwords are Fernet-encrypted at ingest and masked in the stored copy and the preview. Schema change: one new table vip_discoveries plus two additive columns (SCHEMA_VERSION 10 → 11, auto-migrated, no existing table altered) — see the upgrade notes: this bump re-seeds the four built-in roles, and the Linux agent script must reach the nodes before discovery starts.
  • v1.10.3 (2026-08-08) — Multi-account ACME: the certificate wizard honours the account you pick: with more than one ACME account registered, picking an HTTP-01 account in Request ACME Certificate still produced a DNS-01 request. Three faults compounded. (1) Form.useWatch reports only fields that are currently rendered, and the account Select lives on the Configuration step — so as soon as the wizard advanced to Review the watch read undefined and the wizard silently reverted to the default account, even though the value was still in the form store; the watches now pass preserve: true. The same fault disabled the wildcard guard on Review, the one step where Submit lives. (2) The UI and the backend disagreed on which account is the default: the backend takes the newest valid account (ORDER BY created_at DESC), the UI took the oldest entry of a list ordered by id — the opposite account whenever the two differ. The wizard now resolves the same one, and sends account_id explicitly so there is no guess left to disagree about. (3) account_id was read from the form store while challenge_type came from the reverted account object, so the request asked for DNS-01 validation on an HTTP-01 account and the API answered The selected ACME account has no DNS provider configured for DNS-01. — both are now derived from one resolved account. The Review step also showed the default account's address instead of the chosen one, and Submit stayed enabled for a deactivated account; both fixed. Frontend only — no schema, API-shape, agent or rendered-config changes, and single-account installations behave exactly as before.
  • v1.10.2 (2026-08-08) — Dark mode fixes on Apply Management: several panels on the Apply Management page were painted with light-mode colour literals, so in dark mode the Pending Changes box rendered as a cream panel with light text on it — measured contrast 1.03:1, effectively unreadable, now 11.50:1. The same bug affected the added/removed rows in the View Change diff (2.21:1 and 2.99:1, now 5.49:1 and 4.01:1), the ACME and pending-version panels, the VIP pending-delete row, and the agent-error recommendation box; all now derive from theme tokens. Separately, static confirm dialogs came up white in dark mode: in Ant Design 5 the static Modal.confirm / message / notification APIs render into their own detached root and never see the app's ConfigProvider, so they always used the light algorithm. Registering ConfigProvider.config({ holderRender }) once at the app root fixes every static dialog in the application (12 components use them), not only this page. Light mode is byte-identical — each token resolves under the default algorithm to exactly the literal it replaced. Frontend only: no schema, API, environment or agent change.
  • v1.10.1 (2026-08-08) — CSR private key encrypted at rest (Issue #53): the private key of a pending CSR is now Fernet-encrypted in the database instead of stored as PEM. It is the one key in the system worth protecting this way — it sits idle for the entire signing window (days to weeks), is never transmitted to an agent, and is destroyed the moment the signed certificate is imported; ssl_certificates.private_key_content and the ACME order keys are unchanged, because agents must receive those in plaintext on every poll. The token replaces the PEM in the same column, so there is no schema change and no SCHEMA_VERSION bump (and therefore no re-seed of the built-in roles). CSRs created before this release keep a raw PEM and are still read transparently, so anything already out for signature imports normally with no data migration. The key derives from SECRET_KEY via HKDF with its own info string, independent of the VIP/MFA/DNS keys, and an optional CSR_ENCRYPTION_KEY enables independent rotation — rotating SECRET_KEY without it makes pending CSR keys unrecoverable, which now fails with an explicit "delete and re-create this CSR" error rather than a misleading key-mismatch. .env.template now documents all four per-purpose encryption keys. No API, UI or agent change.
  • v1.10.0 (2026-08-07) — GoDaddy DNS provider for DNS-01 (Issue #35 follow-up): DNS-01 challenges can now be published and cleaned up automatically through GoDaddy, alongside the existing Manual and Cloudflare providers, so wildcard and internal-cluster certificates on GoDaddy-hosted zones renew unattended. Credentials are a Production API Key + Secret pair from developer.godaddy.com/keys (a Personal Access Token also works — paste it as the Key and leave the Secret blank, which is the forward path as GoDaddy retires sso-key); they are verified against the GoDaddy API before being saved and encrypted at rest (Fernet, the same path as Cloudflare), and are never returned by the API, logged, or written to an order event. GoDaddy's v1 API has no per-value TXT writePUT replaces an entire RRset — so add/remove are read-modify-write with sibling values merged back, empty-data tombstones filtered out, and DELETE used for the last value (PUT [] is rejected); this is what keeps the apex + wildcard case (two TXT values at one _acme-challenge name) working, and the record path is hard-gated so it can never collapse onto the zone-wide endpoint that would wipe SPF/DKIM/DMARC. Zone lookup probes the records API rather than the domain listing, so delegated sub-zones resolve and small accounts are not falsely rejected. Registry-only addition: one new provider module plus one registry line — no frontend change (the credential form is schema-driven). No schema, API-shape, agent, or rendered-config changes; Manual, Cloudflare and HTTP-01 are unaffected.
  • v1.9.0 (2026-08-04) — CSR creation (in-app key + CSR generation and signed-certificate import): a new CSR tab on the SSL Certificates page generates a private key and Certificate Signing Request server-side (RSA 2048/4096 or ECDSA P-256/P-384; full subject — O/OU/L/ST/C/email — plus DNS SANs with wildcard support), for certificates signed by an external or corporate CA. The operator downloads/copies the CSR PEM, has it signed, then imports the signed certificate (+ optional chain): the backend verifies the certificate against the stored key (hard gate), rejects expired certs, warns on SAN drift, and creates a normal SSL certificate entry (source CSR) that flows through the standard PENDING → Apply Management → agent pull pipeline. The private key never leaves the server — no CSR endpoint returns it, and after import the CSR row's key copy is destroyed (the key then lives only on the certificate, like every other key). Additive schema change: one new table ssl_csrs (SCHEMA_VERSION 9 → 10, auto-migrated, no existing table altered); key generation runs off the event loop and is rate-limited per user; existing ssl.* permissions govern all new endpoints. No agent or rendered-config changes.
  • v1.8.10 (2026-07-20) — Security hardening (GHSA-7rhv-c5pc-69r8, GHSA-3p5c-m5m4-mjpx, GHSA-3vh4): three advisory classes remediated, backend-only, no agent changes. (1) RCE: the agent script-template read/write endpoints now require the agents.version permission on top of authentication — a poisoned template is executed as root on every HAProxy node, so authentication alone was insufficient. (2) Missing authentication: operator/UI endpoints that were served without a JWT (dashboard stats, pool/cluster listings, agent inventory, WAF rules, config validate/optimize, SSL config-versions, health deep/agents/clusters) are now gated by a require_authenticated_user dependency, and agent data-plane endpoints that treated the X-API-Key header as optional (heartbeat, config, ssl-certificates, upgrade-status, pending-requests) now hard-reject a missing key. In every case the auth check was moved ahead of the handler's try: block so a 401 can no longer be rewritten into a 500 by the generic exception handler. (3) SSRF: a new utils/ssrf_guard.py (https-only, IPv4-pinned connector, all resolved addresses must be public, no redirects) protects the ACME directory fetch, the signed-request target and the ACME connection test, which accept operator- or DB-supplied URLs; the connection test also stopped reflecting arbitrary upstream JSON. Frontend dependency advisories patched in the same release. No schema, API-shape or rendered-config changes.
  • v1.8.9 (2026-07-13) — ACL -f pattern-file support (Issue #38 follow-up): ACL definitions that reference a host-side pattern file (acl … -f /etc/haproxy/lists/blocked.lst) are accepted on import and edit instead of being rejected. The referenced file lives on the HAProxy node and cannot be validated from the manager, so the manager emits an advisory warning rather than a hard rejection and lets the agent's haproxy -c check be the fail-safe gate (a broken reference fails validation on the node and the previous config is restored). Consistent with the SPOE handling introduced in v1.8.8.
  • v1.8.8 (2026-07-10) — SPOE filter and frontend log-format preserved on import/edit (Issue #38): importing an existing haproxy.cfg or editing a frontend silently dropped filter spoe … directives and custom log-format lines, so the next Apply pushed a config that had lost them. Both are now round-tripped through import and edit. As with -f pattern files, the SPOE engine config is a host-side file the manager cannot read, so it is preserved verbatim and reported as an advisory rather than validated centrally.
  • v1.8.7 (2026-07-09) — Version reporting single-source fix: the version shown in the UI (backend-sourced via /api/version) could lag behind the real release. The canonical version lived in the repo-root version.json, but the backend image is built from the ./backend context, so that file did not reach the container in every pipeline; the backend then fell back to a hardcoded constant in main.py that had to be bumped by hand and had drifted (it reported 1.8.4 after 1.8.5/1.8.6 shipped). The version now lives in a single file, backend/version.json, baked into every image automatically, and main.py no longer carries a real version literal (its fallback is a neutral "unknown"). A new test enforces that the version stays single-source and cannot drift. No functional or API change.
  • v1.8.6 (2026-07-06) — Performance: opt-in API workers + heartbeat micro-optimization (Issue #35 follow-up): the backend container can now run multiple uvicorn worker processes via the new UVICORN_WORKERS environment variable (default 1 — behavior unchanged unless you opt in), letting the API use all cores on multi-core hosts; background tasks were already multi-replica safe, as exercised by the Kubernetes HPA deployment. The agent heartbeat handler now reads the agent's status/version/upgrade_status in one query instead of three (one round-trip per heartbeat, per agent, every 30s). Added a Performance Tuning section to the README (worker/replica scaling and how to use the X-Response-Time header and Slow request detected logs to pinpoint slow endpoints). Zero-risk release: no schema, API, or agent changes; defaults preserve existing behavior exactly.
  • v1.8.5 (2026-07-03) — ACME completion-task SQL fix (Issue #35 follow-up): the background order-completion task (complete_pending_acme_orders, runs every 60s) died on every cycle with syntax error at or near ")" — an extra closing parenthesis introduced in v1.8.0's bounded DNS-01 retry claim query. Because that query is the task's first database call, no background ACME work ran at all from v1.8.0 through v1.8.4: orders were never claimed for finalize/download, the DNS-01 TXT record was never published (so DNS-01 with an automated provider such as Cloudflare could never validate), Site Wizard staged orders never left wizard_staged, and DNS-01 retry/TXT-cleanup never executed. The stray parenthesis is removed and a regression test now scans all ACME modules' SQL for unbalanced parentheses (the unit suite mocks the database, which is why a raw-SQL syntax error could slip through). One-line backend query fix; no schema, API, or agent changes — fully backward compatible.
  • v1.8.4 (2026-06-27) — Agent installer self-kill fix (Issue #31): the Linux/macOS agent installer could abort during "pre-installation cleanup" (terminal showed Killing processes matching: haproxy-agent then Killed) when the install script's own filename contained "haproxy-agent". The cleanup killed processes by matching the bare string "haproxy-agent" against full command lines, which also matched the running installer (and a sudo/PAM ancestor the self-exclusion did not cover), so the installer terminated itself. Cleanup now targets only the installed agent (the $INSTALL_DIR/haproxy-agent binary and the agent service), never the bare string, and the UI now names the downloaded scripts install-agent-<platform>.sh / uninstall-agent-<platform>.sh. Installer-only change; the running agent and its privilege model (it runs as root for HAProxy reload, config writes, keepalived, and self-upgrade) are unchanged.
  • v1.8.3 (2026-06-25) — Agent heartbeat JSON fix (Issue #31): a self-hosted agent could fail every heartbeat with HTTP 400 Invalid JSON: Expecting property name enclosed in double quotes when the system-info block it collects came back empty on an unusual host, leaving a stray comma in the hand-built heartbeat JSON. The agent script now substitutes a valid placeholder when that block is empty so it can no longer emit a stray comma, and the backend heartbeat endpoint now parses valid payloads as-is and, only when a body fails to parse, tolerates that specific malformed pattern (a leading or doubled comma) so an already-deployed agent recovers on its next heartbeat after this build is deployed. Backend + agent-script only; healthy agents of every version are byte-for-byte unaffected.
  • v1.8.2 (2026-06-25) — ACME nonce fix (Issue #35 follow-up): the ACME client now scopes the anti-replay nonce per certificate authority so a nonce issued by one CA is never sent to another. This fixes ZeroSSL/Google account registration failing with malformed: The Replay Nonce could not be base64url-decoded (the client previously shared one nonce across CAs and only auto-retried on badNonce). Account registration now always uses a fresh nonce from the target CA, and the retry covers this case too. Backend-only; HTTP-01 and Let's Encrypt are unaffected.
  • v1.8.1 (2026-06-24) — ACME DNS-01 fixes (Issue #35 follow-up): Cloudflare API tokens are now sanitized so a pasted token with quotes/spaces no longer fails with "Invalid request headers"; ZeroSSL/Google External Account Binding (EAB) can be entered per-account in the register dialog and EAB-required failures show a clear message; and Apply Management now categorizes cluster ACME enable/disable changes under their own "ACME Challenge Routing" section and Apply/Reject All correctly process them (previously "Rejected 0 HA/VIP change(s)"), consistent with every other entity. Fully backward compatible.
  • v1.8.0 (2026-06-23) — ACME DNS-01 challenge support (Issue #35): Auto SSL can now validate via a DNS TXT record (_acme-challenge.<domain>) instead of HTTP-01 on port 80, enabling certificates for internal/isolated clusters with no public ingress and wildcard certificates (*.example.com). Pluggable per-account DNS provider (Manual + Cloudflare to start; credentials verified on save and encrypted at rest, never returned by the API or logged), the same PENDING → APPLIED pipeline, a bounded automatic retry on propagation lag, and a DNS-01 event timeline in the order detail. Opt-in via Settings → ACME (global switch, default off); HTTP-01 is byte-for-byte unchanged, with zero agent or rendered-config changes. Manual DNS-01 certificates cannot auto-renew unattended; the UI states this and disables auto-renew for them.
  • v1.7.8 (2026-06-07) — HA / VIP apply progress now shows per-node convergence: a multi-node VIP's apply popup reads "Syncing HA/VIP… 1/2 node(s) converged" (matching the HA/VIP table) instead of a coarse per-change count. Frontend-only.
  • v1.7.7 (2026-06-07) — HA / VIP apply-progress consistency: applying a VIP change (or approving a delete) used to flash the progress popup green instantly while the HA/VIP page still showed SYNCING (0/1) for a couple of minutes. The popup now keeps showing "Syncing HA/VIP… X/Y node(s) converged" until each member node reports the VIP ACTIVE (create/edit) or fully torn down (delete) — exactly like the HAProxy agent-sync widget — then completes green. It's a fire-and-forget background poll (the Apply button is released immediately), bounded at ~5 min so an offline node can't spin forever (then it completes with an informational "still converging — track on the HA/VIP page"). Frontend-only; no backend/agent/schema change.
  • v1.7.6 (2026-06-07) — HA / VIP UX + accuracy polish: (1) the on-prem/L2 cloud caveat is now a subtle, collapsed-by-default "Network requirements" info link instead of a prominent yellow warning. (2) The delete dialog is simplified — deletion is always a graceful teardown (stop & disable keepalived, remove our config, release the VIP, keep the package); the confusing "also uninstall the package" checkbox was removed (it was a no-op on any node whose keepalived predates the install marker, and package removal is better handled as a deliberate node-decommission step — the purge_package API remains for that). (3) The agent now reports keepalived FAULT state (e.g. when the chosen interface has no usable IPv4) instead of misreporting it as BACKUP, so a misconfigured VIP shows red/FAULT in the UI. (1)+(2) are frontend-only; (3) is an additive agent-script change — push it via Agent Script Management → Reset to Defaults, then Upgrade.
  • v1.7.5 (2026-06-07) — HA / VIP "View Change" fix: editing a VIP (e.g. priority + virtual IP) now shows a real line diff — only the lines that actually changed — in Apply Management's View Change, instead of rendering the whole keepalived.conf as "added". Also fixes a doubled + + prefix (the VIP diff now stores lines without a +/- prefix, matching the standard config diff). View-only; no schema, agent, apply, or render change.
  • v1.7.4 (2026-06-06) — HAProxy config-generator robustness fix: a frontend that uses a stick counter (track-sc<N> or an sc_*_rate(...) fetch, e.g. a rate-limit http-request deny if { sc_http_req_rate(0) gt N }) but declares no stick-table caused HAProxy to fatally reject the whole cluster config with "table '<frontend>' used but not configured". This happened where rate-limit directives had been baked into a frontend's stored request_headers/options (by an older version or a config import). The generator now auto-injects a default stick-table in that case. Purely additive — it only fires when a counter is used and no table exists (a config that was already invalid), so frontends that already declare a stick-table or don't rate-limit are byte-unchanged.
  • v1.7.3 (2026-06-06) — HA / VIP backward-compat fix: the v1.7.2 deletion-tracking list now only resurfaces VIPs deleted through the new approval flow (gated on last_config_status='APPLIED'), so a VIP soft-deleted under an earlier version's immediate-delete is no longer shown as DELETING. Display-only; no schema or agent change.
  • v1.7.2 (2026-06-06) — HA / VIP safety & visibility follow-up:
    • Approval-gated deletion (safety). Deleting a running VIP from the UI no longer takes effect immediately — it is staged for Apply Management and the VIP keeps running, untouched, until you Approve it (Reject keeps it). The agent is told to tear keepalived down only after approval, so a misclick can never tear down a production VIP — an agent never deletes without an explicit human approval. The deletion is trackable through the standard Apply popup and a DELETING status on the HA/VIP tab. (A VIP that was never applied is removed at once — nothing is running to tear down.)
    • Diagnostics view — a per-VIP search-icon modal showing each node's keepalived deploy state, the message it reported, last-ack time and live VRRP state (handy while a fresh install is SYNCING, ~30s), plus the exact node-side log commands.
    • Opt-in package uninstall — the safe default keeps the keepalived package (stop & disable, remove our config, release the VIP); a default-off checkbox additionally uninstalls the package only on nodes where OpenManager installed it (an admin's pre-existing keepalived is never removed, tracked via an install marker).
    • Adds additive purge_on_teardown + pending_delete columns; SCHEMA_VERSION 5 → 7 (idempotent; existing data/passwords unaffected). Agent script updated — push it via Agent Script Management → Reset to Defaults, then Upgrade agents.
  • v1.7.1 (2026-06-06) — HA / VIP follow-up: a VIP can now be created on a single node — a Keepalived-managed floating IP without failover (e.g. a one-box HAProxy that wants a stable address, or before a second node is added). Add a second node anytime for real VRRP failover. A single-node VIP renders a clean multicast config (no bare unicast_src_ip, which keepalived -t rejects); multi-node behaviour is unchanged. The Create-VIP form now auto-selects the first chosen node as MASTER and shows an in-UI notice that, on Apply, Keepalived is installed automatically from the node's OS package repositories (apt/dnf/yum/zypper/apk — the node must reach its repos / an internal mirror), while a hand-managed Keepalived is still left untouched. No schema change.
  • v1.7.0 (2026-06-05) — Feature (Issue #27): HA / VIP (Keepalived) management from the UI. A new "HA / VIP" tab lets you create a virtual IP, pick a per-node interface, and select which pool nodes participate (with MASTER/BACKUP roles + priorities); on Apply, the agent installs & configures Keepalived (unicast VRRP, cloud-safe default) with a HAProxy health-check so the VIP fails over automatically when HAProxy drops, and the tab shows live MASTER/BACKUP per node. Fully opt-in and backward compatible — nodes/clusters without a VIP are untouched, and a node already running a hand-managed Keepalived is detected and never overwritten (reported as "externally managed"). Pending VIP changes can be Rejected to fully restore the last applied state. Keepalived is installed across the major distros (Debian/Ubuntu, RHEL/CentOS/Alma/Rocky, Fedora, SUSE/openSUSE, Alpine), and live MASTER/BACKUP detection works across distros/init systems (journald + log files + portable interface-based detection). On-prem/L2 scope (VRRP); a clear in-UI notice covers the cloud caveat. Adds two new tables (vip_instances, vip_members) — SCHEMA_VERSION bumps to 3 (idempotent re-run; existing data and passwords unaffected). A new "HA / VIP" tab lets you create a virtual IP, pick a per-node interface, and select which pool nodes participate (with MASTER/BACKUP roles + priorities); on Apply, the agent installs & configures Keepalived (unicast VRRP, cloud-safe default) with a HAProxy health-check so the VIP fails over automatically when HAProxy drops, and the tab shows live MASTER/BACKUP per node. Fully opt-in and backward compatible — nodes/clusters without a VIP are untouched, and a node already running a hand-managed Keepalived is detected and never overwritten (reported as "externally managed"). Pending VIP changes can be Rejected to fully restore the last applied state. Keepalived is installed across the major distros (Debian/Ubuntu, RHEL/CentOS/Alma/Rocky, Fedora, SUSE/openSUSE, Alpine), and live MASTER/BACKUP detection works across distros/init systems (journald + log files + portable interface-based detection). On-prem/L2 scope (VRRP); a clear in-UI notice covers the cloud caveat. Adds two new tables (vip_instances, vip_members) — SCHEMA_VERSION bumps to 3 (idempotent re-run; existing data and passwords unaffected).
  • v1.6.5 (2026-06-02) — Security: re-pinned the bundled nginx reverse-proxy image to nginx:1.31.1-alpine (mainline patched release) for the nginx "poolslip" advisory (fixed in mainline 1.31.1+ / stable 1.30.2+). Supersedes the v1.6.4 stable pin. No config, schema, or behavior changes.
  • v1.6.4 (2026-06-02) — Security: pinned the bundled nginx reverse-proxy image to a patched stable release (nginx:1.30.2-alpine) for the nginx "poolslip" advisory (mainline ≤ 1.31.0 affected; fixed in stable 1.30.2+). The product's nginx config uses no rewrite capture groups, so the config-level mitigation did not apply — the fix is the version pin. No config, schema, or behavior changes.
  • v1.6.3 (2026-06-01) — Bugfix: a backend server toggled OFF (is_active=false) disappeared from the UI with no way to reactivate it. GET /api/backends now honors include_inactive for servers (previously only backends), so disabled servers stay visible with an OFF switch + "Inactive" tag and can be re-enabled; soft-deleted (pending-delete) servers stay hidden. A Reject of a server toggle now correctly rolls back is_active (entity snapshot). Startup migrations are hardened for multiple replicas / rolling deploys (advisory lock + schema-version gate, so an already-current schema isn't re-migrated under a serving peer's load). Version is reported consistently across all layers. No schema changes; config generation unchanged (disabled servers stay commented out).
  • v1.6.2 (2026-05-30) — Bugfixes: (1) agents (which authenticate with their X-API-Key token) could not reach GET /api/clusters / /api/clusters/{id} after the v1.5.x cluster-read hardening, breaking agent assignment ("401: Authorization header missing"); these endpoints now accept either a user JWT or an agent token (anonymous access is still rejected). (2) The uninstall-script generator returned 400 for macOS agents (which report platform darwin); it now normalizes the platform the same way the install generator does. No UI or schema changes.
  • v1.6.1 (2026-05-21) — Security patch: bump axios to 1.16.x (prototype-pollution hardening, header-injection fix, keep-alive memory leak fix) and fast-uri to 3.1.2 (GHSA-v39h-62p7-jpjc). No functional changes.

For full release notes and the list of features delivered in each version (v1.5.x Site Wizard + ACME Diagnostic Panel, v1.4.0 ACME stability + enterprise audit, v1.3.0, ...) see the GitHub Releases page.


Made with ❤️ for the HAProxy community

Releases

Packages

Used by

Contributors

Languages