Skip to content

Repository files navigation

Automatic Security Updates Setup Script

License: MIT Version Ubuntu Debian

A production-ready shell script that automates the setup of unattended security updates on Ubuntu and Debian servers. Keeps your systems patched against known vulnerabilities with zero manual intervention.


Quick Install

Run as root:

sudo bash -c "$(curl -fsSL https://github.com/ruhanirabin/unattended-setup-script-debian/raw/main/setup_auto_updates.sh)"

For non-interactive (automation/Ansible/Packer):

sudo bash setup_auto_updates.sh --yes

Requirements

  • Root access (sudo or root user)
  • Internet connectivity for package installation
  • Minimum 50 MB free disk space

Container note: The script detects the absence of systemd and skips service enablement, but still writes configuration files. You can use cron or a container-native scheduler instead.


Supported Distributions

Distribution Versions Codenames
Ubuntu 24.04 LTS noble
Ubuntu 24.10 oracular
Ubuntu 25.04 plucky
Ubuntu 25.10 questing
Ubuntu 26.04 LTS resolute
Ubuntu 26.10
Debian 13 trixie
Debian 14 forky
Debian Sid (unstable) sid

Note: Unsupported versions will be detected and rejected with a clear error message listing supported releases.


What This Script Does

  1. Root verification — ensures the script runs with elevated privileges
  2. Distribution detection — identifies OS type, version, and codename via /etc/os-release
  3. Existing config check — detects running unattended-upgrades and warns about reconfiguration
  4. Package installation — installs unattended-upgrades via apt
  5. Config backup — backs up existing APT configuration files before modification
  6. Unattended-upgrades configuration — writes distribution-specific config (50unattended-upgrades)
  7. Schedule configuration — sets daily update/check cycles (20auto-upgrades)
  8. Dry-run validation — tests the configuration without applying changes
  9. Service enable/start — enables and starts the unattended-upgrades systemd service
  10. Status verification — reports service state and provides verification commands

Usage

Interactive Mode (default)

sudo bash setup_auto_updates.sh

Prompts for confirmation before making changes.

Non-Interactive Mode

sudo bash setup_auto_updates.sh --yes

Skips all prompts. Ideal for automation, Ansible, Packer, or cloud-init.

Dry-Run Mode

sudo bash setup_auto_updates.sh --dry-run

Validates the environment and shows what would be done without making any changes.

Verbose Mode

sudo bash setup_auto_updates.sh --yes --verbose

Enables detailed debug output on stdout and in the log file.

Quiet Mode (CI/CD / Ansible)

sudo bash setup_auto_updates.sh --yes --quiet

Minimal output: only errors and the final STATUS: CHANGED or STATUS: UNCHANGED marker.

Custom Log File

sudo bash setup_auto_updates.sh --log-file /var/log/my-setup.log

All Options

Flag Description
-y, --yes Skip all confirmation prompts
-n, --dry-run Validate only, no changes
-q, --quiet Minimal output (errors + status only)
-v, --verbose Enable debug output
-l, --log-file <path> Set custom log file path
-h, --help Show help and exit

Automation & Idempotency

The script is designed to be idempotent — running it multiple times is safe and will not duplicate work:

  • Package installation skips if already present
  • Config files are compared before writing; unchanged files are left alone
  • Service enablement only reports a change if it was not previously enabled
  • Final output includes a status marker for automation tools:
    • STATUS: CHANGED — something was modified
    • STATUS: UNCHANGED — everything was already up to date

Use --quiet with --yes for Ansible, Packer, cloud-init, or CI/CD pipelines.


Ansible Integration

A ready-to-use Ansible role, sample inventory, and playbook are included in the ansible/ directory. The layout follows standard Ansible conventions so it can be run manually, via cron, or imported into tools like Ansible Semaphore / AWX.

Directory Layout

ansible/
├── ansible.cfg                 # Ansible settings (become, inventory path, forks)
├── inventory/
│   └── hosts.yml               # Sample YAML inventory
├── playbooks/
│   └── site.yml                # Main entry-point playbook
└── roles/
    └── unattended_updates/
        ├── defaults/main.yml   # Default variables (safe to override)
        ├── handlers/main.yml   # Service restart handler
        ├── tasks/main.yml      # Role tasks
        └── templates/
            ├── 20auto-upgrades.j2
            └── 50unattended-upgrades.j2

Prerequisites

  • Ansible 2.15+ on the control node.
  • SSH key-based access (or password with ansible_ssh_pass) to target hosts.
  • Sudo / root privileges on targets (become: true is set in ansible.cfg).
  • Targets must run Ubuntu 24.04+ or Debian 13+.

Inventory Setup

The included inventory/hosts.yml is a minimal example. Adapt it to your environment:

all:
  children:
    homelab:
      vars:
        ansible_user: root
      hosts:
        node-02-web:
          ansible_host: 192.168.68.10
        node-02-db:
          ansible_host: 192.168.68.11
    vps:
      vars:
        ansible_user: root
      hosts:
        vps-hermes:
          ansible_host: 203.0.113.5

You can also use a static INI file if you prefer:

[homelab]
node-02-web ansible_host=192.168.68.10 ansible_user=root
node-02-db  ansible_host=192.168.68.11 ansible_user=root

[vps]
vps-hermes ansible_host=203.0.113.5 ansible_user=root

Group / Host Variables

Place overrides under inventory/group_vars/ or inventory/host_vars/ so they are automatically loaded:

# inventory/group_vars/homelab.yml
---
unattended_updates_auto_reboot: true
unattended_updates_auto_reboot_time: "00:30"
# inventory/host_vars/vps-hermes.yml
---
unattended_updates_auto_reboot: false

Running the Playbook

Basic Deploy (All Hosts)

cd ansible
ansible-playbook playbooks/site.yml

Check Mode (Dry Run)

ansible-playbook playbooks/site.yml --check --diff

Limit to a Group or Single Host

ansible-playbook playbooks/site.yml --limit homelab
ansible-playbook playbooks/site.yml --limit node-02-web

Verbose Output

ansible-playbook playbooks/site.yml -v
ansible-playbook playbooks/site.yml -vvv   # Debug-level

Custom Inventory File

ansible-playbook playbooks/site.yml -i /path/to/your/inventory.yml

Role Variables

All variables are defined in ansible/roles/unattended_updates/defaults/main.yml. Override them at the playbook, inventory, group_vars, or command-line level.

Variable Default Description
unattended_updates_auto_reboot false Reboot automatically if a kernel or critical update requires it
unattended_updates_auto_reboot_time "02:00" Reboot schedule in 24-hour format
unattended_updates_auto_reboot_with_users false Reboot even when users are logged in
unattended_updates_dev_release "false" Allow development release upgrades (keep false)
unattended_updates_periodic_update_package_lists "1" Days between apt update runs (1 = daily)
unattended_updates_periodic_unattended_upgrade "1" Days between unattended upgrade runs
unattended_updates_periodic_autoclean_interval "7" Days between apt autoremove / autoclean runs
unattended_updates_log_file "/var/log/ansible-unattended-setup.log" Path for the role’s internal log

Override Examples

Inside the playbook:

---
- name: Configure automatic security updates
  hosts: all
  become: true
  roles:
    - role: unattended_updates
      vars:
        unattended_updates_auto_reboot: true
        unattended_updates_auto_reboot_time: "00:30"

On the command line:

ansible-playbook playbooks/site.yml \
  -e "unattended_updates_auto_reboot=true" \
  -e "unattended_updates_auto_reboot_time=04:00"

Idempotency

The role is fully idempotent:

  • APT cache is only updated if older than 1 hour.
  • Configuration files are backed up before changes.
  • The systemd service is restarted only when configs change (handler).
  • The dry-run step reports changed_when: false and is allowed to fail gracefully on systems with no pending updates.

Ansible Semaphore / AWX

This repository is compatible with Ansible Semaphore and Red Hat Ansible Automation Platform:

  1. Add this repository as a Project / Task Template source.
  2. Set the playbook path to ansible/playbooks/site.yml.
  3. Point the inventory to your own inventory file or use the sample at ansible/inventory/hosts.yml.
  4. The role automatically detects Ubuntu vs Debian and applies the correct origin patterns.

Cron Automation (Control Node)

Run the playbook automatically from your control node at midnight:

0 0 * * * cd /opt/unattended-setup-script-debian/ansible && \
  git pull origin main >/dev/null 2>&1 && \
  ansible-playbook playbooks/site.yml >> /var/log/ansible-nightly.log 2>&1

Using the Shell Script Directly in Ansible

If you prefer invoking the shell script instead of the role:

- name: Set up unattended security updates
  ansible.builtin.script: ../setup_auto_updates.sh
  args:
    creates: /etc/apt/apt.conf.d/50unattended-upgrades
  register: unattended_setup
  changed_when: "'STATUS: CHANGED' in unattended_setup.stdout"

For non-interactive execution, pass --yes:

- name: Set up unattended security updates (non-interactive)
  ansible.builtin.command: "bash {{ playbook_dir }}/../setup_auto_updates.sh --yes --quiet"
  args:
    creates: /etc/apt/apt.conf.d/50unattended-upgrades
  register: unattended_setup
  changed_when: "'STATUS: CHANGED' in unattended_setup.stdout"

Configuration Details

/etc/apt/apt.conf.d/50unattended-upgrades

