15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started
10.11.2023

How to Enable Script Autoloading in Ubuntu: Three Production-Ready Methods

Enabling script autoloading in Ubuntu means configuring the operating system to automatically execute one or more shell scripts or services at system startup, without any manual intervention. This is achieved through three primary mechanisms: the legacy SysVinit-based /etc/init.d/ directory, the /etc/rc.local compatibility shim, and the modern systemd service unit framework — the latter being the authoritative, recommended approach on all Ubuntu releases from 15.04 onward.

For system administrators running workloads on a VPS Hosting environment, startup automation is not a convenience — it is a reliability requirement. A misconfigured or absent autostart entry means that critical daemons, monitoring agents, backup scripts, or custom network configurations silently fail to launch after a reboot, causing service outages that are difficult to diagnose after the fact.

Why Startup Script Automation Matters on Ubuntu Servers

Every production Ubuntu server accumulates operational scripts over time: database pre-warm routines, log rotation triggers, VPN tunnel initializers, firewall rule loaders, and application health checks. Without a structured autoloading mechanism, these scripts depend entirely on manual execution — a single missed step after a kernel update or emergency reboot can cascade into downtime.

Ubuntu's startup automation ecosystem has evolved significantly:

  • SysVinit (pre-Ubuntu 15.04): Sequential, slow, script-based. Each service blocked the next.
  • Upstart (Ubuntu 6.10–15.04): Event-driven, faster, but now deprecated.
  • systemd (Ubuntu 15.04+): Parallel service activation, dependency graphs, socket activation, cgroup-based resource control, and structured logging via journald.

Understanding which layer you are working with — and why — prevents you from deploying a working solution in a test environment that silently breaks in production.

Method 1: Using the /etc/init.d/ Directory (SysVinit / LSB Scripts)

How It Works

The /etc/init.d/ directory is the traditional home for Linux Standard Base (LSB) init scripts. Each script in this directory is a shell script that responds to standardized commands: start, stop, restart, status, and optionally reload. The update-rc.d utility creates symbolic links in the /etc/rcN.d/ runlevel directories, determining when and in what order the script executes during boot and shutdown sequences.

On modern Ubuntu systems running systemd, these scripts are still supported through a compatibility layer called systemd-sysv-generator, which automatically converts LSB init scripts into transient systemd units. This means your /etc/init.d/ scripts will still run, but they are wrapped by systemd rather than executed directly by SysVinit.

Step-by-Step Implementation

Step 1: Create your script

Write your script and ensure it follows the LSB header convention. A minimal, production-safe example:

#!/bin/bash
### BEGIN INIT INFO
# Provides:          examplescript
# Required-Start:    $remote_fs $syslog $network
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Example autoload script
# Description:       Runs a custom initialization task at boot
### END INIT INFO

case "$1" in
  start)
    echo "Starting examplescript..."
    /usr/local/bin/examplescript.sh &
    ;;
  stop)
    echo "Stopping examplescript..."
    pkill -f examplescript.sh
    ;;
  restart)
    $0 stop
    $0 start
    ;;
  status)
    pgrep -f examplescript.sh > /dev/null && echo "Running" || echo "Stopped"
    ;;
  *)
    echo "Usage: $0 {start|stop|restart|status}"
    exit 1
    ;;
esac
exit 0

Step 2: Place the script in /etc/init.d/

sudo cp examplescript /etc/init.d/examplescript

Step 3: Make it executable

sudo chmod +x /etc/init.d/examplescript

Step 4: Register it with the runlevel system

sudo update-rc.d examplescript defaults

The defaults argument registers the script to start in runlevels 2, 3, 4, and 5, and stop in runlevels 0, 1, and 6 — the standard behavior for most server daemons.

Step 5: Verify registration

ls -la /etc/rc2.d/ | grep examplescript

You should see a symlink like S01examplescript pointing back to /etc/init.d/examplescript.

Critical Pitfall

The most common mistake with this method is omitting the LSB header block. Without it, update-rc.d cannot determine dependency ordering, and systemd-sysv-generator may assign an incorrect execution order relative to network availability or filesystem mounts. Always define Required-Start dependencies explicitly.

To remove the script from autostart without deleting it:

sudo update-rc.d examplescript disable

To fully remove it:

sudo update-rc.d examplescript remove

Method 2: Using /etc/rc.local (Compatibility Shim)

How It Works

/etc/rc.local is a legacy mechanism that executes a shell script once, after all standard multi-user runlevel services have started. It is the simplest possible autostart method — no service management, no dependency declarations, no restart logic. On Ubuntu 18.04 and later, rc.local support is provided by the rc-local.service systemd unit, which is disabled by default and must be explicitly enabled.

When to Use It

Use /etc/rc.local only for:

  • One-off initialization commands that do not need to be managed as services
  • Quick prototyping or testing before formalizing a systemd unit
  • Simple environment variable exports or kernel parameter tweaks

