Running HAProxy on Windows


General

To run the built haproxy.exe on Windows, there are a few options.  You can run it interactively in a command prompt window, start it with a scheduled task or when you use a wrapper, you can even run it as a Windows service.

It all depends on why you want to use HAProxy. If you just want to run it to test some stuff, the interactive way in a command prompt window might be the easiest option.  If you however want to run it in a lab or test environment to access different web resources behind it, then the better option will be to run it as a service or start it from a scheduled task.

To run HAProxy as a service, you need some kind of wrapper around it.  Examples of such wrappers are: SrvStart, NSSM and so on.  If you search the web for it, you'll find many more of them.  As I don't want to depend on more freeware software then needed, I prefer the option of running HAProxy via a scheduled task as in the end, it will have exactly the same behavior.  Therefore, I'm not recommending any of the available wrappers mentioned.  Neither will I describe how to set it up.

In my personal setup and in the examples in this article, I use the following folder structure:

E:\HAProxy
├───bin
├───Certs
├───Conf
├───Log
└───SysLog

  • bin - This folder contains the binaries (haproxy.exe and the required DLL's)
  • Certs - In this folder the certificates for SSL offloading are stored.
  • Conf - This folder is used to store the configuration file(s).
  • Log - A folder used to store the log files from the script that I use to manipulate HAProxy.
  • Syslog - In the configuration file, logging to a syslog server can be configured.  In this folder I store my syslog logfiles.

I Start all commands relative to the HAProxy root folder (E:\HAProxy in this example).  So, the binary is referred to as ".\bin\haproxy.exe", the config file is referred to as ".\Conf\haproxy.conf", the certifcates as ".\Certs\Certificate.pem", etc.

Running HAProxy Interactively

If you run haproxy.exe in a command prompt with the "-?" parameter, all the parameters and available options are displayed (example from v2.6.6):

HAProxy version 2.6.6-274d1a4 2022/09/22 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2027.
Known bugs: http://www.haproxy.org/bugs/bugs-2.6.6.html
Running on: CYGWIN_NT-10.0-20348 3.3.6-341.x86_64 2022-09-05 11:15 UTC x86_64
Usage : haproxy [-f <cfgfile|cfgdir>]* [ -vdVD ] [ -n <maxconn> ] [ -N <maxpconn> ]
    [ -p <pidfile> ] [ -m <max megs> ] [ -C <dir> ] [-- <cfgfile>*]
    -v displays version ; -vv shows known build options.
    -d enters debug mode ; -db only disables background mode.
    -dM[<byte>,help,...] debug memory (default: poison with <byte>/0x50)
    -V enters verbose mode (disables quiet mode)
    -D goes daemon ; -C changes to <dir> before loading files.
    -W master-worker mode.
    -q quiet mode : don't display messages
    -c check mode : only check config files and exit
    -cc check condition : evaluate a condition and exit
    -n sets the maximum total # of connections (uses ulimit -n)
    -m limits the usable amount of memory (in MB)
    -N sets the default, per-proxy maximum # of connections (0)
    -L set local peer name (default to hostname)
    -p writes pids of all children to this file
    -dp disables poll() usage even when available
    -dK{class[,...]} dump registered keywords (use 'help' for list)
    -dr ignores server address resolution failures
    -dV disables SSL verify on servers side
    -dW fails if any warning is emitted
    -dD diagnostic mode : warn about suspicious configuration statements
    -sf/-st [pid ]* finishes/terminates old pids.
    -x <unix_socket> get listening sockets from a unix socket
    -S <bind>[,<bind options>...] new master CLI

Examples

Checking the version:

With the following command you check/verify the version of HAProxy.

E:\HAProxy\bin>haproxy.exe -v
HAProxy version 2.6.6-274d1a4 2022/09/22 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2027.
Known bugs: http://www.haproxy.org/bugs/bugs-2.6.6.html
Running on: CYGWIN_NT-10.0-20348 3.3.6-341.x86_64 2022-09-05 11:15 UTC x86_64

Validating the config file:

With the following command, you can check whether your configuration file is valid.

E:\HAProxy>.\bin\haproxy.exe -c -f .\Conf\haproxy.cfg
Configuration file is valid

Running HAProxy:

The simpliest way to run HAProxy is as follows:

E:\HAProxy>.\bin\haproxy.exe -f .\Conf\haproxy.cfg

This will start HAProxy as a background process and exits back to the command line.  This is similar as running:

E:\HAProxy>.\bin\haproxy.exe -f .\Conf\haproxy.cfg -D

The only way to stop HAProxy in this case is to kill the process via task manager.  Warnings, errors and other messages are not visible this way.  By adding the "-db" parameter instead of "-D", HAProxy runs interactive and displays the messages in the command prompt window.

E:\HAProxy>.\bin\haproxy.exe -f .\Conf\haproxy.cfg -db
[WARNING] (1449) : Server SERVERNAME/SERVERNAME is DOWN, reason: Layer4 timeout, check duration: 2014ms. 0 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
[ALERT] (1449) : backend 'BACKENDNAME' has no server available!

Warnings and alerts only as example of course ;-)

