You rebooted and your service is gone
You upgraded a package, rebooted, and your custom web server vanished from the process list. Or you ran sudo systemctl restart nginx and got a wall of red text instead of a clean prompt. The system is not broken. The init system follows a strict dependency graph, and you are trying to push a unit into a state it refuses to accept. This happens to everyone who moves from manual startup scripts to systemd. The fix is usually a missing dependency, a misconfigured executable path, or a stale configuration cache.
What systemd actually does
systemd is PID 1. It does not just start programs. It builds a dependency tree and activates units in parallel when their requirements are met. Think of it like a construction site foreman. The foreman does not hand out blueprints and walk away. The foreman checks that the foundation is poured before allowing the framing crew to show up. In Fedora, every service, mount, timer, and socket is a unit file. The file tells the foreman what to run, when to run it, and what happens if it crashes.
Unit files live in two directories. Package maintainers ship defaults to /usr/lib/systemd/system/. You place overrides and custom units in /etc/systemd/system/. Always edit /etc/. Package updates overwrite /usr/lib/ without warning, and your manual changes will disappear on the next dnf upgrade --refresh. The system reads /etc/ first, so your overrides take precedence without touching vendor files.
Targets group units together and represent system states. They replaced the old runlevel concept. Fedora uses multi-user.target for a standard server or desktop without a graphical login, and graphical.target for a full desktop environment. rescue.target drops you to a single-user shell with root access. emergency.target mounts the root filesystem read-only and gives you a minimal shell for disk repairs. The default target is set at boot time. You can change it permanently or switch to it temporarily while the system is running.
Managing units without guessing
The terminal commands for systemd follow a predictable pattern. You check the state, you change the state, and you verify the result. Never skip the status check. Restarting a broken service just to see it fail again wastes time and obscures the original error.
Here is how to inspect a unit before you touch it.
# Show current state, recent logs, and dependency status in one view
systemctl status sshd.service
# Check if the unit is currently running
systemctl is-active sshd.service
# Check if the unit is set to start on boot
systemctl is-enabled sshd.service
The status command shows the active state, the sub-state, the main PID, and the last five log lines. If the service is dead, the output tells you why it stopped. If it is active, it shows how long it has been running. The is-active and is-enabled commands return plain text. They are safe to use in scripts and aliases.
Here is how to change the runtime state and the boot behavior.
# Start the unit immediately without changing boot behavior
sudo systemctl start sshd.service
# Stop the unit immediately without changing boot behavior
sudo systemctl stop sshd.service
# Enable the unit to start automatically on boot
sudo systemctl enable sshd.service
# Disable the unit from starting on boot
sudo systemctl disable sshd.service
# Enable and start in a single step
sudo systemctl enable --now sshd.service
The enable command creates a symlink in /etc/systemd/system/. It does not start the service. The --now flag combines enable and start. Use it when you are deploying a new service and want it running immediately and surviving reboots.
Here is how to read the logs for a specific unit.
# Show logs for the unit, jump to the end, and add explanatory hints
journalctl -xeu sshd.service
# Follow new log lines in real time
journalctl -feu sshd.service
The -x flag adds explanatory text to priority messages. The -e flag jumps to the end of the journal. The -u flag filters by unit name. Most sysadmins type journalctl -xeu <unit> muscle-memory style. It cuts through the noise and shows exactly what the service printed to standard error.
Verify the state before you change it
Run systemctl status first. Read the actual error before guessing. A botched restart can leave a service in a failed state that blocks dependent units. If you see active (running), the process is alive and listening. If you see inactive (dead), the process exited normally or was never started. If you see failed, systemd caught a non-zero exit code or a signal termination.
Check the boot configuration with systemctl is-enabled. The output will be enabled, disabled, static, or masked. static means the unit has no install section and must be pulled in by another unit. masked means the unit is symlinked to /dev/null and cannot start under any circumstances. Unmask it before you try to enable it.
Reload the systemd manager when you edit a unit file. The manager caches unit definitions in memory. It does not watch the filesystem for changes.
# Reread all unit files and rebuild the dependency tree
sudo systemctl daemon-reload
Run daemon-reload after every edit to /etc/systemd/system/. Skip it and you will spend twenty minutes debugging a configuration that systemd never saw.
When a unit refuses to start
systemd will not start a unit if the dependency graph is broken or the executable path is wrong. The error messages are specific. Read them carefully.
Job for myapp.service failed because the control process exited with error code.
See "systemctl status myapp.service" and "journalctl -xeu myapp.service" for details.
This message means the process launched but crashed immediately. Check the journal for the actual crash reason. It is usually a missing library, a permission denial, or a syntax error in the application config.
Dependency failed for My Application.
See "systemctl status myapp.service" and "journalctl -xeu myapp.service" for details.
This message means a unit listed in After= or Requires= failed to start. systemd stops the chain to prevent cascading failures. Fix the dependency first.
Failed to start myapp.service: Unit myapp.service not found.
This message means the unit file does not exist in /etc/systemd/system/ or /usr/lib/systemd/system/. Check the spelling. systemd is case-sensitive. The .service suffix is optional in commands but required in file names.
SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. A missing file context or an incorrect port label will cause a service to fail silently. Restore the context with restorecon -Rv /path/to/config or check the audit log for the exact denial.
Writing a custom service unit
You do not need to memorize every directive. Most services follow the same three-section structure. The [Unit] section defines metadata and dependencies. The [Service] section defines how the process runs. The [Install] section defines when the service starts automatically.
Here is how to create a minimal, production-ready unit file.
[Unit]
# Human-readable name shown in status output
Description=My Custom Application
# Wait until the network stack is up before starting
After=network.target
[Service]
# Absolute path to the executable
ExecStart=/usr/local/bin/myapp
# Restart if the process exits with a non-zero code
Restart=on-failure
# Run as an unprivileged user instead of root
User=myappuser
# Drop capabilities and restrict the namespace
ProtectSystem=strict
ReadWritePaths=/var/lib/myapp
[Install]
# Start this unit when the multi-user target is reached
WantedBy=multi-user.target
Place the file in /etc/systemd/system/myapp.service. Set the correct permissions. systemd refuses to load unit files that are world-writable or owned by the wrong user.
# Set ownership to root and remove group/other write permissions
sudo chown root:root /etc/systemd/system/myapp.service
sudo chmod 644 /etc/systemd/system/myapp.service
# Reload the manager and enable the service
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
Test the unit in a terminal first. Run the executable manually with the same user and environment variables. systemd does not inherit your interactive shell profile. It uses a clean environment. Hardcode paths or use Environment= directives inside the unit file.
User services and boot analysis
Regular users can manage their own units without root. Place unit files in ~/.config/systemd/user/ and manage them with the --user flag. User services start when you log in and stop when you log out. They run under your UID and GID. They cannot bind to privileged ports or access system-wide resources.
# Enable a user service for your login session
systemctl --user enable --now myapp.service
# View logs for your user units
journalctl --user -u myapp.service
Use user services for development servers, personal daemons, and GUI applications that do not require root. Keep system services for network-facing daemons, hardware drivers, and system-wide configuration.
Boot performance analysis helps you find slow units. The systemd-analyze command breaks down the boot timeline.
# List units sorted by startup time, slowest first
systemd-analyze blame
# Show the critical dependency chain that blocks the desktop
systemd-analyze critical-chain
The blame output shows how long each unit took to activate. A unit taking longer than two seconds usually indicates a network timeout, a heavy database initialization, or a missing hardware device. The critical-chain output shows the longest path from systemd to your default target. Fix the bottleneck at the top of the chain. Do not optimize units that are not on the critical path.
Choosing the right tool for the job
Use systemctl start when you need a service running immediately for testing or temporary work. Use systemctl enable when you want the service to survive a reboot without starting it right now. Use systemctl enable --now when you are deploying a new service and want it active immediately. Use systemctl daemon-reload after every unit file edit or symlink change. Use systemctl isolate when you need to switch the entire system to a different target state, like dropping to a rescue shell. Use systemctl --user when you are managing personal daemons that run under your login session and do not require root privileges. Use journalctl -xeu when a service fails and you need the exact error context. Use systemd-analyze blame when boot takes longer than expected and you need to find the slowest units.