Do not use /etc/rc.local for long-running daemons. Because it runs in a blocking, sequential manner with no process supervision, a hung command in rc.local will delay or prevent the completion of the boot sequence.

Step-by-Step Implementation

Step 1: Check if /etc/rc.local exists

ls -la /etc/rc.local

If it does not exist, create it:

sudo bash -c 'cat > /etc/rc.local << EOF
#!/bin/bash
exit 0
EOF'
sudo chmod +x /etc/rc.local

Step 2: Enable the rc-local systemd unit (Ubuntu 18.04+)

sudo systemctl enable rc-local
sudo systemctl start rc-local

Step 3: Add your command before exit 0

sudo nano /etc/rc.local

Insert your command above the exit 0 line:

#!/bin/bash
/usr/local/bin/examplescript.sh >> /var/log/examplescript.log 2>&1 &
exit 0

The & at the end is essential for any long-running command — it forks the process to the background so rc.local does not block.

Step 4: Verify execution

sudo systemctl status rc-local

Critical Pitfall

On Ubuntu 20.04 and 22.04, rc-local.service has a hardcoded 30-second timeout. If your script takes longer than 30 seconds to complete, systemd will mark the service as failed and subsequent commands in rc.local will not execute. Redirect output and background long-running processes explicitly.

Why systemd Is the Correct Approach for Production

systemd is not simply a replacement for SysVinit — it is a complete system and session manager that provides dependency resolution, parallel startup, socket and D-Bus activation, on-demand service spawning, process supervision with automatic restart, cgroup-based resource isolation, and structured log aggregation through journald. For any workload running on a Dedicated Server or production VPS, systemd units are the only appropriate mechanism for managing autoloaded scripts.

Anatomy of a systemd Unit File

A .service unit file is divided into three mandatory sections:

  • [Unit]: Metadata, human-readable description, and dependency declarations (After=, Requires=, Wants=).
  • [Service]: Execution parameters — the binary or script to run, the service type, restart policy, environment variables, and security sandboxing options.
  • [Install]: Defines which systemd target activates this unit (WantedBy=multi-user.target is the standard for server daemons).

Step-by-Step Implementation

Step 1: Prepare your script

Ensure your script is executable and located in a stable path:

sudo cp examplescript.sh /usr/local/bin/examplescript.sh
sudo chmod +x /usr/local/bin/examplescript.sh

Step 2: Create the unit file

sudo nano /etc/systemd/system/examplescript.service

A production-grade unit file with security hardening:

[Unit]
Description=Example Autoload Script
Documentation=https://your-internal-wiki/examplescript
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/examplescript.sh
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=examplescript
User=nobody
Group=nogroup
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Step 3: Reload the systemd daemon

After creating or modifying any unit file, you must reload the daemon's configuration index:

sudo systemctl daemon-reload

Step 4: Enable the service for autostart

sudo systemctl enable examplescript.service

This creates a symlink in /etc/systemd/system/multi-user.target.wants/ pointing to your unit file.

Step 5: Start the service immediately

sudo systemctl start examplescript.service

Step 6: Verify status and logs

sudo systemctl status examplescript.service
sudo journalctl -u examplescript.service -f

Understanding systemd Service Types

The Type= directive in the [Service] section is one of the most misunderstood parameters. Choosing the wrong type causes systemd to misreport service readiness, leading to dependency failures.

TypeBehaviorUse Case
simpleProcess started by ExecStart is the main process. Systemd considers it ready immediately.Scripts and simple daemons that do not fork
forkingThe process forks and the parent exits. Systemd tracks the child via PID file.Traditional Unix daemons (e.g., Apache with PidFile)
oneshotProcess runs to completion and exits. Systemd waits before starting dependent units.One-time initialization tasks, setup scripts
notifyProcess signals readiness via sd_notify().Daemons with native systemd integration
idleExecution is delayed until all active jobs are dispatched.Low-priority background tasks

For a script that runs once at boot and exits, use Type=oneshot with RemainAfterExit=yes to keep the unit in an "active" state after the script completes.

Advanced: Dependency Ordering with After= and Wants=

A common production failure occurs when a script that requires network connectivity starts before the network stack is fully initialized. The correct dependency chain for network-dependent scripts is:

After=network-online.target
Wants=network-online.target

This is distinct from After=network.target, which only guarantees that the network interfaces have been configured — not that they are actually online and reachable. The network-online.target dependency requires the systemd-networkd-wait-online.service or equivalent to confirm connectivity.

Comparison: All Three Methods at a Glance

Feature/etc/init.d//etc/rc.localsystemd Unit
Recommended for productionNoNoYes
Parallel startup supportNoNoYes
Process supervision / auto-restartNoNoYes
Dependency managementLimited (LSB headers)NoneFull
Structured loggingNoNoYes (journald)
Security sandboxingNoNoYes
ComplexityLowVery lowMedium
Supported on Ubuntu 22.04+Via compatibility layerVia rc-local.serviceNatively
Suitable for long-running daemonsPartiallyNoYes
Suitable for one-time init tasksYesYesYes (oneshot)

