Managing Linux services comes down to one command: systemctl. It talks to systemd, the init system that runs as PID 1 on Ubuntu, Debian, Fedora, RHEL, AlmaLinux, and Rocky Linux. Learn a dozen subcommands and you can start, stop, enable, inspect, and repair almost any service on a Linux server.

Quick Answer: The systemctl Commands You Actually Need

The systemctl command manages systemd services on Linux. Check a service with status, control it with start, stop, or restart, and use enable --now to start it immediately and at every boot. Replace nginx with whatever service you're managing.

sudo systemctl status nginx
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl enable --now nginx

To see what's currently running, use this:

systemctl list-units --type=service --state=running

Key takeaway: start controls the current session. enable controls future boots. They're not the same thing, and confusing them is the single most common mistake I see.

What Are systemd and the systemctl Command?

systemd is the init system and service manager on most modern Linux distributions. It's the first process the kernel starts, so it holds PID 1, and everything else on the system descends from it. It replaced SysV init on the major distros roughly a decade ago.

systemctl is the client. You don't manage systemd by editing runlevels or poking at scripts in /etc/init.d — you send instructions to systemd through systemctl.

Hierarchy of systemd PID 1 branching to unit types and nginx worker processes.

Units, not just services

systemd manages "units," and a service is only one kind. You'll also run into sockets (.socket), timers (.timer, the modern cron alternative), mount points (.mount), paths (.path), and targets (.target), which group units together the way runlevels used to. The multi-user.target is the normal boot state for a server.

systemctl vs. service: which should you use?

The old service and chkconfig commands still work on many systems, but only as thin compatibility wrappers. They quietly hand off to systemctl and hide half the useful output.

Capability systemctl service / chkconfig
Start, stop, restart Yes Yes (via wrapper)
Boot enablement enable / disable chkconfig, RHEL-family only
Show recent log lines Yes, in status No
Block activation entirely mask No equivalent
Non-service units (timers, sockets) Yes No
Dependency inspection list-dependencies No

Use systemctl. There's no reason to keep the legacy muscle memory alive.

System services vs. systemctl --user

Everything above manages system-wide units owned by root. Each logged-in user also gets a personal systemd instance for things like sync agents or a development server. Those use the --user flag and unit files in ~/.config/systemd/user/, and they need no sudo. One caveat: user units normally stop when the session ends unless you enable lingering with loginctl enable-linger USERNAME.

Prerequisites for Managing Linux Services

You need a systemd-based distribution, terminal or SSH access, and a user with sudo privileges for anything that changes state. Read-only commands like status and list-units usually work fine without elevation. If sudo is new to you, read up on how to use sudo in Linux first, along with the basic Linux commands you'll be pairing with it.

Confirm systemd is actually in charge:

ps -p 1 -o comm=
systemctl --version

The first command should print systemd. If it prints init, bash, or something else, systemctl won't manage that environment — common in minimal Docker containers, chroots, older releases still on SysV init or Upstart, and WSL installs where systemd hasn't been switched on.

Warning: don't stop ssh, sshd, networking, or firewall services while you're connected remotely. I've locked myself out of a box that way, and recovering it meant a console session through the provider panel. Test disruptive commands where you have out-of-band access.

If you're practising on a remote machine, a Linux VPS with root access and a sudo-enabled non-root account is the right setup. Keep console access handy before touching SSH or network units.

systemctl Syntax and Finding the Right Service Name

The pattern is simple:

systemctl [OPTIONS] COMMAND [UNIT...]
sudo systemctl restart nginx.service

You can usually drop the .service suffix — systemd infers it for service subcommands. But you must include the suffix for other unit types, so backup.timer and docker.socket need spelling out in full.

Half the failed commands I see are just wrong names. Stop guessing and search instead:

systemctl list-unit-files --type=service | grep -i ssh
systemctl cat nginx
systemctl show -p FragmentPath nginx

Naming differs by distro family, and that trips people up constantly. On Debian and Ubuntu, OpenSSH is ssh.service and Apache is apache2.service. On RHEL, AlmaLinux, Rocky Linux, and Fedora, they're sshd.service and httpd.service. Some packages ship aliases, so ssh may resolve on both — don't count on it.