In this case, the program can be stopped by pressing CTRL-C.

Running HAProxy via a Scheduled Task

The PowerShell Script

To run HAProxy via a scheduled task and to manipulate the program (start, stop, restart), I've created a PowerShell script.

The code of the script "HAProxy.ps1":

#Requires -RunAsAdministrator
#Requires -Version 7.0

Param (
     # Path to configuration file.
    [string[]]
    $ConfigFile = ".\HAProxyConfig.xml",

    # Requested action to take: Stop, Start or Restart.  Default will be Start or Restart depending on whether HAProxy is running or not.
    [string[]]
    $RequestedAction
)

function EndScript {
    Write-Log -Text "Script [$scriptName.ps1] finished." -File_LOG $LogFile
    Write-Log -Text "=========================================================================================" -File_LOG $LogFile
    Pop-Location
    Pop-Location
    Exit
}

$ScriptPath = Split-Path $MyInvocation.MyCommand.Path
Push-Location $ScriptPath
. .\Write-Log.ps1

# Setting logging parameters ($LogFile)
$scriptName = [IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name)
$LogFilename = "$scriptName.log"
$LogFile = "$PSScriptRoot\Log\$LogFilename"

Write-Log -Text "Script [$scriptName.ps1] started ..." -File_LOG $LogFile
Write-Log -Text "Running on PowerShell version: $($Host.Version.Major)" -File_LOG $LogFile
Write-Log -Text "Value provided for parameter ConfigFile: $ConfigFile" -File_LOG $LogFile
if ($RequestedAction) {
    Write-Log -Text "Value provided for parameter RequestedAction: $RequestedAction" -File_LOG $LogFile
    $Action = $RequestedAction
} else {
    Write-Log -Text "No value provided for parameter RequestedAction ..." -File_LOG $LogFile
    Write-Log -Text "Action will be Start or Restart depending on whether HAProxy is running or not." -File_LOG $LogFile
}

Write-Log -Text "Reading default parameters from $ConfigFile" -File_LOG $LogFile
# Read XML file, the HAProxy part ;-)
$HAProxyConfig = ([xml](Get-Content $ConfigFile)).HAProxy

