Linux & Shell
Packages, Services, Cron and systemd
apt and other package managers, running a service with systemd, reading unit files, journalctl, cron and systemd timers.
On this page
In one line: a package manager (apt, dnf, apk) installs software and its dependencies from trusted lists, systemd starts your app at boot and restarts it if it crashes, and cron (or a systemd timer) runs jobs on a schedule. If you know how to read a unit file and a cron line, you can run almost any small production service.
On your laptop you start an app by typing node server.js in a terminal. When you close the terminal, the app dies. On a server that is not good enough. The app must start when the machine boots, restart after a crash, write logs somewhere you can read, and run as a limited user. Something has to be in charge of that. On modern Linux that something is systemd. For work at certain times, like a nightly backup, there is cron. And the software itself, from nginx to git, comes from the package manager.
This is lesson 6 of nine. In lesson 5 we reached a remote server over SSH. Now we put it to work. The next lesson, disks, memory, logs and troubleshooting, is about what to do when the server is slow or full. If you use containers, remember that a container usually has no systemd at all; see Docker lesson 0 for how a container runs one main process instead.
What I ran / checked. Everything ran on Ubuntu 24.04.5 (kernel 6.18) as root in a sandbox VM, with apt 2.7 and dpkg, cron 3.0pl1-184 (the Debian/Ubuntu cron), systemd-analyze from systemd 255.4, Node.js v22.22.0 with the npm package cron-parser 4.9.0, and bash 5.2. Important limit: in this sandbox, systemd is not PID 1 (PID 1 here is a small program called process_api). So systemctl start, systemctl status, systemctl enable and journalctl -u myapp could not run. The tools printed System has not been booted with systemd as init system (PID 1). Can't operate. and No journal files were found. I did run the parts of systemd that work offline: systemd-analyze verify (checks unit files), systemd-analyze calendar (explains timer schedules), systemd-analyze security --offline (scores a unit). Anything about the running service is marked reviewed, not run, and I show commands but no invented output. I did run the real cron daemon for a few minutes, with real jobs, and pasted what it wrote. Sources I read (own words): the systemd manual sources from GitHub (systemd.service, systemd.timer, systemd.unit, systemd.exec XML files), the crontab(5) manual page from the cronie project (a different cron program from the one on Ubuntu, so I checked behaviour on Ubuntu myself), and the "Art of Command Line" README. Could not reach: Brendan Gregg's site and the Ubuntu docs pages. I did not verify any star counts. dnf, apk and brew were not installed or run here; the table of equivalents is from my own knowledge of those tools and should be double-checked on your distribution.
Words used in this lesson
- Package is a bundle of files (a program, its docs, its settings) plus a description of what it needs.
- Package manager is the tool that installs, upgrades and removes packages:
apton Debian and Ubuntu,dnfon Fedora and Red Hat,apkon Alpine. - Repository (repo) is a server with a list of packages, signed so you can trust them.
- Dependency is another package that this one needs to work.
- Service is a program that runs in the background for a long time, like a web server.
- systemd is the first program the kernel starts on most Linux systems (PID 1). It starts and watches services.
- Unit is systemd's word for a thing it manages. A service unit runs a program. A timer unit runs another unit on a schedule.
- Unit file is a small text file (INI style) that describes a unit.
systemctlis the command to control systemd.journalctlreads the logs systemd collects.- Journal is systemd's log store.
- Cron is an older scheduler. A crontab is the list of scheduled jobs for one user. A cron job is one line in it.
- Environment is the set of variables (like
PATH) a program starts with. - Idempotent means running something twice gives the same result as running it once.
Packages: how software gets onto the machine
You could download a program from a website, unpack it, and copy it into /usr/local/bin. That works once. Then you must update it by hand, remember what else it needs, and find every file when you want to remove it. A package manager does all of that.
On Ubuntu and Debian there are two layers you should know:
dpkgworks on individual.debfiles on this machine. It knows what is installed. It does not fetch from the internet or solve dependencies.aptsits on top. It downloads from repositories, works out dependencies, and callsdpkgto do the install.
Looking at what is installed
Let us look at jq, a small JSON tool, which was already installed:
$ dpkg -l jq | tail -1ii jq 1.7.1-3ubuntu0.24.04.2 amd64 lightweight and flexible command-line JSON processorThe first two letters ii mean: wanted state "install", actual state "installed". Next, what files did the package put on disk, and what package owns a given file?
$ dpkg -L jq | head -6/./usr/usr/bin/usr/bin/jq/usr/share/usr/share/doc$ dpkg -S /usr/bin/jqjq: /usr/bin/jqdpkg -L pkg lists files of a package. dpkg -S file answers "which package owns this file?" This second one is very handy when you find a strange file and wonder where it came from.
What apt knows about a package
$ apt-cache policy jqjq: Installed: 1.7.1-3ubuntu0.24.04.2 Candidate: 1.7.1-3ubuntu0.24.04.2 Version table: *** 1.7.1-3ubuntu0.24.04.2 500 500 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages$ apt-cache depends jq | head -4jq Depends: libc6 Depends: libjq1Installed is what you have. Candidate is what apt install would give you. The number 500 is the repository's priority. jq depends on a shared library package, libjq1; this is the "dependency" that apt installs for you without being asked.
Installing: always look first
apt-get install -s (-s means simulate) shows the plan without doing it. Here is a package I did not have yet, tree:
$ apt-get install -s treeThe following NEW packages will be installed: tree0 upgraded, 1 newly installed, 0 to remove and 42 not upgraded.Inst tree (2.1.1-2ubuntu3.24.04.2 Ubuntu:24.04/noble-updates [amd64])Conf tree (2.1.1-2ubuntu3.24.04.2 Ubuntu:24.04/noble-updates [amd64])Inst is unpack, Conf is configure. Then I really installed it (apt-get install -y tree) and ran tree --version, which printed tree v2.1.1 .... The -y flag answers "yes" to the prompt, which is needed in scripts and Dockerfiles. Be careful: -y also says yes to things you did not read. Do a dry run first on a machine that matters.
Remove versus purge
$ apt-get remove -s treeThe following packages will be REMOVED: treeRemv tree [2.1.1-2ubuntu3.24.04.2]$ apt-get purge -s treeThe following packages will be REMOVED:Purg tree [2.1.1-2ubuntu3.24.04.2]The two simulations look similar, but note the marker: Remv versus Purg. remove deletes the program but keeps your config files (in case you reinstall later). purge deletes the config files too. For a service like a database, this difference matters: remove keeps the settings; purge deletes them. I did not test the config-file difference with a package that has config in /etc (the tree package has none), so that sentence comes from the way the two commands are documented, not from this run.
update is not upgrade
This is a classic beginner confusion.
apt updateonly refreshes the list of packages and versions from the repositories. It installs nothing.apt upgradeinstalls newer versions of what you have.
Without apt update first, apt works from an old list, and may say a package "has no installation candidate" or install an old version. The lists are stored as files; on this lab machine they live in /var/lib/apt/lists. In a Dockerfile you write apt-get update && apt-get install -y ... in one line (see Docker lesson 1) so a cached old list is never reused.
A simulated upgrade shows what would change:
$ apt list --upgradable 2>/dev/null | head -3Listing...containerd.io/noble 2.4.1-2~ubuntu.24.04~noble amd64 [upgradable from: 2.3.6-1~ubuntu.24.04~noble]docker-buildx-plugin/noble 0.38.0-1~ubuntu.24.04~noble amd64 [upgradable from: 0.37.1-1~ubuntu.24.04~noble]$ apt-get -s upgrade | tail -2Conf librsvg2-2 (2.58.0+dfsg-1ubuntu0.1 Ubuntu:24.04/noble-updates, Ubuntu:24.04/noble-security [amd64])Conf libxml2-utils (2.9.14+dfsg-1.3ubuntu3.10 Ubuntu:24.04/noble-updates, Ubuntu:24.04/noble-security [amd64])Upgrading a database or a runtime on a live server can restart it. Read the list first. On important machines, upgrade in a maintenance window, and keep the machine's package versions the same as in your test environment. "I just ran apt upgrade" is a classic way to start an incident.
Manual versus automatic packages
apt remembers which packages you asked for (manual) and which were pulled in only as dependencies (auto). apt-mark showmanual lists the first kind (842 on this VM); apt-mark showauto | head -3 shows names like bind9-dnsutils. When you remove a package, its automatic dependencies stay behind until you run apt autoremove. Use apt-get autoremove -s first to see the plan.
Where do packages come from?
The list of repositories is in /etc/apt/sources.list.d/. On this Ubuntu 24.04 machine there are two files: ubuntu.sources (the Ubuntu archive) and docker.list (Docker's own repo that someone added). Every repo has a signing key, and apt checks the signature. Adding a third-party repo gives its owner the ability to put software on your server, so add only repos you trust, and prefer to install from the distribution when you can.
Other families (reviewed, not run)
| Task | Debian / Ubuntu | Fedora / RHEL | Alpine |
|---|---|---|---|
| Refresh lists | apt update | dnf makecache (automatic on most commands) | apk update |
| Install | apt install pkg | dnf install pkg | apk add pkg |
| Remove | apt remove pkg | dnf remove pkg | apk del pkg |
| Which package owns a file | dpkg -S file | rpm -qf file | apk info -W file |
| List a package's files | dpkg -L pkg | rpm -ql pkg | apk info -L pkg |
The ideas are the same everywhere. Language package managers (npm, pip) are separate from the system one; see the Node.js series for npm.
Never run sudo npm install -g or sudo pip install to "fix" a permission error. That mixes your language tools with the system's files. Use a version manager, a virtual environment or a project folder.
Services: why a plain node server.js is not enough
Say you start your API with node server.js & over SSH. When your SSH session ends, the shell sends a hang-up signal, and your app may stop. Even with nohup, nothing restarts it when it crashes, nothing starts it after a reboot, and its logs go who-knows-where. A service manager solves that. On most Linux systems, that is systemd.
Is systemd even running here?
Check what PID 1 is:
$ ps -p 1 -o comm=process_apiOn a normal Ubuntu server this prints systemd. In my sandbox it did not, so the commands below that need a running systemd failed with:
System has not been booted with systemd as init system (PID 1). Can't operate.Failed to connect to bus: Host is downNo journal files were found.That is the same message you would see inside many Docker containers and some WSL setups. If you ever type systemctl in a container and get this, it is not broken: there is just no systemd there. Keep that in mind during interviews: "container" and "systemd" usually do not go together.
A unit file, line by line
A service is described by a unit file, usually in /etc/systemd/system/ (your own units) or /usr/lib/systemd/system/ (units shipped by packages). Here is the file I wrote for a small Node API called myapp:
[Unit]Description=My demo Node APIAfter=network-online.targetWants=network-online.target[Service]Type=simpleUser=deployWorkingDirectory=/opt/myappEnvironment=NODE_ENV=productionEnvironmentFile=-/etc/myapp.envExecStart=/opt/node22/bin/node /opt/myapp/server.jsRestart=on-failureRestartSec=3NoNewPrivileges=yes[Install]WantedBy=multi-user.targetIt has three sections.
[Unit] says what this thing is and how it relates to other units.
Descriptionis the text you see insystemctl status.After=network-online.targetmeans "start me after the network is up".Afteris only about order.Wants=network-online.targetmeans "also try to start the network-online target".Wantsis about pulling in another unit, and it is soft: if that fails, I still start. (Requires=is the strict version.) The two are separate on purpose: order and dependency are different questions.
[Service] says how to run the program.
Type=simplemeans "the process I start withExecStartis the service". This is the default for a foreground program likenode server.js. Other types exist; for exampleoneshotis for a command that runs, finishes and exits (good for scripts), andforkingis for old daemons that detach themselves. If a program forks into the background but you chosesimple, systemd thinks it exited.User=deployruns it as that limited user, not as root (lesson 1).WorkingDirectorysets the folder it starts in.Environment=sets a variable.EnvironmentFile=-/etc/myapp.envreads variables from a file; the-before the path means "do not fail if the file is missing".ExecStartis the command. Use the full path to the program. (When I wrote/usr/bin/nodethe verifier correctly told me the file did not exist on this machine, see below.)Restart=on-failurerestarts the service if it exits with an error, or is killed by a signal, or times out. Other common values arealwaysandno.RestartSec=3waits 3 seconds first, so a crash loop does not spin the CPU.NoNewPrivileges=yesis a small safety setting: the service and its children cannot gain more privileges, for example through a setuid program (lesson 1).
[Install] matters only for systemctl enable. WantedBy=multi-user.target means "when the machine boots into normal multi-user mode, start this too". enable makes a link so that happens. start runs it now. enable and start are two separate ideas: systemctl enable --now myapp does both.
A shell is not involved. ExecStart is not run by bash. Pipes |, redirects >, $VAR and && do not work in it the way they do in a terminal. If you need them, run ExecStart=/bin/bash -c '...', or better, put the logic in a script file. This follows how systemd documents command lines: it splits the line into words itself. I did not run a test of every special character, so treat this as the rule and check systemd.service(5) for the exact variable syntax.
Checking a unit file without systemd running
systemd-analyze verify reads unit files and reports problems. It worked here even though systemd is not PID 1.
The first version, with the wrong path to node:
$ systemd-analyze verify myapp.servicemyapp.service: Command /usr/bin/node is not executable: No such file or directoryAfter I changed the path to the real one (/opt/node22/bin/node on this VM), the command printed nothing, which in this tool means "no problems found".
A unit with a typo in a key name:
$ systemd-analyze verify bad3.servicebad3.service:9: Unknown key name 'Typo' in section 'Service', ignoring.And a bad value, Restart=sometimes:
$ systemd-analyze verify bad.servicebad.service:6: Failed to parse service restart specifier, ignoring: sometimes"Ignoring" means the setting is silently dropped. systemd does not stop; it ignores the line it did not understand and goes on. A typo in Restart= means no restarts, and you may only notice when the service dies at 3 a.m. Always run systemd-analyze verify on a unit before you rely on it.
The verifier has limits. I put User=nosuchuser in bad3.service and it did not complain, because that check needs the real system. So "verify is clean" means the syntax is fine, not that the service will start. I also tried ExecStart=node server.js (no full path) and verify did not complain either. I did not read why; systemd can find a bare command name in its own search path in newer versions, but I still recommend a full path so behaviour does not depend on PATH.
You can also ask for a rough security score:
$ systemd-analyze security --offline=true myapp.service | tail -1→ Overall exposure level for myapp.service: 8.8 EXPOSED :-(The scale runs from 0 (locked down) to 10 (wide open). Our tiny unit scored 8.8 because it uses almost none of the sandboxing options (ProtectSystem=, PrivateTmp=, SystemCallFilter= and more). The list printed shows what is missing, line by line. You do not need a perfect score, but it is a cheap checklist.
The commands you will use (reviewed, not run)
These need a running systemd, so I could not run them. This is the common set:
sudo cp myapp.service /etc/systemd/system/sudo systemctl daemon-reload # tell systemd to re-read unit filessudo systemctl enable --now myapp # start now AND at every bootsystemctl status myapp # state, recent logs, main PIDsudo systemctl restart myapp # stop and startsudo systemctl reload myapp # ask it to re-read config, if it supports thatsystemctl is-active myapp # prints "active" or "inactive" (good for scripts)systemctl list-units --type=service --state=failedThe step people forget is daemon-reload: after you edit a unit file, systemd still has the old copy in memory until you run it. Another good habit is to use sudo systemctl edit myapp, which creates an override file for a packaged unit instead of changing the package's own file (which a package upgrade could overwrite).
Reading logs with journalctl (reviewed, not run)
systemd collects the standard output and standard error of every service into the journal. So your Node app only needs to console.log; you do not need log files. Commands:
journalctl -u myapp # all logs of the myapp unitjournalctl -u myapp -f # follow live, like tail -fjournalctl -u myapp --since "1 hour ago"journalctl -u myapp -p err # only errors and worsejournalctl -b # this boot onlyjournalctl -u myapp -n 50 --no-pagerIn this sandbox the only output I got was No journal files were found. For logs without systemd, and how to search them, see lesson 7. If you want the bigger picture of structured logs, see Observability lesson 1.
Why Restart= needs care
A service that crashes immediately and restarts every 3 seconds is a crash loop. systemd has a start-rate limit, so it will eventually stop trying and mark the unit failed. This protects the machine, but it also means your service stays down until someone looks. For a service that must stay up, combine Restart= with monitoring that tells you about it. I did not trigger a crash loop here because systemd was not running; this is from the systemd.service and systemd.unit manual sources I read, which describe restart delays and start limits.
Cron: a job at a time
Cron is a program (a daemon named cron) that wakes up every minute, reads everyone's crontab files, and starts the jobs whose time has come. You edit your own list with crontab -e, show it with crontab -l.
The five fields
A cron line is five time fields and then a command:
┌───────── minute (0-59)│ ┌─────── hour (0-23)│ │ ┌───── day of month (1-31)│ │ │ ┌─── month (1-12)│ │ │ │ ┌─ day of week (0-7, 0 and 7 are Sunday)│ │ │ │ │* * * * * commandSome examples, and I checked each with the real Node library cron-parser 4.9.0, starting from a fixed time (12 October 2026, 03:50 UTC):
*/15 * * * * 2026-10-12T04:00 2026-10-12T04:15 2026-10-12T04:3030 2 * * * 2026-10-13T02:30 2026-10-14T02:30 2026-10-15T02:300 9 * * 1-5 2026-10-12T09:00 2026-10-13T09:00 2026-10-14T09:0030 4 1,15 * 5 2026-10-15T04:30 2026-10-16T04:30 2026-10-23T04:300 0 31 2 * ERROR: Invalid explicit day of month definition61 * * * * ERROR: Constraint error, got value 61 expected range 0-59Read them as: every 15 minutes; at 02:30 every day; at 09:00 Monday to Friday; and the surprising one.
The day-of-month and day-of-week trap. 30 4 1,15 * 5 looks like "the 1st and 15th, if it is a Friday". It is not. The manual says: when both day fields are restricted (neither is *), the job runs when either one matches. So it runs on the 1st, the 15th, and every Friday. The parser output agrees: 15 October (a Thursday, the 15th), then Friday 16 and 23 October. To run only on certain days and certain weekdays, you have to test the date inside the script.
The last two lines show invalid schedules being rejected: February 31 does not exist, and minute 61 is out of range. (I wanted to check syntax with crontab -T, which the cronie manual mentions, but the Ubuntu cron program said invalid option -- 'T'. Different cron programs have different options, so check man crontab on your machine. A Node parser is a handy extra check, but it might not agree with every cron program on edge cases.)
There are also shortcuts like @daily, @hourly, @reboot that some cron programs accept.
What cron really does: a real run
I started the real cron daemon in the sandbox and installed a crontab for the deploy user with two jobs that write what they see into a file:
* * * * * echo "PATH=$PATH SHELL=$SHELL pwd=$(pwd) user=$(whoami)" >> /tmp/lab-linux-b/pkg/cron.out 2>&1* * * * * which node jq >> /tmp/lab-linux-b/pkg/cron.out 2>&1About a minute later the file contained:
PATH=/usr/bin:/bin SHELL=/bin/sh pwd=/home/deploy user=deploy/usr/bin/jqThis is the single most important thing to know about cron. A cron job does not run in your normal login environment:
PATHwas only/usr/bin:/bin. Mynode(in/opt/node22/bin, which my shell has on its PATH) was not found:which node jqprinted onlyjq. This is the number-one reason "my script works in the terminal but not from cron".SHELLwas/bin/sh, not bash. Bash-only features may not work.- The working directory was the user's home folder, not where your script lives.
- No
.bashrc, nonvm, no exported variables from your session.
The fix: use full paths for commands (/opt/node22/bin/node /opt/app/job.js), or set PATH= or SHELL= at the top of the crontab, and cd to the right folder inside the job.
The crontab file itself is stored here, with strict permissions:
$ ls -la /var/spool/cron/crontabs/-rw------- 1 deploy crontab 378 Oct 12 03:45 deployYou do not edit that file directly; use crontab -e so the syntax is checked on save.
The percent sign trap (real run)
In a crontab line, the character % is special: the cron program turns an unescaped % into a newline, and the text after the first one is passed to the command as standard input. I tested three jobs:
* * * * * echo "bad: $(date +%H)" >> /tmp/lab-linux-b/pkg/cron.out 2>&1* * * * * echo "ok: $(date +\%H)" >> /tmp/lab-linux-b/pkg/cron.out 2>&1* * * * * date +%H >> /tmp/lab-linux-b/pkg/cron.outThe output file got exactly one line:
ok: 03Only the line that escaped the percent (\%H) worked. The other two produced nothing in the file: the first became a broken shell command, the second lost its redirect because everything after % was cut off and became input. Their error output had nowhere to go. Which shows another cron fact: cron emails output and errors to the user, and on a server with no mail program the message just disappears. I saw /var/mail was empty. The lesson: redirect output yourself (>> /var/log/job.log 2>&1) and escape every % as \%, or put the command in a script file.
Other cron habits
- Time zone: cron runs in the server's local time zone (often UTC on cloud servers). A job at "02:30" can be missed or repeated around a daylight-saving change; the manual notes that times that do not exist (the "missing hour") never match. I did not test DST here.
- Overlap: if a job takes longer than its interval, cron starts another copy. Use
flock -n /tmp/job.lock commandso a second copy does not start (reviewed, not run). - Silent failure: log to a file, and check the exit code. Add a "heartbeat" check so someone is told if the job stops running.
- Idempotent jobs: if a job can be run twice by mistake, it should be safe to do so.
systemd timers: cron's modern cousin
A timer unit runs a service unit on a schedule. It has two files that share a name: backup.timer and backup.service. The service describes what to run. The timer says when.
# backup.service[Unit]Description=Nightly backup[Service]Type=oneshotExecStart=/bin/true# backup.timer[Unit]Description=Run backup every night[Timer]OnCalendar=*-*-* 02:30:00Persistent=trueRandomizedDelaySec=5min[Install]WantedBy=timers.targetsystemd-analyze verify backup.timer backup.service printed no problems. (I replaced the real backup command by /bin/true because this is only a demo of the file shapes.) You enable the timer, not the service: systemctl enable --now backup.timer (reviewed, not run).
Why use timers instead of cron?
Persistent=trueremembers the last run on disk. If the machine was off at 02:30, the job runs when the machine comes back. Cron simply misses it. This only works forOnCalendar=timers, according to the manual.RandomizedDelaySec=5minadds a random delay so a hundred servers do not all hit the same backup server at 02:30. The related settingAccuracySecdefaults to one minute, which lets systemd group wake-ups to save power; set it small if you need exact timing.- Output goes to the journal, so
journalctl -u backup.serviceshows each run. No lost mail. - You can see what is planned:
systemctl list-timers(reviewed, not run).
The calendar syntax, explained by a tool
The time format is different from cron. Instead of guessing, ask systemd-analyze calendar. It works without a running systemd:
$ systemd-analyze calendar '*-*-* 02:30:00'Normalized form: *-*-* 02:30:00 Next elapse: Tue 2026-10-13 02:30:00 UTC From now: 22h left$ systemd-analyze calendar 'Mon..Fri *-*-* 09:00' --iterations=3 | tail -7Normalized form: Mon..Fri *-*-* 09:00:00 Next elapse: Mon 2026-10-12 09:00:00 UTC From now: 5h 15min left Iteration #2: Tue 2026-10-13 09:00:00 UTC From now: 1 day 5h left Iteration #3: Wed 2026-10-14 09:00:00 UTC From now: 2 days leftAnd the shortcut words expand like this:
| You write | It means |
|---|---|
daily | *-*-* 00:00:00 |
weekly | Mon *-*-* 00:00:00 |
hourly | *-*-* *:00:00 |
*:0/15 | *-*-* *:00/15:00 (every 15 minutes) |
A bad expression is rejected clearly:
$ systemd-analyze verify bad4.timerbad4.timer:2: Failed to parse calendar specification, ignoring: every nightbad4.timer: Timer unit lacks value setting. Refusing.Note both lines: the unit is not just "ignored", it is refused because a timer with no valid schedule is useless.
For "run N seconds after boot" or "every 10 minutes after the last run finished", timers have OnBootSec= and OnUnitActiveSec=. Check systemd.timer(5) for details; I read the page but did not run these.
Cron or timer?
| cron | systemd timer | |
|---|---|---|
| Setup | one line | two unit files |
| Logs | email (often lost) or your redirect | the journal, automatically |
| Missed run while off | skipped | Persistent=true catches up |
| Spread load | write your own sleep | RandomizedDelaySec |
| Where it works | any Unix, also in containers with a cron daemon | needs systemd |
| Dependencies and limits | none | can use After=, resource limits, sandboxing |
My rule: on a normal Linux server with systemd, use timers for anything important. Use cron for something quick, for compatibility, or where systemd is not there.
A short checklist before you ship a service
- Does the unit use a full path and a non-root
User=? - Did
systemd-analyze verifysay nothing? - After
daemon-reloadandenable --now, doessystemctl statussayactive (running)? - If you kill the process with
kill -9, doesRestart=bring it back? (reviewed, not run) - Can you read its logs with
journalctl -u? - For scheduled jobs: are all paths full, all
%escaped, output redirected, and overlaps prevented?
Try it
You need a Linux machine. Use a VM or a Linux box where you may install things.
- Run
dpkg -S $(command -v ls)(orrpm -qfon Fedora). Which package ownsls? - Run
apt-get install -s htop(or any package you do not have). What doesInsttell you? Did it add dependencies? - Write a small
hello.servicewithType=oneshotandExecStart=/bin/echo hello. Runsystemd-analyze verify hello.service. Now misspellExecStartasExecStrat. What does verify say? - Run
systemd-analyze calendar 'Mon..Fri *-*-* 09:00' --iterations=3. Why is the first answer not always today? - Add this to
crontab -e:* * * * * date +%s >> /tmp/c.out. After two minutes, what is in the file? Then fix the line. - Predict, then check with a cron parser or by reasoning: when does
0 9 1 * 1run?
Answers
- On most Debian systems it is
coreutils. (lsbelongs to a package of basic tools.) Instmeans "would be installed". If the package depends on libraries you lack, those appear as extraInstlines.- I ran it. The misspelled key gave
Unknown key name 'ExecStrat' in section 'Service', ignoring.and thenService has no ExecStart=, ExecStop=, or SuccessAction=. Refusing.A service needs a command to run, so this typo is not just ignored; the whole unit is refused. - If it is already past 09:00 today or it is a weekend, the next time is the next weekday morning.
- The file will be empty or wrong:
%sis cut at the percent sign. The fix isdate +\%s. - At 09:00 on the 1st of every month and at 09:00 every Monday (both day fields restricted, so either matches).
Interview questions
What is the difference between apt update and apt upgrade? (Must know)
apt update downloads the latest package lists from the repositories. It installs nothing. apt upgrade installs newer versions of the packages I already have, using those lists. If I skip update, apt works from a stale list. In Dockerfiles I put apt-get update && apt-get install -y ... in one RUN line so a cached old list is not reused. And before upgrading a production machine I check the plan with a simulation.
What is the difference between dpkg and apt? (Must know)
dpkg is the low-level tool that installs, removes and queries individual .deb packages on this machine. It does not download or solve dependencies. apt is the higher-level tool that talks to repositories, finds dependencies, downloads, and calls dpkg to do the work. For questions like "which package owns this file" I use dpkg -S; for installs I use apt.
apt remove versus apt purge? (Must know)
remove deletes the package's files but keeps its configuration files, so a reinstall keeps my settings. purge removes the configuration too. In a dry run they show different markers, Remv and Purg. I use purge when I really want a clean start, and I take a backup of /etc settings first for services that matter.
What is systemd, and what is a unit? (Must know)
systemd is the init system, the first process (PID 1) on most modern distributions. It starts services in the right order, restarts them, and collects their logs. A unit is anything it manages, described in a unit file: services, timers, sockets, mount points and more. A service unit has [Unit], [Service] and [Install] sections.
What is the difference between systemctl enable and systemctl start? (Must know)
start runs the service now but does nothing about boot. enable creates the link so the service starts at boot but does not start it now. To do both I use systemctl enable --now. After editing a unit file I also need systemctl daemon-reload, otherwise systemd still uses the old copy.
Your service crashes and does not come back. What do you check? (Must know)
I run systemctl status to see the state and last logs, and journalctl -u name -n 100 for details. Then I check the unit: is Restart=on-failure (or always) there, and was it spelled right? A wrong value is silently ignored, and systemd-analyze verify would have warned me. I also check whether it hit the start rate limit and was marked failed, and whether the user, the working directory, the environment variables and the file permissions are right. I could not run these on my sandbox (no systemd), so I know them from the manual pages.
My script works in the terminal but not from cron. Why? (Must know)
The environment is different. In my real test, cron gave PATH=/usr/bin:/bin, SHELL=/bin/sh, and the user's home as working directory. So commands in other folders (like node) were not found. The fixes: full paths to every command, PATH= or SHELL= lines at the top of the crontab, cd inside the job, and redirect output to a log file so I can see the errors.
What does 30 4 1,15 5 mean? (Trap)
Most people say "4:30 on the 1st and 15th if it is a Friday". The correct answer: when both the day-of-month and day-of-week fields are restricted, cron runs the job when either matches. So it runs at 04:30 on the 1st, on the 15th, and on every Friday. The cron-parser library I tried agreed. To mean "and", I would run the job daily and test the date in the script.
Why does my cron command with date +%Y-%m-%d fail? (Trap)
In a crontab line an unescaped % is turned into a newline, and the rest is sent to the command as standard input. So the command is cut off. I tested it: the unescaped versions produced no output, and \% worked. I escape each % or put the command inside a script.
Cron or a systemd timer? (Senior signal)
On a systemd machine I prefer timers for important jobs. They log to the journal, can catch up after downtime (Persistent=true, only for OnCalendar timers), can spread the load (RandomizedDelaySec), and can use the same sandboxing as services. Cron is simpler, works on any Unix, and is fine for small jobs. I remember that in a typical container there is no systemd, so a timer will not work there.
How do you run a long-lived Node.js app on a server? (Senior signal)
I would write a systemd service: a dedicated non-root user, full path to node, WorkingDirectory, Environment or EnvironmentFile for settings, Restart=on-failure with RestartSec, and a few hardening options like NoNewPrivileges. I check it with systemd-analyze verify, enable it with systemctl enable --now, and read logs with journalctl -u. The app logs to stdout and the journal does the rest. In a container setup I would run Node as the single main process and let the orchestrator restart it, as in Docker lesson 2.
Is adding a third-party apt repository safe? (Trap)
Not automatically. Packages from that repo can run scripts as root during install and can replace system packages. It is safe only as far as I trust that publisher and key. I check what a repo adds, keep its key limited to that repo, and prefer the distribution's own packages when possible.
systemctl status says "System has not been booted with systemd". What does that mean? (Trap)
It means systemd is not PID 1 on this machine. This is normal inside most containers and some WSL setups. systemctl is not broken; there is just no systemd to talk to. I would run the app directly as the container's main process, or use another supervisor.
Key takeaways
dpkgis the low-level package tool;aptadds repositories and dependencies.apt updaterefreshes lists;apt upgradeinstalls. Use-sto look before you act.removekeeps config files;purgedeletes them too.- A service needs a supervisor. A systemd service unit has
[Unit],[Service],[Install].enableis for boot,startis for now, anddaemon-reloadis needed after edits. - systemd ignores bad lines silently, so run
systemd-analyze verifybefore you trust a unit. It checks syntax, not whether the user exists or the service will start. - Cron runs with a tiny environment (
PATH=/usr/bin:/bin,sh, home directory). Use full paths, redirect output, escape%. - If both cron day fields are set, the job runs when either matches.
- Timers add logs in the journal, catch-up after downtime, and load spreading.
- Containers often have no systemd. In this sandbox
systemctlandjournalctl -ucould not run, so those commands in this lesson are from the manuals, not from my output.
What's next
In lesson 7 we handle the bad day: the server is slow, the disk is full, or memory is gone. You will see df say "100%" for two different reasons, a deleted file that still takes space, a runaway process, and a step-by-step method to find the problem. Practise now: write a one-line cron job for yourself that logs the date to a file, with full paths and an escaped percent sign, and confirm it writes after one minute.
Published by Jainik · Last updated · How these lessons are made
Keep learning
Related lessons
- Why This Package? Choosing and Carrying Dependencies · Team Lead Mindset
Part of the study guide
Related series