How to Check Status and List systemd Services

Before you change anything, look at what's there.

systemctl status nginx
Annotated systemctl status nginx panel explaining Loaded, Active, Main PID, Tasks, Memory, and journal output.

Three separate facts hide in that output. Loaded tells you whether systemd found and parsed the unit file, plus its boot state in parentheses (enabled, disabled, static, masked). Active is the runtime state. Main PID is the actual process. Loaded does not mean running, and enabled does not mean running either.

State Meaning What to do next
active (running) Process is up and persistent Nothing
active (exited) A one-shot job finished successfully Normal for oneshot units
inactive (dead) Stopped, no error recorded Start it, or find out who stopped it
activating Still starting up Wait; watch for a timeout
failed Exited non-zero or timed out Read the journal
not-found No unit file exists Check the name and the package

Listing commands you'll actually use:

systemctl list-units --type=service --state=running
systemctl list-units --type=service --all
systemctl list-unit-files --type=service
systemctl --failed --type=service

The difference matters: list-units shows units systemd has loaded into memory, while list-unit-files shows every unit file installed on disk. For more angles on this, see our guide to list running services in Linux.

Checking state inside scripts

These three return exit codes instead of pretty output, which makes them perfect for automation:

systemctl is-active --quiet nginx && echo "nginx is up"
systemctl is-enabled nginx
systemctl is-failed nginx

Exit code 0 means true. Anything else means false. To tie a service back to its underlying processes, pair this with inspect Linux processes with ps.

Start, Stop, Restart, and Reload a Linux Service

Now the actions themselves. All of these need sudo, and all of them change runtime state only — nothing here affects the next reboot.

sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx
sudo systemctl reload-or-restart nginx
Action Effect Disruption Use when
start Launches an inactive unit None Service is stopped
stop Sends the stop sequence, waits Full outage Maintenance, decommissioning
restart Stops then starts; new PID Brief outage, dropped connections Binary upgrades, stuck process
reload Asks the app to reread its config Usually minimal Config change on a live server
reload-or-restart Reloads if supported, else restarts Varies Scripts that must not fail

Not every service implements reload. If the unit file has no ExecReload directive, the command fails and you'll need a restart instead. And reload isn't magic — some settings only take effect on a full restart, so read the application's own documentation for anything critical.

Diagram comparing restart downtime and a new PID with reload using SIGHUP on the same PID.

You can act on several units at once:

sudo systemctl restart nginx php8.3-fpm

A production-safe workflow

Never restart a web server, database, or SSH daemon blind. The sequence I use every time:

  1. Validate the config first — nginx -t for NGINX, apachectl configtest for Apache.
  2. Prefer reload over restart if the service supports it.
  3. Check status immediately afterwards.
  4. Read the journal for warnings even when the command "worked."

Apache is a good example of naming pain, and we cover the specifics in restart Apache on Linux.

Enable, Disable, and Mask systemd Services at Boot

Boot behaviour is a completely separate axis from runtime state. enable creates symlinks based on the unit's [Install] section — typically WantedBy=multi-user.target — so systemd activates the unit at boot. It does not start anything right now.

sudo systemctl enable nginx
sudo systemctl enable --now nginx
sudo systemctl disable nginx
sudo systemctl disable --now nginx
sudo systemctl mask nginx
sudo systemctl unmask nginx
systemctl is-enabled nginx
Command Runs now? Runs at boot? Can dependencies start it?
start Yes No Yes
enable No Yes Yes
enable --now Yes Yes Yes
disable No change No Yes
disable --now Stops it No Yes
mask No change No No

That last row is the whole point of masking. It symlinks the unit to /dev/null, so nothing can start it — not you, not another unit's dependency chain. Handy when a package keeps reviving a service you want gone. Dangerous if you forget you did it.

Warning: mask is not a routine disable. Use disable for normal "don't start at boot" cases, and reserve mask for services that must never run.

What static and indirect mean

Run is-enabled and you may get static instead of enabled or disabled. That means the unit has no [Install] section, so there's nothing to enable — it gets pulled in by another unit or a socket. It's not broken. You'll also see indirect (enablement is delegated to an alias), generated (created on the fly, often from /etc/fstab), and alias (another name for a real unit).

