Overview
Daemon deployment allows you to run Horse applications as background services on Linux systems. Daemons automatically start on boot, run without user interaction, and provide production-ready service management with proper logging and signal handling.
Daemon deployment uses the HORSE_DAEMON conditional define on Linux. For Windows services, use the Windows Service provider instead.
When to Use Daemon Deployment
Production Linux Servers : Background services that start on boot
Systemd Integration : Native integration with systemd service manager
Unattended Operation : Services that run without user interaction
Server Applications : APIs and microservices on Linux
Process Management : Automatic restart on failure
Logging Integration : System logging via syslog
Platform Deployment Type Configuration Linux Daemon with systemd HORSE_DAEMONWindows Windows Service Use WinSvc sample (separate from HORSE_DAEMON) macOS launchd Service Use daemon mode with launchd config
Required Conditional Define
For Linux daemon deployment, you must add HORSE_DAEMON to your project’s conditional defines.
Open Project Options
Go to Project → Options → Delphi Compiler → Compiling
Add Conditional Define
Add: HORSE_DAEMON
Set Target Platform
Set target to Linux64 or appropriate Linux platform
Rebuild Project
Clean and rebuild your project
Linux Daemon Example
Delphi Implementation
program Daemon;
{$APPTYPE CONSOLE}
{$R *.res}
uses Horse, System.SysUtils;
begin
// Need to set "HORSE_DAEMON" compilation directive
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
THorse.Listen;
end.
Lazarus/FPC Implementation
program daemon;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils, Horse;
procedure GetPing (Req: THorseRequest; Res: THorseResponse);
begin
Res.Send( 'pong' );
end ;
procedure OnListen ;
begin
if THorse.IsRunning then
Writeln(Format( 'Server is running on %s:%d' , [THorse.Host, THorse.Port]));
if not THorse.IsRunning then
Writeln( 'Server stopped' );
end ;
var
sCMD: string ;
bTerminated: Boolean ;
begin
// Need to set "HORSE_DAEMON" compilation directive
THorse.Get( '/ping' , @GetPing);
bTerminated := False ;
WriteLn( 'COMMANDS: START, STOP, TERMINATE' );
while not bTerminated do
begin
ReadLn(sCMD);
case sCMD.ToUpper() of
'START' : THorse.Listen( 9000 , @OnListen);
'STOP' : THorse.StopListen;
'TERMINATE' : bTerminated := True ;
end ;
end ;
end .
Advanced Daemon with Full Configuration
program ProductionDaemon;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Classes,
System.JSON,
Horse,
Horse.Jhonson,
Horse.CORS,
Horse.Logger,
Horse.Logger.Provider.Console;
begin
// Configure logging
THorseLoggerManager.RegisterProvider(
THorseLoggerProviderConsole.New
);
THorse.Use(THorseLoggerManager.HorseCallback);
// Enable CORS
THorse.Use(CORS);
// Enable JSON parsing
THorse.Use(Jhonson);
// Configure server
THorse.Port := 9000;
THorse.Host := '0.0.0.0';
THorse.MaxConnections := 1000;
// Health check endpoint
THorse.Get('/health',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('{"status": "healthy", "type": "daemon"}');
end);
// API endpoints
THorse.Get('/api/status',
procedure(Req: THorseRequest; Res: THorseResponse)
var
Status: TJSONObject;
begin
Status := TJSONObject.Create;
try
Status.AddPair('uptime', DateTimeToStr(Now));
Status.AddPair('port', THorse.Port.ToString);
Status.AddPair('host', THorse.Host);
Status.AddPair('running', TJSONBool.Create(THorse.IsRunning));
Res.Send<TJSONObject>(Status);
finally
Status.Free;
end;
end);
THorse.Get('/api/users',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('[{"id": 1, "name": "User 1"}, {"id": 2, "name": "User 2"}]');
end);
// Start daemon
THorse.Listen(
procedure
begin
// Logged to syslog
end);
end.
Systemd Service Configuration
Create Service File
Create /etc/systemd/system/horse-api.service:
[Unit]
Description =Horse API Daemon
After =network.target
[Service]
Type =forking
User =www-data
Group =www-data
WorkingDirectory =/opt/horse-api
ExecStart =/opt/horse-api/daemon
ExecStop =/bin/kill -TERM $MAINPID
Restart =on-failure
RestartSec =5s
# Security settings
PrivateTmp =true
NoNewPrivileges =true
ProtectSystem =strict
ProtectHome =true
ReadWritePaths =/var/log/horse-api
# Resource limits
LimitNOFILE =65536
LimitNPROC =4096
# Logging
StandardOutput =syslog
StandardError =syslog
SyslogIdentifier =horse-api
[Install]
WantedBy =multi-user.target
Service Management
Install Service
# Copy executable
sudo mkdir -p /opt/horse-api
sudo cp daemon /opt/horse-api/
sudo chmod +x /opt/horse-api/daemon
# Create log directory
sudo mkdir -p /var/log/horse-api
sudo chown www-data:www-data /var/log/horse-api
# Copy service file
sudo cp horse-api.service /etc/systemd/system/
Enable and Start Service
# Reload systemd
sudo systemctl daemon-reload
# Enable service (start on boot)
sudo systemctl enable horse-api
# Start service
sudo systemctl start horse-api
# Check status
sudo systemctl status horse-api
Verify Service
# Check if running
curl http://localhost:9000/health
# View logs
sudo journalctl -u horse-api -f
Signal Handling
The daemon provider automatically handles Unix signals:
Signal Action SIGTERM Graceful shutdown SIGHUP Reload configuration (logged) SIGCHLD Ignored (child processes)
Custom Signal Handlers
uses
Posix.Signal,
Posix.SysTypes;
var
CustomShutdown: Boolean;
procedure HandleCustomSignal(SigNum: Integer); cdecl;
begin
case SigNum of
SIGUSR1:
begin
// Custom signal handling
Writeln('Received SIGUSR1');
end;
SIGTERM:
begin
CustomShutdown := True;
THorse.StopListen;
end;
end;
end;
begin
// Register custom handler
Signal(SIGUSR1, TSignalHandler(@HandleCustomSignal));
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
THorse.Listen;
end.
Logging with Syslog
Daemon provider uses syslog for system integration:
uses
ThirdParty.Posix.Syslog;
begin
// Open syslog connection
openlog('horse-api', LOG_PID or LOG_NDELAY, LOG_DAEMON);
// Log messages (done automatically by provider)
Syslog(LOG_INFO, 'Server starting');
Syslog(LOG_ERR, 'Error occurred');
Syslog(LOG_NOTICE, 'Configuration reloaded');
// Configure routes
THorse.Get('/test',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Syslog(LOG_INFO, 'Test endpoint accessed');
Res.Send('OK');
end);
THorse.Listen;
// Close syslog (done automatically)
closelog();
end.
View Syslog Messages
# View all logs
sudo journalctl -u horse-api
# Follow logs in real-time
sudo journalctl -u horse-api -f
# Filter by priority
sudo journalctl -u horse-api -p err
# View logs from today
sudo journalctl -u horse-api --since today
# Export logs
sudo journalctl -u horse-api --since "2026-01-01" > logs.txt
Process Management
Daemon Process Flow
Fork Process : Create background process
Create Session : Detach from terminal
Fork Again : Ensure process is not session leader
Close Descriptors : Close standard input/output/error
Set umask : Configure file permissions
Change Directory : Set working directory to /
Start Server : Begin listening for requests
PID File Management
# Create PID file
echo $$ > /var/run/horse-api.pid
# Read PID
PID = $( cat /var/run/horse-api.pid )
# Send signals
kill -TERM $PID # Graceful shutdown
kill -HUP $PID # Reload configuration
Configuration Management
Configuration File
Create /etc/horse-api/config.json:
{
"server" : {
"host" : "0.0.0.0" ,
"port" : 9000 ,
"maxConnections" : 1000
},
"logging" : {
"level" : "info" ,
"facility" : "daemon"
},
"security" : {
"enableSSL" : false ,
"certFile" : "/etc/horse-api/cert.pem" ,
"keyFile" : "/etc/horse-api/key.pem"
}
}
Load Configuration
uses
System.JSON,
System.IOUtils;
var
ConfigFile: string;
ConfigJSON: TJSONObject;
ServerConfig: TJSONObject;
begin
// Load configuration
ConfigFile := '/etc/horse-api/config.json';
if TFile.Exists(ConfigFile) then
begin
ConfigJSON := TJSONObject.ParseJSONValue(
TFile.ReadAllText(ConfigFile)
) as TJSONObject;
try
ServerConfig := ConfigJSON.GetValue('server') as TJSONObject;
THorse.Port := ServerConfig.GetValue<Integer>('port');
THorse.Host := ServerConfig.GetValue<string>('host');
THorse.MaxConnections := ServerConfig.GetValue<Integer>('maxConnections');
finally
ConfigJSON.Free;
end;
end;
// Configure routes
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
THorse.Listen;
end.
SSL/TLS Configuration
uses
Horse,
IdSSLOpenSSL;
begin
// Configure SSL
THorse.IOHandleSSL
.CertFile('/etc/horse-api/cert.pem')
.KeyFile('/etc/horse-api/key.pem')
.SSLVersions([sslvTLSv1_2, sslvTLSv1_3])
.Active(True);
THorse.Get('/secure',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('Secure connection');
end);
THorse.Listen(443);
end.
See SSL/TLS Configuration for detailed SSL setup.
Monitoring and Health Checks
Health Check Endpoint
THorse.Get('/health',
procedure(Req: THorseRequest; Res: THorseResponse)
var
Health: TJSONObject;
begin
Health := TJSONObject.Create;
try
Health.AddPair('status', 'healthy');
Health.AddPair('timestamp', DateTimeToStr(Now));
Health.AddPair('uptime', IntToStr(GetTickCount div 1000) + 's');
Res.Send<TJSONObject>(Health);
finally
Health.Free;
end;
end);
Monitoring with systemd
# Check service status
systemctl status horse-api
# Check if service is active
systemctl is-active horse-api
# View failed services
systemctl --failed
# Restart on failure
sudo systemctl restart horse-api
Troubleshooting
Service Won’t Start
Check Logs
sudo journalctl -u horse-api -n 50
sudo journalctl -xe
Verify Permissions
ls -la /opt/horse-api/daemon
sudo -u www-data /opt/horse-api/daemon # Test as service user
Check Port Availability
sudo netstat -tuln | grep 9000
sudo lsof -i :9000
Test Manually
# Run without daemon mode for debugging
/opt/horse-api/daemon
Port Permission Denied
# For ports < 1024, use capability instead of root
sudo setcap 'cap_net_bind_service=+ep' /opt/horse-api/daemon
# Or use port forwarding
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 9000
Memory Leaks
{$IFDEF DEBUG}
ReportMemoryLeaksOnShutdown := True;
{$ENDIF}
Security Best Practices
Always run daemons with minimal privileges:
[Service]
# Run as non-root user
User =www-data
Group =www-data
# Security hardening
PrivateTmp =true
NoNewPrivileges =true
ProtectSystem =strict
ProtectHome =true
ProtectKernelTunables =true
ProtectKernelModules =true
ProtectControlGroups =true
# Restrict capabilities
CapabilityBoundingSet =CAP_NET_BIND_SERVICE
AmbientCapabilities =CAP_NET_BIND_SERVICE
Next Steps
SSL Configuration Secure your daemon with HTTPS
Console Deployment Standard console application deployment