Enable Automatic Security Updates on a Django Server

Keep a Django server patched automatically: unattended-upgrades on Ubuntu and Debian or dnf-automatic on RHEL, security-only origins, service restarts, reboot windows, exclusions for PostgreSQL, and monitoring.

Problem statement

The operating system under your Django app (OpenSSL, the kernel, OpenSSH, Nginx, glibc, the system Python) receives security fixes every week. On a server someone logs into once a month, those fixes wait, sometimes for years. Many compromises of small production servers exploit vulnerabilities for which patches had long been available.

Automatic security updates close that gap with little effort. The concerns are real but manageable: an update restarting PostgreSQL mid-day, a library update that needs services restarted to take effect, and kernel updates that only apply after a reboot. This guide enables automatic security updates on Ubuntu, Debian, and RHEL-family servers and handles those concerns.

Automatic patching cycleDaily timerrefresh package listsSecurity updatesinstall only theseRestart servicesload new librariesReboot windowkernel updatesSecurity patches apply daily; reboots happen in a planned window, not at random.
  1. Ubuntu and Debian: install unattended-upgrades and enable the daily periodic job; keep the default security-only origins.
  2. RHEL, Rocky, and AlmaLinux: install dnf-automatic, set upgrade_type = security and apply_updates = yes, enable dnf-automatic.timer.
  3. Make sure services using updated libraries are restarted (needrestart on Ubuntu and Debian, dnf needs-restarting on RHEL).
  4. Decide on reboots: automatic at a fixed low-traffic time, or alert when /var/run/reboot-required exists and reboot manually.
  5. Exclude packages you upgrade deliberately (often PostgreSQL) if restarts must be scheduled.
  6. Monitor that updates run and the server is not waiting for a reboot.

Step-by-step solution

1. Ubuntu and Debian: unattended-upgrades

sudo apt install unattended-upgrades apt-listchanges
sudo dpkg-reconfigure --priority=low unattended-upgrades

This writes /etc/apt/apt.conf.d/20auto-upgrades:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

The main configuration is /etc/apt/apt.conf.d/50unattended-upgrades. The defaults install from the security pocket only. Review and adjust a few options, ideally in a separate file so package upgrades do not conflict with your edits:

// /etc/apt/apt.conf.d/52unattended-upgrades-local
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:30";
Unattended-Upgrade::Mail "ops@example.com";
Unattended-Upgrade::MailReport "only-on-error";
Unattended-Upgrade::Package-Blacklist {
    "postgresql-";
};
52unattended-upgrades-localUnattended-Upgrade::Automatic-Reboot"true";Unattended-Upgrade::Automatic-Reboot-Time"04:30";Unattended-Upgrade::MailReport "only-on-error";Unattended-Upgrade::Package-Blacklist {"postgresql-"; };1231Kernel fixes only apply after a reboot.2Pick your quietest time.3Upgrade the database deliberately, not overnight.Defaults install from the security pocket onlydjango-deployment.com
A dry run with --debug shows what would be installed.

Mail requires a working local mail setup; without one, use monitoring instead (step 5). Test the configuration:

sudo unattended-upgrade --dry-run --debug 2>&1 | tail -20

Runs are logged in /var/log/unattended-upgrades/.

2. RHEL-family: dnf-automatic

sudo dnf install dnf-automatic
# /etc/dnf/automatic.conf
[commands]
upgrade_type = security
apply_updates = yes
download_updates = yes

[emitters]
emit_via = stdio
sudo systemctl enable --now dnf-automatic.timer
systemctl list-timers | grep dnf-automatic

Newer versions of dnf-automatic (with dnf5) also support a reboot setting for automatic reboots when needed; check man dnf-automatic for your release. Excluded packages go in /etc/dnf/dnf.conf with excludepkgs=.

3. Restart services after library updates

Updating OpenSSL or glibc on disk does not change running processes; Gunicorn, Nginx, and PostgreSQL keep using the old, vulnerable copy in memory until restarted.

