Set up automated security patching on Linux servers with unattended-upgrades and dnf-automatic. Configure email notifications, update policies, and monitoring to keep your systems secure while maintaining control over critical services.
Prerequisites
- Root or sudo access
- Internet connectivity
- Basic Linux command line knowledge
- Email delivery capability
What this solves
Manual security updates create vulnerability windows that attackers exploit. This tutorial configures automated security patching with unattended-upgrades (Ubuntu/Debian) and dnf-automatic (RHEL-based systems) to maintain security while protecting critical services. You'll implement email notifications, update policies, and monitoring to balance automation with operational control.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure you get the latest security patches and package definitions.
sudo apt update && sudo apt upgrade -y
Install automatic update packages
Install the appropriate automatic update service for your distribution. Ubuntu/Debian use unattended-upgrades while RHEL-based systems use dnf-automatic.
sudo apt install -y unattended-upgrades apt-listchanges
Install mail transfer agent
Install a mail server to send update notifications. Postfix provides reliable local mail delivery without requiring external SMTP configuration.
sudo apt install -y postfix mailutils
sudo systemctl enable --now postfix
Configure unattended-upgrades (Ubuntu/Debian)
Configure unattended-upgrades to automatically install security updates with email notifications and exclusion policies.
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Package-Blacklist {
"nginx";
"apache2";
"mysql-server";
"postgresql";
"docker-ce";
"kubernetes*";
};
Unattended-Upgrade::DevRelease "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";
Unattended-Upgrade::Mail "admin@example.com";
Unattended-Upgrade::MailReport "on-change";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Verbose "1";
Unattended-Upgrade::Debug "false";
Unattended-Upgrade::SyslogEnable "true";
Unattended-Upgrade::SyslogFacility "daemon";
Enable automatic updates (Ubuntu/Debian)
Configure the automatic update schedule and enable the service to run daily security updates.
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
sudo systemctl enable --now unattended-upgrades
sudo systemctl status unattended-upgrades
Configure dnf-automatic (RHEL-based systems)
Configure dnf-automatic to download and install security updates automatically with email notifications.
[commands]
upgrade_type = security
random_sleep = 360
network_online_timeout = 60
download_updates = yes
apply_updates = yes
[emitters]
emit_via = email,stdio
[email]
email_from = root@example.com
email_to = admin@example.com
email_host = localhost
email_port = 25
[base]
debuglevel = 1
Enable dnf-automatic service (RHEL-based systems)
Enable and start the dnf-automatic timer to run daily security updates at a random time within the configured window.
sudo systemctl enable --now dnf-automatic.timer
sudo systemctl status dnf-automatic.timer
Configure update exclusions
Create package exclusion policies to prevent automatic updates of critical services that require manual intervention or testing.
sudo apt-mark hold nginx apache2 mysql-server postgresql
apt-mark showhold
Configure log monitoring
Set up log rotation and monitoring for automatic update activities to track successful updates and failures.
/var/log/unattended-upgrades/unattended-upgrades.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 644 root root
}
sudo mkdir -p /var/log/unattended-upgrades
sudo touch /var/log/unattended-upgrades/unattended-upgrades.log
sudo chown root:root /var/log/unattended-upgrades/unattended-upgrades.log
sudo chmod 644 /var/log/unattended-upgrades/unattended-upgrades.log
Create update monitoring script
Create a monitoring script to check for failed updates and send alerts when automatic updates encounter problems.
#!/bin/bash
LOGFILE="/var/log/unattended-upgrades/unattended-upgrades.log"
ERROR_LOG="/tmp/update-errors.log"
ADMIN_EMAIL="admin@example.com"
if [[ -f "$LOGFILE" ]]; then
# Check for errors in the last 24 hours
grep -i "error\|failed\|abort" "$LOGFILE" | tail -20 > "$ERROR_LOG"
if [[ -s "$ERROR_LOG" ]]; then
echo "Automatic update errors detected on $(hostname):" | mail -s "Update Errors - $(hostname)" "$ADMIN_EMAIL" < "$ERROR_LOG"
fi
fi
Check for pending security updates
if command -v apt &> /dev/null; then
PENDING=$(apt list --upgradable 2>/dev/null | grep -c security)
if [[ $PENDING -gt 0 ]]; then
echo "$PENDING pending security updates on $(hostname)" | mail -s "Pending Security Updates - $(hostname)" "$ADMIN_EMAIL"
fi
elif command -v dnf &> /dev/null; then
PENDING=$(dnf updateinfo list security 2>/dev/null | wc -l)
if [[ $PENDING -gt 0 ]]; then
echo "$PENDING pending security updates on $(hostname)" | mail -s "Pending Security Updates - $(hostname)" "$ADMIN_EMAIL"
fi
fi
sudo chmod 755 /usr/local/bin/check-updates.sh
sudo chown root:root /usr/local/bin/check-updates.sh
Schedule monitoring with cron
Schedule the monitoring script to run daily and send weekly update summaries to track system maintenance activities.
sudo crontab -e
# Check for update errors daily at 8 AM
0 8 * /usr/local/bin/check-updates.sh
Send weekly update summary on Mondays at 9 AM
0 9 1 grep -i "upgraded\|installed\|removed" /var/log/unattended-upgrades/unattended-upgrades.log | tail -50 | mail -s "Weekly Update Summary - $(hostname)" admin@example.com
Verify your setup
Test your automatic update configuration and verify that email notifications work correctly.
sudo systemctl status unattended-upgrades
sudo unattended-upgrade --dry-run --debug
sudo tail -f /var/log/unattended-upgrades/unattended-upgrades.log
echo "Test automatic update notification" | mail -s "Test Email - $(hostname)" admin@example.com
/usr/local/bin/check-updates.sh
Configure rollback strategies
Enable system snapshots
Configure automatic system snapshots before updates to enable quick rollback if updates cause issues. This requires appropriate filesystem support.
sudo apt install -y snapper
sudo snapper -c root create-config /
sudo snapper list
Create update rollback script
Create a script to rollback recent updates in case of system issues after automatic updates.
#!/bin/bash
echo "WARNING: This will attempt to rollback recent updates"
echo "Press Ctrl+C to cancel, or Enter to continue..."
read
if command -v apt &> /dev/null; then
echo "Checking recent package changes..."
grep " install \| upgrade " /var/log/dpkg.log | tail -20
echo "Use 'sudo apt install package=version' to downgrade specific packages"
elif command -v dnf &> /dev/null; then
echo "Recent DNF transactions:"
dnf history list | head -10
echo "Use 'sudo dnf history undo ID' to rollback a transaction"
fi
sudo chmod 755 /usr/local/bin/rollback-updates.sh
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| No email notifications received | Postfix not configured or email settings incorrect | Test with echo "test" | mail -s "test" admin@example.com and check /var/log/mail.log |
| Updates not running automatically | Service not enabled or configuration errors | Check service status and run dry-run to identify configuration issues |
| Critical service broken after update | Package conflict or configuration change | Use package exclusions and test updates in staging environment first |
| Disk space filling up | Old kernels and packages not cleaned up | Enable automatic cleanup in configuration and run sudo apt autoremove |
| System requiring reboot frequently | Kernel updates installed automatically | Consider enabling automatic reboots during maintenance windows |
Security considerations
Automatic updates balance security with stability. Monitor update logs regularly and maintain staging environments for testing critical service updates. Consider implementing comprehensive monitoring with Prometheus and Grafana to track system health after updates.
For containerized environments, integrate automatic host updates with Kubernetes security hardening practices to maintain both container and host security.
Next steps
- Install and configure Fail2ban with advanced rules and email alerts
- Optimize Linux system performance with kernel parameters and system tuning
- Configure centralized logging with rsyslog and logrotate for update tracking
- Implement Linux security hardening with CIS benchmarks
- Setup automated backup verification and recovery testing
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default email address
ADMIN_EMAIL="${1:-admin@$(hostname -f)}"
# Usage message
usage() {
echo "Usage: $0 [admin_email]"
echo "Example: $0 admin@example.com"
echo "If no email provided, uses admin@$(hostname -f)"
exit 1
}
# Logging functions
log_info() { echo -e "${GREEN}$1${NC}"; }
log_warn() { echo -e "${YELLOW}$1${NC}"; }
log_error() { echo -e "${RED}$1${NC}"; }
# Cleanup function for rollback
cleanup() {
log_error "[ERROR] Installation failed. Cleaning up..."
case "$PKG_MGR" in
apt)
systemctl disable unattended-upgrades 2>/dev/null || true
systemctl stop unattended-upgrades 2>/dev/null || true
;;
dnf|yum)
systemctl disable dnf-automatic.timer 2>/dev/null || true
systemctl stop dnf-automatic.timer 2>/dev/null || true
;;
esac
systemctl stop postfix 2>/dev/null || true
systemctl disable postfix 2>/dev/null || true
}
trap cleanup ERR
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
# Validate email format (basic)
if [[ ! "$ADMIN_EMAIL" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
log_error "Invalid email format: $ADMIN_EMAIL"
usage
fi
log_info "[1/8] Detecting distribution..."
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution - /etc/os-release not found"
exit 1
fi
log_info "Detected: $PRETTY_NAME (Package manager: $PKG_MGR)"
log_info "[2/8] Updating system packages..."
$PKG_UPDATE
log_info "[3/8] Installing automatic update packages..."
case "$PKG_MGR" in
apt)
$PKG_INSTALL unattended-upgrades apt-listchanges
;;
dnf|yum)
$PKG_INSTALL dnf-automatic
;;
esac
log_info "[4/8] Installing mail transfer agent..."
case "$PKG_MGR" in
apt)
# Preconfigure postfix to avoid interactive prompts
echo "postfix postfix/main_mailer_type select Local only" | debconf-set-selections
echo "postfix postfix/mailname string $(hostname -f)" | debconf-set-selections
$PKG_INSTALL postfix mailutils
;;
dnf|yum)
$PKG_INSTALL postfix mailx
;;
esac
systemctl enable --now postfix
log_info "[5/8] Configuring automatic updates..."
case "$PKG_MGR" in
apt)
# Configure unattended-upgrades
cat > /etc/apt/apt.conf.d/50unattended-upgrades << EOF
Unattended-Upgrade::Allowed-Origins {
"\${distro_id}:\${distro_codename}";
"\${distro_id}:\${distro_codename}-security";
"\${distro_id}ESMApps:\${distro_codename}-apps-security";
"\${distro_id}ESM:\${distro_codename}-infra-security";
};
Unattended-Upgrade::Package-Blacklist {
"nginx";
"apache2";
"mysql-server";
"postgresql";
"docker-ce";
"kubernetes*";
};
Unattended-Upgrade::DevRelease "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";
Unattended-Upgrade::Mail "$ADMIN_EMAIL";
Unattended-Upgrade::MailReport "on-change";
Unattended-Upgrade::Verbose "1";
Unattended-Upgrade::Debug "false";
Unattended-Upgrade::SyslogEnable "true";
Unattended-Upgrade::SyslogFacility "daemon";
EOF
chmod 644 /etc/apt/apt.conf.d/50unattended-upgrades
;;
dnf|yum)
# Configure dnf-automatic
cat > /etc/dnf/automatic.conf << EOF
[commands]
upgrade_type = security
random_sleep = 360
network_online_timeout = 60
download_updates = yes
apply_updates = yes
[emitters]
emit_via = email,stdio
[email]
email_from = root@$(hostname -f)
email_to = $ADMIN_EMAIL
email_host = localhost
email_port = 25
[base]
debuglevel = 1
EOF
chmod 644 /etc/dnf/automatic.conf
;;
esac
log_info "[6/8] Enabling automatic update schedule..."
case "$PKG_MGR" in
apt)
cat > /etc/apt/apt.conf.d/20auto-upgrades << EOF
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
EOF
chmod 644 /etc/apt/apt.conf.d/20auto-upgrades
systemctl enable --now unattended-upgrades
;;
dnf|yum)
systemctl enable --now dnf-automatic.timer
;;
esac
log_info "[7/8] Configuring package exclusions..."
case "$PKG_MGR" in
apt)
cat > /etc/apt/preferences.d/01-security-updates << EOF
Package: nginx apache2 mysql-server postgresql docker-ce
Pin: release a=$VERSION_CODENAME-security
Pin-Priority: -1
Package: kubernetes*
Pin: release a=$VERSION_CODENAME-security
Pin-Priority: -1
EOF
chmod 644 /etc/apt/preferences.d/01-security-updates
;;
dnf|yum)
if [ ! -f /etc/dnf/dnf.conf.bak ]; then
cp /etc/dnf/dnf.conf /etc/dnf/dnf.conf.bak
fi
grep -q "^exclude=" /etc/dnf/dnf.conf || echo "exclude=nginx httpd mysql-server postgresql docker-ce kubernetes*" >> /etc/dnf/dnf.conf
;;
esac
log_info "[8/8] Verifying configuration..."
# Verification checks
ERRORS=0
# Check services
case "$PKG_MGR" in
apt)
if ! systemctl is-active --quiet unattended-upgrades; then
log_error "unattended-upgrades service is not running"
((ERRORS++))
fi
;;
dnf|yum)
if ! systemctl is-active --quiet dnf-automatic.timer; then
log_error "dnf-automatic.timer is not running"
((ERRORS++))
fi
;;
esac
if ! systemctl is-active --quiet postfix; then
log_error "postfix service is not running"
((ERRORS++))
fi
# Check config files
case "$PKG_MGR" in
apt)
if [ ! -f /etc/apt/apt.conf.d/50unattended-upgrades ]; then
log_error "unattended-upgrades configuration missing"
((ERRORS++))
fi
if [ ! -f /etc/apt/apt.conf.d/20auto-upgrades ]; then
log_error "auto-upgrades configuration missing"
((ERRORS++))
fi
;;
dnf|yum)
if [ ! -f /etc/dnf/automatic.conf ]; then
log_error "dnf-automatic configuration missing"
((ERRORS++))
fi
;;
esac
if [ $ERRORS -eq 0 ]; then
log_info "✓ Automatic security updates configured successfully!"
log_info "✓ Email notifications will be sent to: $ADMIN_EMAIL"
log_info "✓ Security updates will be installed automatically"
log_info "✓ Critical services are excluded from automatic updates"
case "$PKG_MGR" in
apt)
log_info "✓ Service: unattended-upgrades ($(systemctl is-active unattended-upgrades))"
;;
dnf|yum)
log_info "✓ Service: dnf-automatic.timer ($(systemctl is-active dnf-automatic.timer))"
;;
esac
log_warn "Note: First update check will occur within 24 hours"
log_warn "Monitor logs: journalctl -u unattended-upgrades -f (Ubuntu/Debian) or journalctl -u dnf-automatic -f (RHEL-based)"
else
log_error "Configuration completed with $ERRORS error(s). Please review the output above."
exit 1
fi
Review the script before running. Execute with: bash install.sh