Automating System Updates with Bash Scripts

Learn how to automate system updates with simple Bash scripts to keep Linux servers secure and up-to-date. Step-by-step guide for Linux and DevOps beginners to create, test, and schedule updates.

Automating System Updates with Bash Scripts

Short introduction
A simple, reliable update process reduces security risk and maintenance overhead. Automating system updates with a Bash script gives you control, visibility, and reproducibility while still being lightweight and transparent.

Why automate system updates?

Manual updates are fine for occasional maintenance, but they’re easy to forget and slow when you manage more than one system. Automation ensures updates run on a schedule, captures logs for auditing, and avoids repetitive manual steps.

Example: check what would update right now (Debian/Ubuntu and RHEL/Fedora):

# Debian/Ubuntu: list upgradable packages
apt list --upgradable

# RHEL/CentOS/Fedora:
yum check-update
# or for dnf:
dnf check-update

Why you might prefer a script versus unattended-upgrades:

  • Scripts give you full control over when to reboot, what to exclude, and how to log.
  • unattended-upgrades is great for hands-off security patches, but scripts can implement policy and hooks (pre/post) easily.

Writing a basic update Bash script

Here is a beginner-friendly script that:

  • Detects apt vs dnf
  • Runs updates non-interactively
  • Logs output to a timestamped log file
  • Exits with a non-zero status on failure

Save as /usr/local/bin/auto-update.sh and make executable (chmod +x).

#!/usr/bin/env bash
set -eo pipefail

LOG_DIR="/var/log/auto-update"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/update-$(date +%F_%H%M%S).log"

echo "Starting update at $(date)" | tee -a "$LOG_FILE"

# Detect package manager
if command -v apt >/dev/null 2>&1; then
  PM="apt"
elif command -v dnf >/dev/null 2>&1; then
  PM="dnf"
elif command -v yum >/dev/null 2>&1; then
  PM="yum"
else
  echo "No supported package manager found" | tee -a "$LOG_FILE" >&2
  exit 2
fi

if [[ "$PM" == "apt" ]]; then
  apt-get update 2>&1 | tee -a "$LOG_FILE"
  DEBIAN_FRONTEND=noninteractive apt-get -y upgrade 2>&1 | tee -a "$LOG_FILE"
  apt-get -y autoremove 2>&1 | tee -a "$LOG_FILE"
else
  # dnf and yum behavior
  $PM -y upgrade 2>&1 | tee -a "$LOG_FILE"
  $PM -y autoremove 2>&1 | tee -a "$LOG_FILE" || true
fi

echo "Update finished at $(date)" | tee -a "$LOG_FILE"

Commands table

Command Purpose
apt-get update Refresh package index (Debian/Ubuntu)
apt-get -y upgrade Install available upgrades non-interactively
apt-get -y autoremove Remove orphaned packages
dnf -y upgrade Upgrade packages (Fedora/RHEL newer)
yum -y update Upgrade packages (older RHEL/CentOS)
systemctl reboot Reboot the machine if needed
crontab -e Edit user/system cron jobs

Notes:

  • For Debian/Ubuntu, using DEBIAN_FRONTEND=noninteractive prevents prompts.
  • Always test scripts in a non-production environment first.
  • Consider excluding kernel updates or handling them specially: kernels often require reboots that must be planned.

Scheduling updates with cron

Cron is the simplest scheduler for periodic checks. Use root’s crontab (sudo crontab -e) to ensure permissions for package operations.

Example: run the script every Sunday at 3 AM and capture environment differences:

# Edit root crontab: run Sundays at 03:00
0 3 * * 0 /usr/local/bin/auto-update.sh >> /var/log/auto-update/cron-run.log 2>&1

Common gotchas with cron:

Cron emails output to the owner. To suppress, set MAILTO="":

MAILTO=""
0 3 * * 0 /usr/local/bin/auto-update.sh >> /var/log/auto-update/cron-run.log 2>&1

Cron jobs run with a limited PATH. Either use absolute paths in your script (as the example does with command -v) or set PATH at the top of the crontab:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * 0 /usr/local/bin/auto-update.sh >> /var/log/auto-update/cron-run.log 2>&1

If you prefer systemd timers (more robust, better logging), create a .service and .timer unit. Example systemd timer is beyond this beginner tutorial but is a recommended next step for production systems.

Safety, logging, and best practices

When automating updates you must balance availability and security. Start small and build confidence:

  • Dry-run first: many package managers support a simulation mode.
  • Log everything: store logs with timestamps and rotate them.
  • Decide reboot policy: automatic reboots can upset users; consider alerting instead.

Dry-run examples:

# Debian/Ubuntu: simulate upgrade without installing
apt-get -s upgrade

# dnf simulate:
dnf --assumeno upgrade

Simple log rotation with logrotate (example /etc/logrotate.d/auto-update):

/var/log/auto-update/*.log {
    weekly
    rotate 8
    compress
    missingok
    notifempty
    create 0640 root adm
}

Example: send a short failure email if updates fail (requires mailutils configured):

if ! /usr/local/bin/auto-update.sh >> /var/log/auto-update/cron-run.log 2>&1; then
  echo "Auto-update failed on $(hostname) at $(date)" | mail -s "Update failure" admin@example.com
fi

Tips:

  • Use set -euo pipefail at the top of scripts during development. For cron, consider catching errors and handling them gracefully so unrelated cron jobs are not impacted.
  • Keep scripts idempotent: running them multiple times should not have harmful side effects.
  • If updates are highly sensitive, prefer a staged rollout: update a test host group, verify, then update production.

Common Pitfalls

  • Reboot surprises: applying kernel updates automatically without coordination may cause downtime. Make reboot decisions explicit in scripts; consider requiring manual confirmation for reboots or schedule them during maintenance windows.

Missing PATH or environment in cron: cron’s environment is minimal, causing commands not found.

# Ensure PATH at top of crontab
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * 0 /usr/local/bin/auto-update.sh >> /var/log/auto-update/cron-run.log 2>&1

Running updates as a non-root user: package managers need root. Use sudo or run scripts as root.

# Wrong (non-root)
apt-get update
# Right (run as root or sudo)
sudo apt-get update

Next Steps

  • Test the script in a staging VM or container before enabling it in production.
  • Replace cron with systemd timers for better logging and dependency control.
  • Add health checks and remote notification (Slack/email) to report update status.

👉 Explore more IT books and guides at dargslan.com.