{"id":5515,"date":"2024-05-31T02:15:00","date_gmt":"2024-05-31T06:15:00","guid":{"rendered":"https:\/\/www.both.org\/?p=5515"},"modified":"2025-08-16T16:44:18","modified_gmt":"2025-08-16T20:44:18","slug":"replacing-rc-local-in-systemd-linux-systems","status":"publish","type":"post","link":"https:\/\/www.both.org\/?p=5515","title":{"rendered":"Replacing rc.local in systemd Linux systems"},"content":{"rendered":"<div class=\"pld-like-dislike-wrap pld-template-1\">\r\n    <div class=\"pld-like-wrap  pld-common-wrap\">\r\n    <a href=\"javascript:void(0)\" class=\"pld-like-trigger pld-like-dislike-trigger  \" title=\"\" data-post-id=\"5515\" data-trigger-type=\"like\" data-restriction=\"cookie\" data-already-liked=\"0\">\r\n                        <i class=\"fas fa-thumbs-up\"><\/i>\r\n                <\/a>\r\n    <span class=\"pld-like-count-wrap pld-count-wrap\">    <\/span>\r\n<\/div><\/div>\n<p>Are you missing <code>rc.local<\/code> for adding commands to run on startup? Here&#8217;s how to set up similar functionality with today&#8217;s <code>systemd<\/code>.<\/p>\n\n\n\n<p id=\"replacing-rc.local-in-systemd\">A few years ago I encountered two different problems on two different Linux hosts. Each required a unique circumvention because I had not yet found a real solution. However, the method for delivering each circumvention was the same: Run a command during or soon after Linux startup.<\/p>\n\n\n\n<p>The <code>rc.local<\/code> file was\u2014and in some cases still is\u2014the place for Linux sysadmins to put commands that need to be run at startup. Use of the <code>rc.local<\/code> 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 <code>systemd<\/code> documentation mentions the use of a &#8220;generator&#8221; that generates <code>systemd<\/code> services from an <code>rc.local<\/code> file if one exists. That seems to be a good way as any to enforce deprecation\u2014make it not work.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"boot-vs.-startup\">Boot vs. startup<\/h2>\n\n\n\n<p>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 <code>systemd<\/code> is launched. The startup process then takes over and finishes the task of getting the Linux computer into an operational state.<\/p>\n\n\n\n<p>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:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>BIOS Power-On Self-Test (POST)<\/li>\n\n\n\n<li>Boot loader (GRUB2)<\/li>\n\n\n\n<li>Kernel<\/li>\n\n\n\n<li><code>systemd<\/code><\/li>\n<\/ol>\n\n\n\n<p>For a much more detailed description of both the boot and startup sequences, refer to my other article, <a href=\"https:\/\/www.both.org\/?p=4597\" target=\"_blank\" rel=\"noreferrer noopener\">An introduction to the Linux boot and startup processes with GPT and GRUB2 <\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"local-startup\">Local startup<\/h2>\n\n\n\n<p>System administrators 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 <code>systemd<\/code> startup. It is possible to add a new <code>systemd<\/code> service unit to launch each program needed at startup, but the old <code>rc.local<\/code> method provided a single executable file for any and all local startup needs. We, too, can use this single file approach with <code>systemd<\/code>. 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 <code>systemd<\/code>.<\/p>\n\n\n\n<p>Our solution is to create a single <code>systemd<\/code> service unit and place any needed Linux commands into the executable file. There are two parts to this solution. One is&nbsp;obvious: We need an executable file. And two, we need to create a service unit for <code>systemd<\/code> that&nbsp;runs the executable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"create-the-executable-file\">Create the executable file<\/h3>\n\n\n\n<p>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 <a href=\"http:\/\/www.linux-databook.info\/?page_id=2609\" target=\"_blank\" rel=\"noreferrer noopener\">Linux Filesystem Hierarchical Standard (FHS)<\/a> location for local executable files, <code>\/usr\/local\/bin<\/code>. An argument could be made for placing this executable file in another location,&nbsp;but <code>\/usr\/local\/bin<\/code> 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 <code>\/usr\/local\/bin<\/code> directory is always in every user\u2019s <code>$PATH<\/code>, including that of the root user.<\/p>\n\n\n\n<p>Create the <code>mystartup.sh<\/code> file shown here and place it in <code>\/usr\/local\/bin<\/code> (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 <code>\/bin\/bash<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/bash\n\n################################################################################\n# mystartup.sh\n#\n# This shell program is for testing a startup like rc.local using systemd.\n# By David Both\n# Licensed under GPL V2\n#\n################################################################################\n# This program should be placed in \/usr\/local\/bin\n################################################################################\n# This is a test entry\necho `date +%F\" \"%T` \"Startup worked\" &gt;&gt; \/root\/mystartup.log<\/code><\/pre>\n\n\n\n<p><strong>Note:<\/strong> The comments in the included files tell you where they need to be located.<\/p>\n\n\n\n<p>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, <code>\/root\/mystartup.log<\/code>, with a time and date along with the text, <code>\"Startup worked\"<\/code>. 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.<\/p>\n\n\n\n<p>Run the script a couple more times. Your results should be similar to those here:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;root@testvm1 ~]#  <strong>mystartup.sh<\/strong>\n&#91;root@testvm1 ~]#  <strong>cat mystartup.log<\/strong>\n2019-09-12 19:58:00 Startup worked\n2019-09-12 19:58:17 Startup worked\n2019-09-12 19:58:54 Startup worked\n2019-09-12 19:59:00 Startup worked\n2019-09-12 20:01:08 Startup worked\n2019-09-12 20:04:01 Startup worked\n2019-09-12 20:04:13 Startup worked\n2019-09-12 20:06:11 Startup worked\n2019-09-12 20:06:28 Startup worked\n2019-09-16 09:51:21 Startup worked\n2019-09-16 09:51:51 Startup worked<\/code><\/pre>\n\n\n\n<p>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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"create-the-systemd-service\">Create the systemd service<\/h3>\n\n\n\n<p>The service unit we will now create is a standard <code>systemd<\/code> service unit file. This simple file is used only to run the <code>mystartup.sh<\/code> script at startup. Create a new file, <code>\/usr\/local\/lib\/systemd\/system\/mystartup.service<\/code>, and add the contents shown below:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>################################################################################\n# mystartup.service\n#\n# This service unit is for testing my systemd startup service\n# By David Both\n# Licensed under GPL V2\n#\n################################################################################\n# This program should be placed in \/usr\/local\/lib\/systemd\/system\/.\n# Create a symlink to it from the \/etc\/systemd\/system directory.\n################################################################################\n\n&#91;Unit]\nDescription=Runs \/usr\/local\/bin\/mystartup.sh\n  \n&#91;Service]\nExecStart=\/usr\/local\/bin\/mystartup.sh\n\n&#91;Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p>This file does not need to be executable. This file could also be located in <code>\/etc\/systemd\/system<\/code>, but as a local file it is better placed in the <code>\/usr\/local<\/code> branch of the directory structure, with a link to it from <code>\/etc\/systemd.system<\/code>.<\/p>\n\n\n\n<p>Now, go to <code>\/etc\/systemd\/system<\/code> and create the symbolic link in the service unit file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;root@testvm1 system]# <strong>ln -s \/usr\/local\/lib\/systemd\/system\/mystartup.service<\/strong><\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"test-the-service-unit\">Test the service unit<\/h3>\n\n\n\n<p>We should test the final service unit file before rebooting the Linux host for the final test. First, let\u2019s verify that <code>systemd<\/code> sees the service:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;root@testvm1 ~]#  <strong>systemctl status mystartup<\/strong>\n\u25cf mystartup.service - Runs \/usr\/local\/bin\/mystartup.sh\nLoaded: loaded (\/usr\/local\/lib\/systemd\/system\/mystartup.service; linked; vendor preset: disabled)\nActive: inactive (dead)\n&#91;root@testvm1 ~]#<\/code><\/pre>\n\n\n\n<p>This result tells us that the service is recognized by <code>systemd<\/code>. Now, let\u2019s start the service. Doing so will run the script but will not configure the new service to run at boot time:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;root@testvm1 ~]# <strong>systemctl start mystartup<\/strong><\/code><\/pre>\n\n\n\n<p>Check the log file\u2019s contents to verify the new line was added.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"enable-the-service\">Enable the service<\/h3>\n\n\n\n<p>All that is left is to enable the service so that it runs on startup:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;root@testvm1 ~]# <strong>systemctl enable mystartup<\/strong>\nCreated symlink \/etc\/systemd\/system\/multi-user.target.wants\/mystartup.service \u2192\n\/usr\/local\/lib\/systemd\/system\/mystartup.service.\n&#91;root@testvm1 ~]#<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"final-test\">Final test<\/h3>\n\n\n\n<p>Before we reboot, let\u2019s look the <code>journalctl<\/code> command and how we can use it to view the journal entries that relate to <code>mystartup.service<\/code>. We can also use the <code>journalctl<\/code> command to verify this because <code>systemd<\/code> keeps a journal of everything it does.<\/p>\n\n\n\n<p>In the following command, the <code>-u<\/code> option shows only entries for the <code>mystartup<\/code> unit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;root@testvm1 ~]#  <strong>journalctl -u mystartup<\/strong>\n-- Logs begin at Mon 2019-04-15 22:50:27 EDT, end at Mon 2019-09-16 11:44:30 EDT. --\nSep 16 11:09:28 testvm1 systemd&#91;1]: Started Runs \/usr\/local\/bin\/mystartup.sh.\n&#91;root@testvm1 ~]#<\/code><\/pre>\n\n\n\n<p>Now, reboot the Linux host and check the log file to ensure that a new line was added:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;root@testvm1 ~]#  <strong>systemctl status mystartup<\/strong>\n\u25cf mystartup.service - Runs \/usr\/local\/bin\/mystartup.sh\nLoaded: loaded (\/usr\/local\/lib\/systemd\/system\/mystartup.service; enabled; vendor preset: disabled)\nActive: inactive (dead) since Mon 2019-09-16 11:45:59 EDT; 1min 30s ago\nProcess: 819 ExecStart=\/usr\/local\/bin\/mystartup.sh (code=exited, status=0\/SUCCESS)\nMain PID: 819 (code=exited, status=0\/SUCCESS)\nSep 16 11:45:55 testvm1 systemd&#91;1]: Started Runs \/usr\/local\/bin\/mystartup.sh.\n\n&#91;root@testvm1 ~]#  <strong>journalctl -u mystartu<\/strong>p\n-- Logs begin at Mon 2019-04-15 22:50:27 EDT, end at Mon 2019-09-16 11:47:45 EDT. --\nSep 16 11:09:28 testvm1 systemd&#91;1]: Started Runs \/usr\/local\/bin\/mystartup.sh.\n-- Reboot --\nSep 16 11:45:55 testvm1 systemd&#91;1]: Started Runs \/usr\/local\/bin\/mystartup.sh.\n&#91;root@testvm1 ~]#<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conclusion\">Conclusion<\/h2>\n\n\n\n<p>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 <code>systemd<\/code>. It is not that hard once we know how to do it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Update<\/h2>\n\n\n\n<p>Soon after this article was originally published at Opensource.com I received an email from Tom Murphy who informed me of the existence of the <code>rc-local <\/code>service that is part of <code>systemd<\/code>. I appreciate that email because I was not aware of that service so I learned something new.<\/p>\n\n\n\n<p>It is possible to add support for the old <code>rc.local<\/code> file by enabling the service with the command, <code><strong>systemctl enable rc-local<\/strong><\/code>. The commands in the rc.local file will run at the next boot. Of course, you can use&nbsp;<code>systemctl enable --now rc-local<\/code> to run <code>rc.local <\/code>immediately.<\/p>\n\n\n\n<p>However, it is still true that rc.local is obsolete. The man page for <code>systemd-rc-local-generator <\/code>states, \u201cSupport 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.\u201d<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"resources\">Resources<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Both.org: <a href=\"https:\/\/www.both.org\/?p=3157\" data-type=\"post\" data-id=\"3157\" target=\"_blank\" rel=\"noreferrer noopener\">Linux Filesystem Hierarchical Standard (FHS)<\/a><\/li>\n\n\n\n<li>Wikipedia: <a href=\"https:\/\/en.wikipedia.org\/wiki\/GNU_GRUB\" target=\"_blank\" rel=\"noreferrer noopener\">GNU GRUB<\/a> information<\/li>\n\n\n\n<li>GNU.org: <a href=\"https:\/\/www.gnu.org\/software\/grub\/manual\/grub.html\" target=\"_blank\" rel=\"noreferrer noopener\">GNU GRUB manual<\/a><\/li>\n\n\n\n<li>Wikipedia: <a href=\"https:\/\/en.wikipedia.org\/wiki\/Master_boot_record\" target=\"_blank\" rel=\"noreferrer noopener\">Master Boot Record<\/a><\/li>\n\n\n\n<li>Wikipedia: <a href=\"https:\/\/en.wikipedia.org\/wiki\/Multiboot_Specification\" target=\"_blank\" rel=\"noreferrer noopener\">Multiboot specification<\/a><\/li>\n\n\n\n<li>Wikipedia: <a href=\"https:\/\/en.wikipedia.org\/wiki\/Systemd\" target=\"_blank\" rel=\"noreferrer noopener\">systemd<\/a> information<\/li>\n\n\n\n<li>Freedesktop.org: <a href=\"https:\/\/www.freedesktop.org\/software\/systemd\/man\/bootup.html\" target=\"_blank\" rel=\"noreferrer noopener\">systemd bootup process<\/a><\/li>\n\n\n\n<li>Freedesktop.org: <a href=\"https:\/\/www.freedesktop.org\/software\/systemd\/man\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">systemd index of man pages<\/a><\/li>\n\n\n\n<li>Both.org: <a href=\"https:\/\/www.both.org\/?p=4597\" target=\"_blank\" rel=\"noreferrer noopener\">An introduction to the Linux boot and startup processes<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Missing rc.local for adding commands to run on startup? <\/p>\n<p>The rc.local file was\u2014and in some cases still is\u2014the 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 &#8220;generator&#8221; that generates systemd services from an rc.local file if one exists.<\/p>\n<p>Here&#8217;s how to set up similar functionality with today&#8217;s systemd.<\/p>\n","protected":false},"author":2,"featured_media":5520,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_lmt_disableupdate":"","_lmt_disable":"","footnotes":""},"categories":[5,419,160],"tags":[337,176],"class_list":["post-5515","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-linux","category-startup","category-systemd","tag-linux-boot-and-startup","tag-systemd"],"modified_by":"David Both","_links":{"self":[{"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/5515","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5515"}],"version-history":[{"count":9,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/5515\/revisions"}],"predecessor-version":[{"id":11552,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/5515\/revisions\/11552"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/media\/5520"}],"wp:attachment":[{"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5515"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5515"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5515"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}