systemctl and journalctl: services and logs for beginners

Start, stop and enable services, then read their logs by unit, boot, time and priority.

Part 10 of 12 in Linux basics for self-hosters. Last updated

In short: sudo systemctl start caddy starts a service now, sudo systemctl enable caddy starts it at every boot (enable --now does both), and systemctl status caddy shows whether it runs plus its last log lines. journalctl -u caddy shows that service's full log, -f follows new lines, -b limits it to the current boot and --since '1 hour ago' to a time window. Rootless podman services use the same commands with --user and no sudo.

systemctl: the everyday commands

systemd starts almost everything on a current Linux system, and systemctl is how you talk to it. Each service is a unit named like sshd.service; you may leave out the .service. Changing a system service needs sudo; looking does not.

systemctl status sshd                 # running? since when? last log lines
sudo systemctl start caddy            # start now
sudo systemctl stop caddy             # stop now
sudo systemctl restart caddy          # stop and start
sudo systemctl reload caddy           # re-read the configuration, if the service supports it
sudo systemctl enable --now caddy     # start at every boot, and now
sudo systemctl disable --now caddy    # the reverse
systemctl is-active caddy             # prints active, inactive or failed (handy in scripts)
systemctl --failed                    # everything that failed
systemctl list-units --type=service --state=running
systemctl cat caddy                   # the unit file and its drop-ins
sudo systemctl edit caddy             # add an override that survives updates
sudo systemctl daemon-reload          # after editing unit files by hand

systemctl status also accepts a PID and tells you which unit that process belongs to. The full reference is systemctl(1).

Reading systemctl status

● sshd.service - OpenSSH Daemon
     Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; preset: disabled)
     Active: active (running) since Fri 2026-09-25 12:52:20 CEST; 1 day 21h ago
   Main PID: 1234 (sshd)
      Tasks: 1
     Memory: 5.2M
     CGroup: /system.slice/sshd.service
             └─1234 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
  • Loaded: where the unit file is, and enabled (starts at boot) or disabled. The preset is only the distribution's default suggestion.
  • Active: active (running) is good; failed means it crashed or exited with an error; activating (auto-restart) means it keeps crashing and systemd keeps trying.
  • The last lines below the header come from the journal. When a service fails, the reason is usually right there.

systemctl --failed is the quickest health check. On a real laptop it found two units; the Cockpit one is a known openSUSE issue explained in the Cockpit troubleshooting guide:

$ systemctl --failed --no-pager --no-legend
● vboxadd-service.service loaded failed failed Start/Stop VirtualBox Guest Linux module
● cockpit.socket          loaded failed failed Cockpit Web Service Socket

Real output on openSUSE Tumbleweed, systemd 261, 2026-09-28; user and host names and addresses redacted.

User services and --user

Every logged-in user has their own systemd instance. Rootless podman quadlets run there, so the commands are the same with --user and without sudo:

systemctl --user daemon-reload          # after adding or changing a quadlet
systemctl --user start whoami.service
systemctl --user status whoami.service
journalctl --user -u whoami.service -f
sudo loginctl enable-linger "$USER"     # run user services at boot, without a login

Quadlet units are generated, so systemctl --user enable fails on them with "transient or generated". The [Install] section in the .container file does the enabling. See the whoami quadlet example for a complete, tested one.

journalctl: reading logs

The journal collects the output of every service, the kernel and the boot process in one place. You filter it instead of hunting through files:

journalctl -u caddy                      # everything from one service, oldest first
journalctl -u caddy -f                   # follow new lines (Ctrl+C to stop)
journalctl -u caddy -n 50                # the last 50 lines
journalctl -u caddy -b                   # only since the current boot
journalctl -b -1 -p err                  # errors from the previous boot
journalctl --since "1 hour ago"          # a time window
journalctl --since today --until "10:30"
journalctl -p warning..err -b            # warnings and errors, this boot
journalctl -k -b                         # kernel messages (like dmesg)
journalctl -u sshd -g 'Failed password'  # search the message text
journalctl -u caddy -o short-iso         # ISO 8601 timestamps
journalctl --list-boots                  # which boots are stored
journalctl --disk-usage                  # how much space the journal uses

Filters combine: journalctl -u caddy -b -p err is "errors from caddy since this boot". Without sudo you only see your own user's logs, and journalctl prints a hint saying so. Tick the filters you need in the journalctl command builder to get the command with every flag explained. Reference: journalctl(1).

Timers instead of cron

A systemd timer starts a service on a schedule. Compared with cron, the job gets the journal for its output, Persistent=true catches up on runs missed while the machine was off, and systemctl list-timers shows the next and last run of every job:

