Skip to main content

Replacing rc.local in systemd Linux systems

Missing rc.local for adding commands to run on startup? Here's how to set up similar functionality with today's systemd.
Image
Replacing rc.local in systemd

I recently encountered two different problems on two different Linux hosts. Each required a unique circumvention because I have yet to find a real solution. However, the method for delivering each circumvention was the same: Run a command during or soon after Linux startup.

The rc.local file was—and in some cases still is—the place for Linux sysadmins to put commands that need to be run at startup. Use of the rc.local file is not only deprecated but after a couple of hours worth of attempts, was not working in any event. This despite the fact that the systemd documentation mentions the use of a "generator" that generates systemd services from an rc.local file if one exists. (That seems to be a good way as any to enforce deprecation—make it not work.)

The details of my specific problem are not particularly relevant to this discussion, so I will use a simple and easily tracked command as the content of our local startup file. We will add a date-stamped line to a local log file to verify that the Bash program we need to run at startup actually works.

Boot vs. startup

Understanding the Linux boot and startup process is important for configuring Linux and resolving startup issues. In reality, there are two sequences of events that are required to boot a Linux computer and make it usable: boot and startup. The boot sequence starts when the computer is turned on and finishes when the kernel is initialized and systemd is launched. The startup process then takes over and finishes the task of getting the Linux computer into an operational state.

Overall, the Linux boot and startup process is fairly simple to understand. It is comprised of the following steps, which will be described later in more detail:

  1. BIOS Power-On Self-Test (POST)
  2. Boot loader (GRUB2)
  3. Kernel
  4. systemd

For a much more detailed description of both the boot and startup sequences, refer to my article, An introduction to the Linux boot and startup processes.

Local startup

Sysadmins sometimes add commands to the startup sequence that are locally useful. These additions may aim to start or run local processes that are not part of the standard systemd startup. It is possible to add a new systemd service unit to launch each program needed at startup, but the old rc.local method provided a single executable file for any and all local startup needs. We, too, can use this single file approach with systemd. The elegance of this solution is that it makes it easy to add more startup commands at a later time, without the need to add more service units to systemd.

Our solution is to create a single systemd service unit and place any needed Linux commands into the executable file. There are two parts to this solution. One is obvious: We need an executable file. And two, we need to create a service unit for systemd that runs the executable.

Create the executable file

This is a trivial exercise for any sysadmin familiar with Bash programming. In fact, we will create a Bash program and place it in the Linux Filesystem Hierarchical Standard (FHS) location for local executable files, /usr/local/bin. An argument could be made for placing this executable file in another location, but /usr/local/bin is the one that makes the most sense to me since this location makes it easy for the sysadmin to run the script from the command line if necessary. The /usr/local/bin directory is always in every user’s $PATH, including that of the root user.

Create the mystartup.sh file shown here and place it in /usr/local/bin (be sure to make it executable). Be sure to use the location for Bash that is correct for your distribution. For example, Debian-based distributions locate Bash at /bin/bash.

#!/usr/bin/bash

################################################################################
# mystartup.sh
#
# This shell program is for testing a startup like rc.local using systemd.
# By David Both
# Licensed under GPL V2
#
################################################################################

# This program should be placed in /usr/local/bin

################################################################################
# This is a test entry

echo `date +%F" "%T` "Startup worked" >> /root/mystartup.log

Note: The comments in the included files tell you where they need to be located.

Be sure to test this executable by running it from the command line. The first time you run this shell script, you should see a new file, /root/mystartup.log, with a time and date along with the text, "Startup worked". We create this log file and add lines to it every time the script is run as a simple test to ensure that our script is working.

Run the script a couple more times. Your results should be similar to those here:

[root@testvm1 ~]#  mystartup.sh

[root@testvm1 ~]#  cat mystartup.log

2019-09-12 19:58:00 Startup worked

2019-09-12 19:58:17 Startup worked

2019-09-12 19:58:54 Startup worked

2019-09-12 19:59:00 Startup worked

2019-09-12 20:01:08 Startup worked

2019-09-12 20:04:01 Startup worked

2019-09-12 20:04:13 Startup worked

2019-09-12 20:06:11 Startup worked

2019-09-12 20:06:28 Startup worked

2019-09-16 09:51:21 Startup worked

2019-09-16 09:51:51 Startup worked

That is all we need to do to create the file that may eventually contain our local startup commands. Just add anything that needs to run at startup to this file.

Create the systemd service

The service unit we will now create is a standard systemd service unit file. This simple file is used only to run the mystartup.sh script at startup.

Create a new file, /usr/local/lib/systemd/system/mystartup.service, and add the contents shown below:

################################################################################
# mystartup.service
#
# This service unit is for testing my systemd startup service
# By David Both
# Licensed under GPL V2
#
################################################################################
# This program should be placed in /usr/local/lib/systemd/system/.
# Create a symlink to it from the /etc/systemd/system directory.
################################################################################

[Unit]

Description=Runs /usr/local/bin/mystartup.sh

  
[Service]

ExecStart=/usr/local/bin/mystartup.sh


[Install]

WantedBy=multi-user.target

This file does not need to be executable. This file could also be located in /etc/systemd/system, but as a local file it is better placed in the /usr/local branch of the directory structure, with a link to it from /etc/systemd.system.

Now, go to /etc/systemd/system and create the symbolic link in the service unit file:

[root@testvm1 system]#  ln -s /usr/local/lib/systemd/system/mystartup.service

Test the service unit

We should test the final service unit file before rebooting the Linux host for the final test. First, let’s verify that systemd sees the service:

[root@testvm1 ~]#  systemctl status mystartup

● mystartup.service - Runs /usr/local/bin/mystartup.sh

Loaded: loaded (/usr/local/lib/systemd/system/mystartup.service; linked; vendor preset: disabled)

Active: inactive (dead)

[root@testvm1 ~]#

This result tells us that the service is recognized by systemd. Now, let’s start the service. Doing so will run the script but will not configure the new service to run at boot time:

[root@testvm1 ~]#  systemctl start mystartup

Check the log file’s contents to verify the new line was added.

Enable the service

All that is left is to enable the service so that it runs on startup:

[root@testvm1 ~]#  systemctl enable mystartup

Created symlink /etc/systemd/system/multi-user.target.wants/mystartup.service →

/usr/local/lib/systemd/system/mystartup.service.

[root@testvm1 ~]#

Final test

Before we reboot, let’s look the journalctl command and how we can use it to view the journal entries that relate to mystartup.service. We can also use the journalctl command to verify this because systemd keeps a journal of everything it does.

In the following command, the -u option shows only entries for the mystartup unit:

[root@testvm1 ~]#  journalctl -u mystartup

-- Logs begin at Mon 2019-04-15 22:50:27 EDT, end at Mon 2019-09-16 11:44:30 EDT. --

Sep 16 11:09:28 testvm1 systemd[1]: Started Runs /usr/local/bin/mystartup.sh.

[root@testvm1 ~]#

Now, reboot the Linux host and check the log file to ensure that a new line was added:

[root@testvm1 ~]#  systemctl status mystartup

● mystartup.service - Runs /usr/local/bin/mystartup.sh

Loaded: loaded (/usr/local/lib/systemd/system/mystartup.service; enabled; vendor preset: disabled)

Active: inactive (dead) since Mon 2019-09-16 11:45:59 EDT; 1min 30s ago

Process: 819 ExecStart=/usr/local/bin/mystartup.sh (code=exited, status=0/SUCCESS)

Main PID: 819 (code=exited, status=0/SUCCESS)


Sep 16 11:45:55 testvm1 systemd[1]: Started Runs /usr/local/bin/mystartup.sh.

[root@testvm1 ~]#  journalctl -u mystartup

-- Logs begin at Mon 2019-04-15 22:50:27 EDT, end at Mon 2019-09-16 11:47:45 EDT. --

Sep 16 11:09:28 testvm1 systemd[1]: Started Runs /usr/local/bin/mystartup.sh.

-- Reboot --

Sep 16 11:45:55 testvm1 systemd[1]: Started Runs /usr/local/bin/mystartup.sh.

[root@testvm1 ~]#

Conclusion

The Bash shell script we have created for this experiment runs once at startup and then exits. It does not remain in memory as a daemon because it was not designed to do so.

In case you had not noticed, the procedure we used to create our local startup service can also be used to create any new service for systemd. It is not that hard once we know how to do it.

Update

Soon after this article was published I received an email from Tom Murphy who informed me of the existence of the rc-local service that is part of systemd. I appreciate that email because I was not aware of that service so I learned something new.

It is possible to add support for the old rc.local file by enabling the service with the command, systemctl enable rc-local. The commands in the rc.local file will run at the next boot. Of course, you can use systemctl enable rc-local to run rc.local immediately.

However, it is still true that rc.local is obsolete. The man page for systemd-rc-local-generator states, “Support for /etc/rc.local is provided for compatibility with specific System V systems only. However, it is strongly recommended to avoid making use of this script today, and instead provide proper unit files with appropriate dependencies for any scripts to run during the boot process.”

Resources

[Want to try out Red Hat Enterprise Linux? Download it now for free.]

Topics:   Linux  
Author’s photo

David Both

David Both is an open source software and GNU/Linux advocate, trainer, writer, and speaker who lives in Raleigh, NC. He is a strong proponent of and evangelist for the "Linux Philosophy." David has been in the IT industry for over 50 years. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.