ExecStart, ExecStop, and Exec Directives
Deep dive into systemd Exec directives: ExecStart, ExecStartPre, ExecStartPost, ExecStop, ExecStopPost, and ExecReload. Learn prefix modifiers, multiple commands, and error handling.
Detailed Explanation
Mastering systemd Exec Directives
The Exec*= directives control the full lifecycle of a service: what happens before start, during start, after start, during reload, and during stop.
The Exec Directive Family
| Directive | When It Runs | Runs As |
|---|---|---|
ExecStartPre= |
Before ExecStart | Service user (or root for + prefix) |
ExecStart= |
Main process | Service user |
ExecStartPost= |
After ExecStart succeeds | Service user |
ExecReload= |
On systemctl reload |
Service user |
ExecStop= |
On systemctl stop |
Service user |
ExecStopPost= |
After service stops | Service user |
Prefix Modifiers
Prefix characters modify how systemd handles command exit codes:
| Prefix | Meaning |
|---|---|
- |
Ignore non-zero exit code (don't fail) |
+ |
Run as root (even if User= is set) |
! |
Don't apply NoNewPrivileges |
!! |
Don't apply NoNewPrivileges and run with elevated capabilities |
Examples:
# Ignore failure (useful for cleanup commands)
ExecStartPre=-/usr/bin/docker stop mycontainer
# Run pre-start as root (e.g., to create directories)
ExecStartPre=+/usr/bin/mkdir -p /run/myapp
# Combine: ignore failure and run as root
ExecStartPre=-+/usr/bin/rm -f /run/myapp/old.pid
Multiple ExecStartPre Commands
You can specify multiple ExecStartPre= directives. They run sequentially:
ExecStartPre=/usr/bin/test -f /etc/myapp/config.yaml
ExecStartPre=/usr/sbin/nginx -t -q
ExecStartPre=+/usr/bin/mkdir -p /run/myapp
ExecStart=/usr/local/bin/myapp
Order: test config exists → test nginx config → create runtime dir → start app
If any non-prefixed-with-- command fails, the startup is aborted.
ExecStartPost for Verification
ExecStartPost= runs after ExecStart has been started (for simple) or after the parent has exited (for forking):
ExecStart=/usr/local/bin/myapp
ExecStartPost=/usr/bin/curl -sf http://localhost:8080/health
This verifies the application is actually responding after startup.
ExecStop and Graceful Shutdown
By default, systemd sends SIGTERM to the main process. Custom ExecStop replaces this:
# Send SIGQUIT for graceful shutdown
ExecStop=/bin/kill -s QUIT $MAINPID
# Use the application's own shutdown command
ExecStop=/usr/local/bin/myapp shutdown --graceful
ExecStopPost for Cleanup
ExecStopPost= always runs after the service stops, even on failure:
ExecStopPost=/usr/bin/rm -f /run/myapp/socket
ExecStopPost=-/usr/bin/docker rm mycontainer
This is the right place for cleanup that must happen regardless of how the service stopped.
ExecReload for Live Configuration
# Signal-based reload (Nginx, HAProxy)
ExecReload=/bin/kill -s HUP $MAINPID
# Command-based reload
ExecReload=/usr/local/bin/myapp reload-config
# Multi-step reload
ExecReload=/usr/sbin/nginx -t -q
ExecReload=/bin/kill -s HUP $MAINPID
Special Variables
| Variable | Value |
|---|---|
$MAINPID |
PID of the main service process |
$SERVICE_RESULT |
Exit result (in ExecStopPost) |
$EXIT_CODE |
Exit code (in ExecStopPost) |
$EXIT_STATUS |
Exit status number (in ExecStopPost) |
Use Case
Building complex service lifecycle management with pre-start validation, post-start health checks, graceful shutdown procedures, and post-stop cleanup for production services.