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.

Infrastructure Services

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.

Try It — Systemd Unit File Generator

Open full tool