Linux service management has gone through two distinct eras. For most of Linux's history, the system used SysV init (System V init) — a port of the original Unix init process. Under SysV, every service was a shell script stored in /etc/init.d/. Boot proceeded sequentially: scripts ran one after another, in numbered order.
In 2010, systemd was introduced and has since become the default init system on Debian, Ubuntu, Fedora, RHEL, Arch, and most other major distributions. systemd runs services in parallel, tracks them with cgroups, and uses declarative unit files instead of shell scripts.
The result: three different tools exist because they map to different layers of this evolution. They're not equivalent — two of them are wrappers or legacy interfaces for the third.
Scripts in /etc/init.d/ are plain shell scripts that accept a verb as their first argument: start, stop, restart, status, reload. They are the raw SysV interface.
# Direct execution — works on any SysV system
/etc/init.d/nginx start
/etc/init.d/nginx stop
/etc/init.d/nginx restart
/etc/init.d/nginx status
/etc/init.d/ may still exist as compatibility shims. Calling them directly invokes a compatibility layer that ultimately delegates to systemd.
# Enable / disable via update-rc.d (Debian/Ubuntu)
update-rc.d nginx enable
update-rc.d nginx disable
# Or via chkconfig (RHEL/CentOS pre-7)
chkconfig nginx on
chkconfig nginx off
init.d scripts have no dependency resolution (beyond ordered numbering), no parallel execution, no automatic restart on failure, and no unified logging. You get whatever the script author implemented — which varies widely.
/etc/init.d/ scripts directly on a systemd system bypasses systemd's cgroup tracking. The process exists but systemd doesn't know about it, which breaks status, automatic restarts, and log aggregation.
service commandservice is a wrapper shipped on Debian/Ubuntu systems. It abstracts over whichever init system is running. On a SysV system it calls the init.d script directly. On a systemd system it calls systemctl.
service nginx start
service nginx stop
service nginx restart
service nginx status
It is intentionally minimal: it covers only the four runtime operations above. It cannot enable services at boot, mask them, show logs, list units, or interact with systemd targets. That is not an oversight — it was designed for portability, not power.
# service nginx start → translates to:
systemctl start nginx.service
# You can verify this by running:
which service # /usr/sbin/service
cat /usr/sbin/service # it's a shell script
service is useful in scripts that must run on both SysV and systemd hosts. For interactive use on modern systems, systemctl gives you more information and control.
systemctl is the primary command-line interface to systemd. It manages units — not just services (.service), but also sockets (.socket), timers (.timer), mount points (.mount), and more. Everything below focuses on services.
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx # sends SIGHUP; fails if not supported
systemctl status nginx # shows state, cgroup, last log lines
systemctl enable nginx # creates symlink; starts at boot
systemctl disable nginx # removes symlink
# Enable AND start in one command
systemctl enable --now nginx
# Disable AND stop in one command
systemctl disable --now nginx
# Masking prevents a service from being started by anything,
# including dependencies or other services.
systemctl mask bluetooth
systemctl unmask bluetooth
# Is it running right now?
systemctl is-active nginx
# Is it enabled at boot?
systemctl is-enabled nginx
# Is it masked?
systemctl is-masked nginx
# Show all running services
systemctl list-units --type=service --state=running
# Show failed units
systemctl --failed
# Dump the full unit file
systemctl cat nginx
# Follow logs for a specific service
journalctl -u nginx -f
# Last 50 lines
journalctl -u nginx -n 50
# Since last boot
journalctl -u nginx -b
# After editing /etc/systemd/system/nginx.service
systemctl daemon-reload
systemctl restart nginx
| Feature | init.d script | service | systemctl |
|---|---|---|---|
| Start / stop / restart | ✓ | ✓ | ✓ |
| Enable at boot | update-rc.d / chkconfig | ✗ | ✓ native |
| Mask (hard disable) | ✗ | ✗ | ✓ |
| Integrated logs | ✗ (syslog only) | ✗ | ✓ via journalctl |
| Parallel startup | ✗ | ✗ | ✓ |
| Dependency management | ordering by number only | ✗ | ✓ full DAG |
| Auto-restart on failure | ✗ (manual in script) | ✗ | ✓ Restart= directive |
| Cgroup process tracking | ✗ | ✗ | ✓ |
| Portable across init systems | SysV only | ✓ abstraction layer | systemd only |
| List all services + status | ✗ | ✗ | ✓ |
| Intent | SysV / init.d | systemctl (modern) |
|---|---|---|
| Start a service | /etc/init.d/nginx start |
systemctl start nginx |
| Stop a service | /etc/init.d/nginx stop |
systemctl stop nginx |
| Restart a service | /etc/init.d/nginx restart |
systemctl restart nginx |
| Check status | /etc/init.d/nginx status |
systemctl status nginx |
| Enable at boot | update-rc.d nginx enable |
systemctl enable nginx |
| Disable at boot | update-rc.d nginx disable |
systemctl disable nginx |
| Start + enable | two commands | systemctl enable --now nginx |
| View logs | cat /var/log/nginx/error.log |
journalctl -u nginx -f |
| Reload unit files | n/a | systemctl daemon-reload |
| List failed | n/a | systemctl --failed |
The answer depends on your context:
Use systemctl for everything. It is the authoritative interface. service works for start/stop/restart but gives you no visibility into the system. init.d scripts should not be called directly.
If your script must run on both systemd and SysV hosts, service is reasonable for the four basic operations. Add a check at the top of the script:
if command -v systemctl >/dev/null 2>&1; then
SVCMGR="systemctl"
else
SVCMGR="service"
fi
$SVCMGR nginx restart
No systemd is present. Use /etc/init.d/ scripts directly or via service. Enable at boot via update-rc.d (Debian) or chkconfig (RHEL).
systemctl exists on the host, use it. Run which systemctl to check.
On systemd hosts, init.d scripts are still supported through a compatibility shim. When you run /etc/init.d/nginx start, the script detects systemd is running and delegates to systemctl start nginx via /lib/lsb/init-functions.
Similarly, service nginx start on a systemd host checks for the corresponding .service unit first; if found, it calls systemctl. If no unit is found but an init.d script exists, it falls back to that.
This means calling any of the three tools on a modern system ultimately reaches systemd — but only systemctl gives you the full status picture, journal integration, and dependency graph. The others are convenience wrappers with deliberately limited scope.
# Check what's actually managing a service on your system:
systemctl show nginx --property=FragmentPath
# FragmentPath=/lib/systemd/system/nginx.service → native unit
# FragmentPath=/run/systemd/generator.late/... → generated from init.d
.service unit. Features like Restart=on-failure, After= dependencies, and Type=notify require a real unit file in /etc/systemd/system/ or /lib/systemd/system/.