patched on disk, not in memorylibssl updatednew file on diskGunicorn still runningold library in memoryneedrestart detects itlists affected servicesServices restartedautomatically or by youFix actually in effectverifiedUntil the restart, the vulnerable code is still runningdjango-deployment.com
needrestart -r a restarts affected services without asking.

On Ubuntu and Debian, needrestart detects such processes after upgrades. In non-interactive runs it can restart affected services automatically:

# /etc/needrestart/conf.d/auto.conf
$nrconf{restart} = 'a';

Check manually:

sudo needrestart -b

On RHEL-family systems:

sudo dnf needs-restarting -s    # services to restart
sudo dnf needs-restarting -r    # is a reboot needed?

Restarting Gunicorn causes a brief interruption unless you run several instances or use socket activation; schedule automatic restarts for low-traffic hours or accept the brief blip in exchange for being patched.

4. Handle reboots

Kernel and some core library updates require a reboot. Options:

  • Automatic reboot at a fixed time (as configured above). Suitable when a short nightly interruption is acceptable, or when you run several servers behind a load balancer and reboot times are staggered.
  • Manual reboot with alerting: check for the flag file and alert:
test -f /var/run/reboot-required && cat /var/run/reboot-required.pkgs

Either way, confirm that everything comes back on its own after a reboot: systemctl is-enabled gunicorn celery-worker nginx postgresql should all report enabled.

5. Exclude what you upgrade deliberately

PostgreSQL minor updates restart the database. That is safe and recommended, but some teams prefer to apply them in a planned window. Excluding a package means you are now responsible for updating it; put a recurring task in your calendar and do not let excluded packages drift for months. The same applies to Docker Engine on hosts running containers.

6. Remember what this does not cover

  • Python packages in your virtual environment (Django, requests, Pillow) are not managed by the OS package manager. Update them through your dependency process.
  • Container images do not change on the host; rebuild them regularly on updated base images.
  • Distribution upgrades (for example Ubuntu 22.04 to 24.04) are not automatic and need planning, since they change the system Python and other major versions.
what unattended upgrades coverCovered?OS packages✓yesKernel!yes, after a rebootPython packages✕no: your lock fileDjango itself✕no: your lock fileDocker base images✕no: rebuild imagesApplication dependencies need their own update processdjango-deployment.com
Pair this with dependency scanning for the Python side.

Verification check:

systemctl list-timers | grep -E "apt-daily|dnf-automatic"
sudo tail -n 20 /var/log/unattended-upgrades/unattended-upgrades.log
apt list --upgradable 2>/dev/null | grep -i security | head

A day after enabling, the log should show a run, and pending security updates should be close to zero.

Explanation

Distribution security teams backport fixes into stable package versions, so security-only updates change very little besides the vulnerable code, which is why they are safe to automate. The package manager replaces files on disk; needrestart or needs-restarting makes running services load the fixed versions; a reboot applies kernel fixes. Automating all three turns patching from a task someone must remember into a background process with a known schedule and visible results.

Edge cases / notes

Several servers

Stagger update and reboot times so not every server restarts at once, or let your load balancer drain each server before its reboot.

Immutable infrastructure

If servers are rebuilt from images rather than updated in place, the equivalent is rebuilding the base image regularly and redeploying. Unattended updates still help between rebuilds.

When to automate this

Enable unattended updates in your provisioning (cloud-init or Ansible), ship the local configuration file with it, and add monitoring for "reboot required for more than N days" and "last successful update run older than two days".

Next steps

FAQ

Is it safe to enable automatic updates on a production Django server?

Security-only updates are low risk and much safer than leaving known vulnerabilities unpatched. Plan reboots and service restarts, and exclude packages you prefer to update manually.

Do automatic updates update Django itself?

No. Django and other Python packages in your virtual environment are managed separately, through your requirements and deploy process.

Why is my server still vulnerable after updates installed?

Running processes use the old libraries until restarted, and kernel fixes need a reboot. Use needrestart or dnf needs-restarting and reboot when required.

Should PostgreSQL be updated automatically?

Minor updates are safe but restart the database. Either allow them in a low-traffic window or exclude PostgreSQL and update it on a regular schedule.

Learning path · step 13 of 15

Hardening a Django deployment

Keep going