View systemd Service Logs with journalctl

systemd-journald captures everything services write to stdout and stderr. The status output only shows the last ten lines or so — journalctl gives you the rest.

Goal Command
All logs for one service sudo journalctl -u nginx
Last 50 lines, no pager sudo journalctl -u nginx -n 50 --no-pager
Follow live sudo journalctl -u nginx -f
Current boot only sudo journalctl -u nginx -b
Recent window sudo journalctl -u nginx --since "30 minutes ago"
Errors system-wide sudo journalctl -p err -b
Annotated nginx journal log highlighting a port 80 conflict and failed service start.

Four messages account for most failures: Permission denied (wrong user, wrong file mode, or SELinux/AppArmor), Address already in use (something else holds the port), No such file or directory (bad ExecStart path), and unknown directive (a config typo). Remember that many applications keep their own logs too — NGINX writes to /var/log/nginx/error.log independently of the journal. Our guide to Linux logs and troubleshooting covers that wider picture, and Linux monitoring tools and commands helps you watch services after recovery.

Need a Linux server to practise on? Deploy a Linux VPS with root access running Ubuntu, Debian, AlmaLinux, or another supported distribution, and break things safely in an isolated environment. View Linux VPS plans

Create and Edit a systemd Service File

Unit files live in three places, and precedence runs from most to least specific: /etc/systemd/system/ for your own units and overrides, /run/systemd/system/ for runtime units, and /usr/lib/systemd/system/ (or /lib/systemd/system/ on older Debian-family systems) for packaged vendor units. Never edit vendor files directly — package upgrades overwrite them.

Labelled anatomy of a systemd unit file showing Unit, Service, and Install directives.

with Description and After/Wants called out, [Service] with Type, User, ExecStart, Restart called out, and [Install] with WantedBy called out]

Here's a production-shaped example for a background worker at /etc/systemd/system/example-worker.service:

[Unit]
Description=Example Background Worker
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=exampleworker
Group=exampleworker
ExecStart=/usr/local/bin/example-worker
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Reading it directive by directive: After= only controls ordering, while Wants= expresses the actual (soft) dependency — that pair is deliberate. Type=simple suits a process that stays in the foreground. User= and Group= drop privileges to a dedicated non-login account you create beforehand. ExecStart= must be an absolute path; systemd doesn't use your shell's PATH. Restart=on-failure with RestartSec=5 gives you automatic recovery without a tight crash loop. WantedBy=multi-user.target is what makes enable work at all.

Security tip: run custom applications under a dedicated non-root user. Root-owned services are the shortcut everyone takes and later regrets — see Linux server security practices for the wider habit set.

Then activate it in this order:

sudo systemd-analyze verify /etc/systemd/system/example-worker.service
sudo systemctl daemon-reload
sudo systemctl enable --now example-worker.service
systemctl status example-worker.service
sudo journalctl -u example-worker.service -n 50 --no-pager

daemon-reload tells systemd to reread unit files from disk. It does not reload application configuration and it does not restart your service — you still need restart for new unit settings to apply to a running process. Skip daemon-reload after editing a unit and systemd keeps using the stale definition, which produces some genuinely baffling debugging sessions.

Overriding a packaged unit

To change a vendor unit safely, create a drop-in:

sudo systemctl edit nginx
sudo systemctl revert nginx

That writes a small override file under /etc/systemd/system/nginx.service.d/ containing only your changes and runs daemon-reload for you. revert throws the override away and returns to vendor defaults.

Running workers, bots, and APIs as systemd services needs persistent resources and full root control, which is exactly what unmanaged VPS hosting is for. If you'd rather someone else own the unit files, managed VPS hosting is the better fit.

Troubleshoot Failed systemd Services

Work the same sequence every time instead of restarting hopefully:

systemctl status SERVICE --no-pager -l
sudo journalctl -u SERVICE -b -n 100 --no-pager
systemctl cat SERVICE
systemctl list-dependencies SERVICE
Symptom Likely cause Diagnose Fix
Unit could not be found Wrong name or package missing list-unit-files | grep -i NAME Correct the name or install the package
failed with exit code App error or bad config journalctl -u SERVICE -b Fix the reported cause, then restart
Unit is masked Someone masked it systemctl is-enabled SERVICE sudo systemctl unmask SERVICE
Enabled but inactive Never started, or exited systemctl status SERVICE Start it and read the journal
Start request repeated too quickly Crash loop hitting the rate limit journalctl -u SERVICE Fix the crash, then reset-failed
Timeout on start Wrong Type=, slow init, missing dep systemctl show SERVICE Correct Type= or raise TimeoutStartSec
Dependency failed A required unit is down systemctl list-dependencies SERVICE Repair the upstream unit first
Permission denied User, mode, SELinux, or AppArmor journalctl -p err -b Fix ownership; check SELinux/AppArmor denials
Address already in use Port conflict sudo ss -tulpn | grep :80 Stop the conflicting service or change ports
Changes not taking effect Stale unit definition systemctl cat SERVICE daemon-reload then restart
Not booted with systemd systemd isn't PID 1 ps -p 1 -o comm= Use the container supervisor or enable WSL systemd
Orphaned process after stop Process ignored the stop signal ps aux | grep NAME Terminate it gracefully before escalating
Decision tree linking systemctl status outcomes to seven next diagnostic commands.

Two rules keep you out of trouble. reset-failed clears the recorded failure state and the rate-limit counter; it repairs nothing, so fix the cause first. And never reach for chmod 777 or a blanket reinstall — you'll trade a diagnosable fault for an undiagnosable one. When a stuck process survives a stop, learn to safely stop a Linux process before escalating to SIGKILL. For system-wide faults, follow the Linux server troubleshooting workflow.

systemctl Command Cheat Sheet

Goal Command sudo?
Full status and recent logs systemctl status nginx No
Show all resolved properties systemctl show nginx No
Print the effective unit file systemctl cat nginx No
List running services systemctl list-units --type=service --state=running No
List installed unit files systemctl list-unit-files --type=service No
List failed services systemctl --failed --type=service No
Show dependencies systemctl list-dependencies nginx No
Start / stop sudo systemctl start nginx / stop nginx Yes
Restart / reload sudo systemctl restart nginx / reload nginx Yes
Reload if possible, else restart sudo systemctl reload-or-restart nginx Yes
Enable at boot sudo systemctl enable nginx Yes
Enable and start now sudo systemctl enable --now docker Yes
Disable at boot sudo systemctl disable nginx Yes
Block all activation sudo systemctl mask nginx Yes
Undo masking sudo systemctl unmask nginx Yes
Script check: is it up? systemctl is-active --quiet nginx No
Script check: boot state systemctl is-enabled nginx No
Script check: failed? systemctl is-failed nginx No
Create a drop-in override sudo systemctl edit nginx Yes
Reread unit files sudo systemctl daemon-reload Yes
Clear failure state sudo systemctl reset-failed nginx Yes
Manage a user unit systemctl --user restart myapp No
Service logs, last 50 lines sudo journalctl -u nginx -n 50 --no-pager Yes
Follow logs live sudo journalctl -u nginx -f Yes

Bookmark this alongside our broader Linux cheat sheet. If you're moving on to scheduled tasks, systemd timers are the modern route, though plenty of admins still create a cron job in Ubuntu instead.

Mistakes worth avoiding

  • Assuming enable starts the service. It doesn't — add --now.
  • Treating reload and daemon-reload as the same thing.
  • Reading "enabled" as "running."
  • Restarting production services before validating config.
  • Editing vendor unit files instead of using drop-ins.
  • Using reset-failed as a repair.

Next Steps

You now have the full loop: find the unit, read its state, control it at runtime, set its boot behaviour, read the journal, write your own unit, and debug failures methodically. Practise on a throwaway service — not your live web server.

Want complete command-line control? Pick an unmanaged VPS and run systemd your way. Prefer help keeping the box healthy? Explore managed VPS hosting and Linux VPS. For directive-level detail, the official systemctl manual and the systemd.unit reference are the sources worth trusting.