| name | linux-systemd-manager |
| description | Use when user wants to create, edit, debug, or optimize a systemd service, timer, socket, mount, or path unit — or asks how to run something as a service, auto-restart a process, schedule tasks with systemd timers, understand journalctl output, troubleshoot a failed unit, or manage systemd targets and dependencies. |
| version | 1.2.0 |
| author | Lehnert |
Linux Systemd Manager
Overview
Writes, debugs, and optimizes systemd units of all types. Covers service sandboxing, security hardening, resource limits, restart policies, timer scheduling, socket activation, and dependency management. Writes all unit files to disk with the exact install commands to activate them.
Language: Respond in the user's language. Unit files and comments in English.
When to Use
- User wants to run any process as a systemd service
- User wants to schedule tasks with a systemd timer (cron replacement)
- User has a failed or misbehaving service and wants to debug it
- User wants to harden an existing service unit
- User asks "how do I auto-start X on boot?"
- User wants socket activation, path units, or mount units
- User wants to understand or use
journalctl
When NOT to Use
- User only wants simple cron scheduling → /linux-cron-manager
- User wants to audit a service config file →
linux-config-auditor
- User wants to write the application being run →
linux-shell-scriptor
Step 1 — Detect Unit Type
Map the user's request to the correct unit type:
| User says | Unit type |
|---|
| "run as a service", "auto-start on boot", "keep process running" | .service |
| "run on a schedule", "every hour", "cron replacement" | .timer + .service |
| "start when file changes", "watch a directory" | .path + .service |
| "socket activation", "start on connection" | .socket + .service |
| "mount a filesystem", "auto-mount" | .mount or .automount |
| "debug a failed service", "why did it crash?" | Debugging workflow |
| "harden my service unit" | Security hardening audit |
Step 2 — Gather Context
Ask at most one question if critical info is missing:
- What command/binary does the service run?
- Which user should it run as?
- Does it need to start after a specific service (database, network)?
If the user gives enough context (command, user, schedule), generate immediately.
Service Unit — Full Template
Always generate a complete, well-commented unit. Never produce a bare-minimum unit.
[Unit]
Description=My Application Service
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=3
EnvironmentFile=-/etc/myapp/myapp.env
Environment="NODE_ENV=production"
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
LimitNOFILE=65536
LimitNPROC=4096
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/myapp /var/log/myapp
StateDirectory=myapp
LogsDirectory=myapp
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
CapabilityBoundingSet=
AmbientCapabilities=
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
[Install]
WantedBy=multi-user.target
Service Types Reference
| Type | When to use | ExecStart behavior |
|---|
simple | Most apps — process runs in foreground | PID of ExecStart is the main process |
forking | Old-style daemons that fork to background | Parent exits after fork; use PIDFile= |
oneshot | Scripts that run and exit (batch jobs) | systemd waits for exit before active |
notify | Apps using sd_notify() to signal ready | Process sends READY=1 when ready |
exec | Like simple but waits for exec to succeed | More precise readiness tracking |
idle | Delay start until job queue is empty | Avoids boot output clutter |
Timer Unit (Cron Replacement)
Always generate a timer + its companion service as a pair.
Naming rule: The timer and service must share the same base name — myapp-task.timer activates myapp-task.service automatically. No Requires= needed in the timer.
/etc/systemd/system/myapp-task.service:
[Unit]
Description=My App Scheduled Task
After=network-online.target
[Service]
Type=oneshot
User=appuser
ExecStart=/opt/myapp/bin/myapp --task run
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp-task
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
/etc/systemd/system/myapp-task.timer:
[Unit]
Description=Run My App Task daily at 2 AM
[Timer]
OnCalendar=*-*-* 02:00:00
AccuracySec=1min
RandomizedDelaySec=30s
Persistent=true
[Install]
WantedBy=timers.target
OnCalendar quick reference:
| Schedule | Expression |
|---|
| Every minute | *:0/1 |
| Every 5 minutes | *:0/5 |
| Every hour at :00 | hourly |
| Daily at 2 AM | *-*-* 02:00:00 |
| Every Monday 3 AM | Mon *-*-* 03:00:00 |
| 1st of month at midnight | *-*-01 00:00:00 |
| Weekdays at 9 AM | Mon..Fri *-*-* 09:00:00 |
Verify expression: systemd-analyze calendar "Mon *-*-* 03:00:00"
Socket Activation Unit
Allows systemd to listen on a port or Unix socket and start the service only when a connection arrives.
Accept=no vs Accept=yes:
| Value | Behavior | Use case |
|---|
Accept=no | One service instance handles all connections | Long-lived servers (web apps, databases) |
Accept=yes | Spawn one service instance per connection | Simple per-connection handlers (inetd style) |
Most modern apps use Accept=no.
TCP socket (/etc/systemd/system/myapp.socket):
[Unit]
Description=My App Socket
[Socket]
ListenStream=8080
Accept=no
SocketUser=appuser
[Install]
WantedBy=sockets.target
Unix domain socket (for local IPC — nginx, PHP-FPM, gunicorn):
[Unit]
Description=My App Unix Socket
[Socket]
ListenStream=/run/myapp/myapp.sock
SocketMode=0660
SocketUser=appuser
SocketGroup=www-data
[Install]
WantedBy=sockets.target
/etc/systemd/system/myapp.service (socket-activated):
[Unit]
Description=My App (socket-activated)
Requires=myapp.socket
After=myapp.socket
[Service]
Type=simple
User=appuser
ExecStart=/opt/myapp/bin/myapp
StandardInput=socket
NoNewPrivileges=yes
PrivateTmp=yes
RuntimeDirectory=myapp
Path Unit (Watch Files or Directories)
Triggers a service when a file or directory changes.
/etc/systemd/system/watch-uploads.path:
[Unit]
Description=Watch /var/uploads for new files
[Path]
PathExistsGlob=/var/uploads/*.pending
DirectoryNotEmpty=/var/uploads
Unit=process-uploads.service
[Install]
WantedBy=multi-user.target
Security Hardening Options
When auditing or hardening an existing unit, check and add these options:
| Option | Effect | Recommended |
|---|
NoNewPrivileges=yes | Blocks privilege escalation via setuid/capabilities | Always |
PrivateTmp=yes | Gives service its own /tmp — isolates temp files | Always |
ProtectSystem=strict | Mounts /, /usr, /boot read-only | Always for non-system services |
ProtectHome=yes | Blocks access to /home, /root, /run/user | Always unless needed |
ReadWritePaths= | Whitelist specific writable paths | Required with ProtectSystem=strict |
PrivateDevices=yes | No access to device nodes | For most services |
ProtectKernelTunables=yes | Blocks /proc/sys writes | Always |
ProtectKernelModules=yes | Blocks module loading | Always |
ProtectControlGroups=yes | Blocks cgroup changes | Always |
RestrictAddressFamilies= | Limit network families | Set to AF_INET AF_INET6 for web services |
CapabilityBoundingSet= | Drop all Linux capabilities | Set unless specific cap needed |
SystemCallFilter=@system-service | Block dangerous syscalls | Always for app services |
MemoryDenyWriteExecute=yes | Block JIT/self-modifying code | Unless JVM/JS runtime |
User= | Run as non-root | Always |
LimitNOFILE= | Set open file descriptor limit | For high-connection services |
MemoryMax= | Cap memory usage | For resource-controlled services |
CPUQuota= | Cap CPU percentage | For background services |
Check current security score: systemd-analyze security myservice.service
Debugging Workflow
When a service fails, run these in order:
systemctl status myservice.service
journalctl -u myservice.service -n 100 --no-pager
journalctl -u myservice.service -f &
systemctl start myservice.service
systemctl list-dependencies myservice.service
systemctl --failed
systemd-analyze verify /etc/systemd/system/myservice.service
systemctl show myservice.service
systemctl cat myservice.service
sudo -u appuser /opt/myapp/bin/myapp --config /etc/myapp/config.yaml
systemd-analyze security myservice.service
systemd-analyze blame
systemd-analyze critical-chain myservice.service
systemd-cgls
systemctl status myservice.service
Common failure causes:
| Error | Likely cause | Fix |
|---|
code=exited, status=1 | App crashed — check logs | journalctl -u service -n 50 |
code=exited, status=200/CHDIR | WorkingDirectory doesn't exist | Create the directory |
code=exited, status=203/EXEC | ExecStart binary not found or not executable | Check path and permissions |
start request repeated too quickly | Crash loop — app failing immediately | Fix app error, set StartLimitBurst |
Permission denied | User lacks access to file/socket | Check ReadWritePaths, user permissions |
Failed to set up namespace | Sandboxing conflicts with app | Disable ProtectSystem or add ReadWritePaths |
Output Format
Write all unit files to ./systemd/ in the current working directory.
systemd/
myservice.service
myservice.timer (if timer requested)
myservice.socket (if socket activation requested)
myservice.path (if path unit requested)
Then print ONLY:
✅ Unit files written to ./systemd/
▶ Install and enable:
sudo cp systemd/*.service systemd/*.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now myservice.service
[sudo systemctl enable --now myservice.timer]
▶ Verify:
systemctl status myservice.service
journalctl -u myservice.service -n 20
▶ Security score:
systemd-analyze security myservice.service
💡 Next: /linux-config-auditor to review the service config file · /linux-security-hardener for host-level hardening