Common Mistakes and How to Avoid Them

Running scripts as root unnecessarily. The User= and Group= directives in systemd unit files allow you to drop privileges. A script that only needs to write to /var/log/myapp/ does not need root — create a dedicated system user and assign directory ownership accordingly.

Not redirecting output in rc.local. Without output redirection, any echo or error output from rc.local commands goes to the system console and is lost. Always append >> /var/log/yourscript.log 2>&1.

Using absolute paths inconsistently. systemd services run in a minimal environment without the user's PATH. Always use absolute paths for every binary referenced in ExecStart, including interpreters like /usr/bin/python3 or /bin/bash.

Forgetting daemon-reload after editing unit files. systemd caches unit file contents. If you edit a .service file and do not run sudo systemctl daemon-reload, systemd will continue using the old configuration.

Placing unit files in /lib/systemd/system/ for custom scripts. The /lib/systemd/system/ directory is managed by the package manager. Custom unit files belong in /etc/systemd/system/, which takes precedence and survives package upgrades.

Practical Decision Matrix: Which Method to Use

Use this framework to select the appropriate method for your specific scenario:

  • Long-running daemon that must restart on failure — systemd unit with Restart=on-failure
  • One-time setup script that must complete before other services start — systemd unit with Type=oneshot and Before= dependencies
  • Script that requires network to be fully online — systemd unit with After=network-online.target
  • Quick one-liner for testing or prototyping/etc/rc.local (temporarily)
  • Legacy application shipped with an LSB init script/etc/init.d/ with update-rc.d
  • Anything running in production — systemd, always

For administrators managing multiple Ubuntu servers, consider pairing systemd unit management with configuration management tools such as Ansible, which can deploy and enable .service files idempotently across your entire fleet. If you need a managed environment with full root access to implement these configurations, VPS Hosting with a VPS Control Panel provides the flexibility to manage systemd services directly without restrictions.

For teams running resource-intensive workloads that require startup scripts to initialize GPU drivers, CUDA environments, or ML inference servers, GPU Hosting environments benefit particularly from systemd's After= dependency chains, ensuring drivers are fully loaded before application services attempt to bind to hardware resources.

If your autoloaded scripts interact with web server configurations or database initialization routines tied to a control panel environment, VPS with cPanel installations require extra care — cPanel manages its own service supervision layer, and custom systemd units must be defined to avoid conflicts with cPanel's service management hooks.

Technical Key-Takeaway Checklist

Before deploying any startup script on an Ubuntu server, verify the following:

  • Unit file location: Custom scripts go in /etc/systemd/system/, not /lib/systemd/system/
  • Executable bit: Confirm with ls -la /path/to/script.sh — the x bit must be set
  • Absolute paths: Every binary in ExecStart uses a full path; no reliance on $PATH
  • Dependency declarations: After=network-online.target for any network-dependent script
  • Service type: Type=simple for persistent daemons, Type=oneshot for run-and-exit scripts
  • Privilege minimization: User=, Group=, NoNewPrivileges=true, ProtectSystem=strict
  • Logging configured: StandardOutput=journal and StandardError=journal for journald integration
  • daemon-reload executed: Always run sudo systemctl daemon-reload after creating or editing unit files
  • Enable vs. start: enable creates the autostart symlink; start runs it immediately — both are required
  • Tested with journalctl: Confirm successful execution with sudo journalctl -u yourservice.service --since "5 minutes ago"

FAQ

What is the difference between systemctl enable and systemctl start?

systemctl enable creates a symbolic link that causes the service to start automatically at the next boot. systemctl start starts the service immediately in the current session. You typically need both commands when setting up a new service for the first time.

Why does my systemd service fail with "executable not found" even though the script exists?

This almost always means the ExecStart path is incorrect or the script lacks the executable bit. Verify with which yourscript and ls -la /path/to/script. Also confirm the first line of your script is a valid shebang (#!/bin/bash or #!/usr/bin/env python3), as systemd does not invoke a shell by default for ExecStart.

Can I run a script at startup only once, not on every boot?

Use Type=oneshot with a ConditionPathExists=!/var/run/myscript.done directive in the [Unit] section. The script creates the sentinel file on first run; subsequent boots skip execution because the condition fails.

Is /etc/rc.local still supported on Ubuntu 22.04?

Yes, but it is disabled by default. You must manually enable the rc-local.service unit with sudo systemctl enable rc-local and ensure the file exists and is executable. It is supported as a compatibility measure, not a recommended practice.

How do I check why a startup script failed to run?

Run sudo journalctl -u yourservice.service -b to view all log entries for that unit since the last boot. For rc.local failures, check sudo systemctl status rc-local.service and review /var/log/syslog for entries timestamped during the boot sequence.

15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started