systemctl list-timers                   # all timers, next run first
systemctl --user list-timers            # your own
sudo systemctl enable --now backup.timer
journalctl -u backup.service            # what the last runs printed
The timer holds the schedule, the service holds the job, the journal keeps the output.

Timers that come with openSUSE, and the unit file of one of them:

$ systemctl list-timers --no-pager fstrim.timer snapper-timeline.timer snapper-cleanup.timer logrotate.timer
NEXT                           LEFT LAST                            PASSED UNIT                   ACTIVATES
Mon 2026-09-28 06:52:18 CEST  16min Mon 2026-09-28 05:52:18 CEST 43min ago snapper-cleanup.timer  snapper-cleanup.service
Mon 2026-09-28 07:00:00 CEST  24min Mon 2026-09-28 06:00:03 CEST 35min ago snapper-timeline.timer snapper-timeline.service
Tue 2026-09-29 00:50:51 CEST    18h Mon 2026-09-28 00:06:45 CEST    6h ago logrotate.timer        logrotate.service
Mon 2026-10-05 00:14:07 CEST 6 days Mon 2026-09-28 00:26:59 CEST    6h ago fstrim.timer           fstrim.service

4 timers listed.
Pass --all to see loaded but inactive timers, too.

$ systemctl cat --no-pager fstrim.timer
# /usr/lib/systemd/system/fstrim.timer
[Unit]
Description=Discard unused filesystem blocks once a week
Documentation=man:fstrim
ConditionVirtualization=!container
ConditionPathExists=!/etc/initrd-release

[Timer]
OnCalendar=weekly
AccuracySec=1h
Persistent=true
RandomizedDelaySec=100min

[Install]
WantedBy=timers.target

Real output on openSUSE Tumbleweed, systemd 261, 2026-09-28; user and host names and addresses redacted.

Before you trust a schedule, let systemd read it back. systemd-analyze calendar prints the normalized form and the next runs:

$ systemd-analyze calendar --iterations=3 'Mon *-*-* 04:00'
  Original form: Mon *-*-* 04:00
Normalized form: Mon *-*-* 04:00:00
    Next elapse: Mon 2026-10-05 04:00:00 CEST
       (in UTC): Mon 2026-10-05 02:00:00 UTC
       From now: 6 days left
   Iteration #2: Mon 2026-10-12 04:00:00 CEST
       (in UTC): Mon 2026-10-12 02:00:00 UTC
       From now: 1 week 6 days left
   Iteration #3: Mon 2026-10-19 04:00:00 CEST
       (in UTC): Mon 2026-10-19 02:00:00 UTC
       From now: 2 weeks 6 days left

Real output on openSUSE Tumbleweed, systemd 261, 2026-09-28; user and host names and addresses redacted.

Build a schedule in plain words with the cron schedule builder, or turn an existing crontab line into a .timer and .service pair with the cron to systemd timer converter. The systemd unit explainer tells you what each line of the result does.

Common mistakes

Editing a unit and forgetting daemon-reload

systemd keeps its own copy of every unit. After changing a file by hand, run sudo systemctl daemon-reload (or systemctl --user daemon-reload) and then restart the service. systemctl edit reloads for you.

enable without --now, or start without enable

enable alone waits for the next boot; start alone is forgotten at the next boot. The classic result is a service that works until the first power cut. Use enable --now.

Logs disappear after a reboot

Without the directory /var/log/journal, some systems keep the journal only in memory. Create it with sudo mkdir -p /var/log/journal and run sudo systemctl restart systemd-journald; from then on journalctl -b -1 works. Size limits go in a drop-in under /etc/systemd/journald.conf.d/. What to do when a service eats CPU instead of failing is in processes and resources.

Frequently asked questions

How do I follow the logs of a service?

journalctl -u name.service -f prints the last lines and then every new one as it arrives; stop with Ctrl+C. For a rootless user service add --user: journalctl --user -u name.service -f.

What is the difference between systemctl enable and start?

start runs the service now, until it stops or the machine reboots. enable makes it start automatically at boot but does not start it now. sudo systemctl enable --now name does both at once; disable --now undoes both.

How do I see logs from the previous boot?

journalctl -b -1 shows the previous boot, -b -2 the one before, and journalctl --list-boots lists them all. This only works when the journal is stored on disk, which is the default when /var/log/journal exists.

Why does journalctl show no entries for other services?

A normal user only sees their own logs. Use sudo, or add your user to the systemd-journal group and log in again. systemd also lets members of the adm and wheel groups read the system journal by default.