One script per service, one line in rc.conf to switch it on, and an order worked out from the script headers. Then write a daemon and its rc script from scratch, with a systemctl-to-rc.d table to keep.
▶Prefer to watch? This short video runs this page's main commands on a real NetBSD 11.1 machine, with on-screen captions and a synthetic voiceover (an AI voice trained on the author's own recording). Click to play, or watch it on YouTube.
echo 'ntpd=YES' >> /etc/rc.conf # switch a service on at boot
/etc/rc.d/ntpd start # start it now
service ntpd status # ask about it
NetBSD starts everything at boot from small shell scripts in /etc/rc.d, in an order worked out from the script headers. A single line in /etc/rc.conf switches a service on. This page shows how to control services, how the order is decided, and how to write an rc script for a daemon of your own, with a table from systemctl to rc.d.
Every example ran for real on NetBSD 11.1 (amd64) in a virtual machine, with root commands marked #. The output you see is the output the machine gave.
How NetBSD starts services
NetBSD has no systemd and no unit files. When the machine boots, init runs one shell script, /etc/rc, and that script runs every script in /etc/rc.d in the right order. One script is one service, and the scripts are short, readable shell. If you can read sh, you can see exactly what happens at boot.
Every example here ran for real on NetBSD 11.1 (amd64) in a virtual machine. Commands that change the system start with # and run as root, because NetBSD has no sudo by default. First look at what is there, and at one script:
$ ls /etc/rc.d | wc -l
132
$ sed -n '5,17p' /etc/rc.d/sshd
# PROVIDE: sshd
# REQUIRE: LOGIN
$_rc_subr_loaded . /etc/rc.subr
name="sshd"
rcvar=$name
command="/usr/sbin/${name}"
pidfile="/var/run/${name}.pid"
required_files="/etc/ssh/sshd_config"
extra_commands="check keygen keyregen reload"
The comment lines at the top are not decoration. PROVIDE names what the script offers, REQUIRE names what must be started before it, and the code below uses rc.subr, a library of shell functions that does the work of starting, stopping and checking a service. A typical script only sets a few variables and then calls run_rc_command.
Controlling a service
Every script takes the same words: start, stop, restart, status, and for many services also reload and check. For the running ssh daemon:
$ /etc/rc.d/sshd status
sshd is running as pid 834.
# /etc/rc.d/sshd check; echo "exit status $?"
exit status 0
$ service sshd status
sshd is running as pid 834.
check tests the configuration without touching the running daemon. The service command is a short way to run a script: service sshd status is the same as /etc/rc.d/sshd status. It can also list things:
$ service -e | head -n 4
/etc/rc.d/fsck_root
/etc/rc.d/modules
/etc/rc.d/root
/etc/rc.d/ccd
-e lists the services that are enabled, in the order they start. -l lists all of them. Both come from rcorder, which reads the PROVIDE and REQUIRE lines of every script and works out the order.
Switching a service on: rc.conf
A script only starts when its variable in /etc/rc.conf says YES. That is the switch. The defaults are in /etc/defaults/rc.conf, and you never edit that file: you set only what you change in /etc/rc.conf. Ask a script which variable it uses with rcvar, and look up the default:
$ /etc/rc.d/ntpd rcvar
# ntpd
ntpd=NO
$ grep -E '^ntpd' /etc/defaults/rc.conf
ntpdate=NO ntpdate_flags="-b -s" # May need '-u' thru firewall
ntpd=NO ntpd_flags="" # see below for ntpd_chrootdir
NO is the default, so ntpd is off. NetBSD also reads a separate file for each service from /etc/rc.conf.d. That is handy when a package or a script of your own should not touch rc.conf:
# mkdir -p /etc/rc.conf.d
# echo 'ntpd=YES' > /etc/rc.conf.d/ntpd
$ /etc/rc.d/ntpd rcvar
# ntpd
ntpd=YES
# rm /etc/rc.conf.d/ntpd
$ /etc/rc.d/ntpd rcvar
# ntpd
ntpd=NO
If a service is not enabled, its script refuses to start it and tells you so. The word onestart ignores the setting and starts it once, which is useful for a test. Linux has systemctl enable for this. Here you edit one line.
The order of things
The REQUIRE and PROVIDE lines make a dependency graph. Some names are real services and some are only markers, like NETWORKING and DAEMON, that stand for a stage of the boot:
$ grep -E '^# (PROVIDE|REQUIRE)' /etc/rc.d/sshd /etc/rc.d/DAEMON
/etc/rc.d/sshd:# PROVIDE: sshd
/etc/rc.d/sshd:# REQUIRE: LOGIN
/etc/rc.d/DAEMON:# PROVIDE: DAEMON
/etc/rc.d/DAEMON:# REQUIRE: NETWORKING SERVERS
$ rcorder /etc/rc.d/* 2>/dev/null | grep -n -E '/(NETWORKING|DAEMON|sshd)$'
39:/etc/rc.d/NETWORKING
86:/etc/rc.d/DAEMON
112:/etc/rc.d/sshd
sshd needs LOGIN, and DAEMON needs the network and the servers it depends on, so the network comes first and sshd comes later. rcorder prints the order that /etc/rc uses, and the position numbers show it. You never write the order yourself. You say what you need, and rcorder works it out.
Writing your own service
A service is a program and a small rc script. First the program. A real daemon puts itself in the background, so this one does the same. It writes a line to a log file every two seconds, and records its process id:
$ cat /tmp/hellod
#!/bin/sh
# a tiny daemon: write a line to the log every 2 seconds
(
while :; do
date '+%T hellod alive' >> /var/log/hellod.log
sleep 2
done
) </dev/null >/dev/null 2>&1 &
echo $! > /var/run/hellod.pid
# mkdir -p /usr/local/sbin
# install -m 755 /tmp/hellod /usr/local/sbin/hellod
The directory /usr/local/sbin does not exist on a fresh install, so it is created first. install copies the file and sets the mode in one step. Now the rc script, which is almost all boilerplate. The program is a shell script, so command_interpreter tells rc.subr how to recognise it in the process list:
$ cat /tmp/hellod.rc
#!/bin/sh
#
# PROVIDE: hellod
# REQUIRE: DAEMON
# KEYWORD: shutdown
$_rc_subr_loaded . /etc/rc.subr
name="hellod"
rcvar=$name
command="/usr/local/sbin/hellod"
command_interpreter="/bin/sh"
pidfile="/var/run/${name}.pid"
load_rc_config $name
run_rc_command "$1"
# install -m 755 /tmp/hellod.rc /etc/rc.d/hellod
# /etc/rc.d/hellod status; echo "exit status $?"
/etc/rc.d/hellod: WARNING: $hellod is not set properly - see rc.conf(5).
$hellod is not enabled - see rc.conf(5).
Use the following if you wish to perform the operation:
/etc/rc.d/hellod onestatus
exit status 1
The first run says the service is not enabled. That is correct. The warning above it appears because the variable has no value yet, and a service that is switched off gives the same messages. Enable it and try again:
# echo 'hellod=YES' >> /etc/rc.conf
# /etc/rc.d/hellod start
Starting hellod.
# sleep 3; /etc/rc.d/hellod status
hellod is running as pid 1742.
$ cat /var/log/hellod.log
17:58:13 hellod alive
17:58:15 hellod alive
17:58:17 hellod alive
The KEYWORD shutdown line means that the script is also run with stop when the machine shuts down. Without it, a daemon would just be killed.
Restarting, stopping and mistakes
The usual words all work on the new service, and the exit status tells a script what happened:
# /etc/rc.d/hellod restart
Stopping hellod.
Starting hellod.
# /etc/rc.d/hellod start; echo "exit status $?"
hellod already running? (pid=1867).
exit status 1
# /etc/rc.d/hellod stop
Stopping hellod.
# /etc/rc.d/hellod stop; echo "exit status $?"
hellod not running? (check /var/run/hellod.pid).
exit status 1
# /etc/rc.d/hellod status; echo "exit status $?"
hellod is not running.
exit status 1
Starting a service that already runs, or stopping one that does not, is reported and exits with 1, so a script can tell. A script that is not executable is the commonest mistake of all:
# chmod -x /etc/rc.d/hellod
# /etc/rc.d/hellod start; echo "exit status $?"
sh: /etc/rc.d/hellod: no execute permission
exit status 126
# chmod +x /etc/rc.d/hellod
To remove the service, delete the line from /etc/rc.conf, and delete the script. Nothing else is registered anywhere, because the directory is the registry.
Linux to NetBSD side by side
The ideas map closely:
- systemctl start name: /etc/rc.d/name start, or service name start.
- systemctl stop, restart, status: the same words.
- systemctl enable name: name=YES in /etc/rc.conf, or a file in /etc/rc.conf.d.
- systemctl is-enabled: /etc/rc.d/name rcvar.
- systemctl list-units: service -e, and service -l.
- A unit file with After= and Wants=: PROVIDE and REQUIRE in the script header.
- ExecStart=: command= and command_args= in the script.
- journalctl: plain text files in /var/log, written by syslogd.
- systemd-analyze: rcorder, which prints the order.
The scripts for the system are in /etc/rc.d. Packages install an example script into /usr/pkg/share/examples/rc.d, and you copy it to /etc/rc.d when you want it. Nothing is enabled without your say.