if ($Action -ne "Stop") {
    Write-Log -Text "Determining the base path for HAProxy ..." -File_LOG $LogFile
    if (Get-Process -Name "haproxy" -ErrorAction SilentlyContinue) {
        Write-Log -Text "HAProxy is running, get base path from current process." -File_LOG $LogFile
        $HAProxyBasePath = (Get-Process -Name "haproxy" | Select-Object Path | Split-Path) + "\"
        if ($HAProxyBasePath.EndsWith("\bin\")) {
            $HAProxyBasePath = $HAProxyBasePath.Replace("\bin\", "\")
        }
        $Action = "Restart"
    } else {
        Write-Log -Text "HAProxy is NOT running, get base path from configuration file." -File_LOG $LogFile
        $HAProxyBasePath = $HAProxyConfig.HAProxyBasePath
        $Action = "Start"
    }
} else {
    $HAProxyBasePath = $HAProxyConfig.HAProxyBasePath
}
Write-Log -Text "Base path for HAProxy is set to $HAProxyBasePath" -File_LOG $LogFile

Push-Location ($HAProxyBasePath)

Write-Log -Text "Effective action that will be taken is: $Action" -File_LOG $LogFile
switch ($Action) {
    "Restart" {
        Write-Log -Text "Restarting HAProxy ..." -File_LOG $LogFile
        $OldPID = (Get-Process -Name "haproxy" -ErrorAction SilentlyContinue).Id
        Write-Log -Text "Old PID is: $OldPID" -File_LOG $LogFile
        Get-Process -Name "haproxy" | Stop-Process
        Start-Sleep -Milliseconds 500
        Start-Process -FilePath ".\bin\haproxy.exe" -ArgumentList "-f .\Conf\haproxy.cfg -D"
        Start-Sleep -Milliseconds 2000
        $NewPID = (Get-Process -Name "haproxy" -ErrorAction SilentlyContinue).Id
        Write-Log -Text "New PID is: $NewPID" -File_LOG $LogFile
        if (Get-Process -Name "haproxy" -ErrorAction SilentlyContinue) {
            Write-Log -Text "HAProxy is running!" -File_LOG $LogFile
        } else {
            Write-Log -Text "ERROR : Something went wrong and HAProxy is NOT running anymore :-(" -File_LOG $LogFile
        }
        if ($NewPID -ne $OldPID) {
            Write-Log -Text "HAProxy has been restarted." -File_LOG $LogFile
        } else {
            Write-Log -Text "ERROR : Oops, something went wrong and HAProxy has not been restarted! (Same PID)" -File_LOG $LogFile
        }
        EndScript
    }

    "Start" {
        Write-Log -Text "Starting HAProxy ..." -File_LOG $LogFile
        Start-Process -FilePath ".\bin\haproxy.exe" -ArgumentList "-f .\Conf\haproxy.cfg -D"
        Start-Sleep -Milliseconds 2000
        $NewPID = (Get-Process -Name "haproxy" -ErrorAction SilentlyContinue).Id
        Write-Log -Text "New PID is: $NewPID" -File_LOG $LogFile
        if (Get-Process -Name "haproxy" -ErrorAction SilentlyContinue) {
            Write-Log -Text "HAProxy is running!" -File_LOG $LogFile
        } else {
            Write-Log -Text "ERROR : HAProxy is NOT running while it should ... Something went wrong, please check manually!" -File_LOG $LogFile
        }
        EndScript
    }

    "Stop" {
        Write-Log -Text "Stopping HAProxy ..." -File_LOG $LogFile
        if (Get-Process -Name "haproxy") {
            Write-Log -Text "HAProxy is running!" -File_LOG $LogFile
            Write-Log -Text "Stopping HAProxy ..." -File_LOG $LogFile
            Get-Process -Name "haproxy" | Stop-Process -Force
            Start-Sleep -Milliseconds 500
            if (Get-Process -Name "haproxy" -ErrorAction SilentlyContinue) {
                Write-Log -Text "ERROR : HAProxy is still running ... Something went wrong, please check manually!" -File_LOG $LogFile
            } else {
                Write-Log -Text "HAProxy successfully stopped." -File_LOG $LogFile
            }
        } else {
            Write-Log -Text "Nothing to do as HAProxy is not running." -File_LOG $LogFile
        }
        EndScript
    }

    Default {
        # Do nothing
        Write-Log -Text "ERROR : This part of the script should never be reached.  But apparently something went wrong :-(" -File_LOG $LogFile
        EndScript
    }
}

For logging purposes, the script uses a function which I located in a separate script.  The code of the separate logging script called "Write-Log.ps1":

.SYNOPSIS
    Writes a message on the screen and to a logfile.
.DESCRIPTION
    Writes a message on the screen and to a logfile.
.PARAMETER Text
    The message to display and to send to the logfile.
.PARAMETER File_LOG
    The full path to the actual logfile to use.
.EXAMPLE
    Write-Log -Text "This is the message to log." -File_LOG .\Write-Log.log
    This writes "This is the message to log." as follows:
       27/12/2021 | 23:55:00 | This is the message to log.
    (With the current timestamp, of course)
.NOTES
    FunctionName : Write-Log
    Created by   : Pascal Van Herck
    Date Coded   : 27/12/2021
.LINK
    Home


function Write-Log {
    [CmdletBinding()]
   
        Param (
            # String to log
            [Parameter(Mandatory)]
            [string[]]
            $Text,
       
            # Full path to logfile
            [Parameter(Mandatory)]
            [string[]]
            $File_LOG
        )
        $DATE = Get-Date -Format "dd/MM/yyyy | HH:mm:ss"
        $Message = $DATE + " | " + $Text
        Write-Host $Message
        Add-Content -Path $File_LOG -Value ($DATE + " | " + $Text)
    }

The HAProxy.ps1 script uses 2 parameters:

  • ConfigFile - The XML config file used by the script (not the HAPRoxy config file!).  This defaults to ".\HAProxyConfig.xml" or in other words, to a file called HAProxyConfig.xml located in the same folder as the script.
  • RequestedAction - The action that the script must execute.  This can either be Stop, Start or Restart.  If the parameter RequestedAction is omitted, either Start or Restart will be used as default action, depending on whether HAProxy is already running or not.

The configuration XML is used to tell the script where HAProxy is located.

<HAProxy>
  <HAProxyBasePath>E:\HAProxy</HAProxyBasePath>
</HAProxy>

The script then looks for the binaries in the bin folder located in this path (E:\HAProxy\bin)

Logging of the script will be located in a subfolder called "Logs" and the logfile will be called "[ScriptName].log"

Assumed that the XML configuration file is in the default location and has the default name, you can start HAProxy as follows:

.\HAProxy.ps1 -RequestedAction Start

The Scheduled Task

If you run HAProxy via the PowerShell script, it will run HAProxy in the current user context.  On itself that is not really a problem, but it will have as a side effect that HAProxy will stop working when the user logs off.  Furthermore, you will have to start the script manually each time you log on again.

That is the reason why we will start HAProxy via the PowerShell script through a scheduled task.  This way the task can be ran as the system account and HAProxy will keep on running whether a user is logged on or not.

The most important properties of the scheduled task:

  • User account: SYSTEM
  • Trigger: At system startup
  • Action: 
    • Program/script: "C:\Program Files\PowerShell\7\pwsh.exe"
    • Add arguments (optional): -NoLogo -File .\HAProxy.ps1 -RequestedAction Start
    • Start in (optional): E:\HAProxy

To stop HAProxy after it is started by the scheduled task, you can stop HAProxy by either killing the process through task manager or by the PowerShell script.  Be aware that the script now must be executed with administrative privileges as the process is running with the system account:

.\HAProxy.ps1 -RequestedAction Stop

To start HAProxy again, just trigger the scheduled task.

 

Choose language:

English (United States)
[en-US]
Dutch (Belgium)
[nl-BE]

Random Quote

Have you ever noticed? Anybody going slower than you is an idiot, and anyone going faster than you is a maniac.

By: George Carlin.