The main unattended-upgrades configuration. This script generates a distro-specific file:

  • Ubuntu: Uses Allowed-Origins with ${distro_id}:${distro_codename}-security patterns
  • Debian: Uses Origins-Pattern with origin=Debian,codename=<codename> patterns

Included settings:

  • Security updates only (updates, proposed, backports commented out)
  • AutoFixInterruptedDpkg "true" — recovers from interrupted package operations
  • MinimalSteps "true" — safer upgrades resilient to interruptions
  • Remove-Unused-Kernel-Packages "true" — cleans old kernels
  • Remove-Unused-Dependencies "true" — removes orphaned packages
  • Automatic-Reboot "false" — disabled by default for safety

Enabling automatic reboot:

Edit /etc/apt/apt.conf.d/50unattended-upgrades and change:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";

/etc/apt/apt.conf.d/20auto-upgrades

Controls the periodic execution schedule:

APT::Periodic::Update-Package-Lists "1";       # Daily
APT::Periodic::Unattended-Upgrade "1";          # Daily
APT::Periodic::AutocleanInterval "7";           # Weekly

Post-Installation Verification

After running the script, verify the setup:

# Check service status
systemctl status unattended-upgrades

# View service journal
journalctl -u unattended-upgrades --no-pager -n 50

# Run a dry-run test
sudo unattended-upgrades --dry-run -v

# Check the unattended-upgrades log
sudo cat /var/log/unattended-upgrades/unattended-upgrades.log

# View the script's own log
sudo cat /var/log/unattended-setup.log

Logging

The script writes structured logs to:

  • Default path: /var/log/unattended-setup.log
  • Custom path: Use --log-file /path/to/log

Log format:

[YYYY-MM-DD HH:MM:SS] [LEVEL] message

Levels: INFO, WARN, ERROR, DEBUG

Unattended-upgrades logs (managed by the package itself):

  • /var/log/unattended-upgrades/unattended-upgrades.log
  • /var/log/unattended-upgrades/unattended-upgrades-dpkg.log

Troubleshooting

Service not starting

# Check journal for errors
journalctl -u unattended-upgrades -e --no-pager

# Check APT configuration syntax
sudo apt-config dump | grep -i unattended

Dry-run test fails

This is normal on freshly installed systems with no pending updates. Verify with:

sudo unattended-upgrades --dry-run -v --apt-debug

"Unsupported distribution" error

The script validates the OS version against a supported list. Update the script or file an issue if your version should be supported.

Configuration not applying

Ensure no conflicting files exist in /etc/apt/apt.conf.d/:

ls -la /etc/apt/apt.conf.d/ | grep -E '(10periodic|20auto|50unattended)'

The script backs up existing files with a .bak.<timestamp> suffix.

"Another instance is already running" error

The script uses a lock file (/var/run/setup_auto_updates.sh.lock) to prevent concurrent runs. If a previous run crashed without cleaning up:

sudo rm -f /var/run/setup_auto_updates.sh.lock

Running in Docker / containers

The script detects the absence of systemd and skips service management. Configuration files are still written. To run unattended-upgrades in a container, use cron instead:

apt-get install -y cron
(crontab -l 2>/dev/null; echo "0 6 * * * /usr/bin/unattended-upgrade --dry-run") | crontab -

Reverting changes

See the Uninstallation section below.


Security Considerations

  • Automatic updates can introduce regressions. Test on non-production systems first
  • The script installs security updates only by default — it does not apply feature updates or backports
  • Kernel updates that require a reboot will not automatically reboot the system (configurable)
  • Always review scripts from the internet before piping them to bash
  • Configuration files are backed up before modification
  • The AutoFixInterruptedDpkg option helps recover from interrupted upgrades but may mask underlying package issues

Uninstallation

To reverse all changes made by this script:

# 1. Stop and disable the service
sudo systemctl stop unattended-upgrades
sudo systemctl disable unattended-upgrades

# 2. Remove configuration files
sudo rm -f /etc/apt/apt.conf.d/20auto-upgrades
sudo rm -f /etc/apt/apt.conf.d/50unattended-upgrades

# 3. Restore backups (if available)
sudo mv /etc/apt/apt.conf.d/*.bak.* /etc/apt/apt.conf.d/ 2>/dev/null || true

# 4. Remove the package (optional)
sudo apt-get remove --purge -y unattended-upgrades
sudo apt-get autoremove -y

# 5. Remove the setup log
sudo rm -f /var/log/unattended-setup.log

Changelog

See CHANGELOG.md for the full release history.


License

This project is licensed under the MIT License. See LICENSE for details.


Author

Ruhani Rabin


Contributing

Issues and pull requests are welcome. Please test any changes on both Ubuntu and Debian before submitting.

About

A production-ready shell script that automates the setup of unattended security updates on Ubuntu and Debian servers. Keeps your systems patched against known vulnerabilities with zero manual intervention.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages