Ubuntu/Debian: E: Could not get lock /var/lib/dpkg/lock-frontend — who holds it and how to wait for it
Hi, it's BlueByte. You run sudo apt-get install on a fresh Ubuntu VM and it stops on the first line with E: Could not get lock /var/lib/dpkg/lock-frontend. It is held by process 1234 (unattended-upgr) followed by E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it?. Nothing is broken: another package manager is working on the same system and apt refuses to run two at once. We'll walk through who holds the lock, why apt and apt-get behave differently in front of it, the fix for a live holder versus an interrupted one, and why deleting the lock file is the one thing not to do.
Why apt-get fails instantly while apt sits and waits
Both commands take the same locks; the difference is patience. The Debian apt changelog records apt(8): Wait for lock in 1.9.11 and a reworked message with absolute times in 2.0.0. On Ubuntu 24.04 with apt 2.8.3, apt-config dump shows where that patience comes from:
apt-config dump | grep Lockbinary::apt::DPkg::Lock::Timeout "120";
The binary::apt:: scope applies to the apt binary only, so apt install prints Waiting for cache lock: Could not get lock /var/lib/dpkg/lock-frontend. It is held by process 1234 (unattended-upgr)... and retries for two minutes, while apt-get install with no such setting gives up at once. That is why scripts using apt-get hit this error far more often than people at a terminal typing apt.
The three lock files and the message each one produces
If you check it yourself by holding each lock with a POSIX lock and running apt against it, three messages come out. /var/lib/dpkg/lock-frontend produces the pair above during install, upgrade or remove. /var/lib/apt/lists/lock produces E: Could not get lock /var/lib/apt/lists/lock. It is held by process 1234 (python3) and E: Unable to lock directory /var/lib/apt/lists/ during apt-get update. /var/cache/apt/archives/lock guards the download cache. All three are fcntl locks on files that are always present; the file existing means nothing, and the kernel releases the lock the moment the holder exits. In our reproduction the error disappeared the instant the holding process ended, with no cleanup of any kind.
Who is holding it: read the PID apt prints, then check the timers
apt already tells you the PID and the process name, so start there rather than with lsof or fuser, which are not installed on a minimal Ubuntu image:
ps -p 1234 -o pid,ppid,etime,cmd
systemctl list-timers 'apt-daily*' --no-pager
systemctl status apt-daily-upgrade.service unattended-upgrades.service --no-pager PID PPID ELAPSED CMD
1234 1 02:41 /usr/bin/python3 /usr/bin/unattended-upgrade
NEXT LEFT LAST PASSED UNIT ACTIVATES
Mon 2026-09-21 06:45:41 KST 6h Sun 2026-09-20 06:53:41 KST 17h ago apt-daily-upgrade.timer apt-daily-upgrade.service
A holder named unattended-upgr is Ubuntu's automatic security updates. The Ubuntu Server docs explain the two systemd timers behind it: apt-daily.timer refreshes package lists and apt-daily-upgrade.timer installs upgrades, both by running /usr/lib/apt/apt.systemd.daily. On the host we checked, the upgrade timer fires at 06:00 with a randomised delay of up to an hour, and Persistent=true means, in the docs' words, that if the machine is off when the timer elapses it may be triggered immediately at the next startup. That last clause is why a freshly booted VM is so often already mid-upgrade when you log in. Its progress is in /var/log/unattended-upgrades/unattended-upgrades.log and the dpkg detail in unattended-upgrades-dpkg.log next to it.
Fix 1: let the running upgrade finish, or tell apt-get to wait
A live, progressing holder needs nothing from you except time. Watch its log and run your command when it exits:
sudo tail -f /var/log/unattended-upgrades/unattended-upgrades.logFor a script, give apt-get the same patience apt has by passing the timeout in seconds; the waiting line then appears until the lock frees or the timeout expires:
sudo apt-get -o DPkg::Lock::Timeout=600 install -y nginxIf you must stop the automatic run early, use systemd rather than kill -9. On Ubuntu the unattended-upgrades service runs unattended-upgrade-shutdown --wait-for-signal, a monitor whose stop path sends SIGTERM to the running unattended-upgrade, and that process logs SIGTERM received, will stop and finishes the package it is on before exiting; the unit allows it up to TimeoutStopSec=1800 to do so. systemctl stop unattended-upgrades therefore blocks for a moment and leaves dpkg consistent, which is what you want.
Fix 2: a dead holder or an interrupted dpkg — finish what it started
If ps -p shows the PID is gone but a different dpkg or apt is now the holder, wait for that one too. If the previous run was killed mid-install, the lock is free but apt tells you: E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem. The dpkg manual defines --configure -a (or --pending) as configuring every package that has been unpacked but not yet configured, which is exactly the state an interrupted run leaves behind. You don't need to memorise which packages: dpkg finds them.
sudo dpkg --configure -a
sudo apt-get install -fThe second command repairs any dependencies the interruption broke. A machine that lost power during an upgrade usually needs both.
Why deleting the lock file is the wrong shortcut
Forum advice to rm /var/lib/dpkg/lock-frontend survives because it appears to work: the next apt run creates the file again and starts. But the lock is on the open file descriptor, not the path, so removing the path does nothing to the process that holds it. You get two package managers writing /var/lib/dpkg/status at once, and the interrupted-dpkg state above is the mild outcome. The dpkg manual describes a DPKG_FRONTEND_LOCKED environment variable set by a frontend since dpkg 1.19.1 so that dpkg itself does not take the frontend lock; that coordination is what a deleted file bypasses.
A real case: cloud-init on a fresh VM racing unattended-upgrades
A team's provisioning script ran apt-get install -y docker.io about forty seconds after first boot on Ubuntu 24.04, and one VM in five failed with the frontend lock error. ps -p on a failed VM showed unattended-upgrade with an elapsed time under a minute: the persistent timer had fired at boot. Adding -o DPkg::Lock::Timeout=300 to the script made every VM wait through the security run instead of failing, and the log confirmed the run took two to three minutes on that image.
Check it worked and stop it recurring in images and CI
After the fix, a plain sudo apt-get check should return without a lock line; we confirmed it takes the frontend lock, so it is a true probe. pgrep -af unattended-upgrade should then list only the unattended-upgrade-shutdown --wait-for-signal monitor, which is always present, and no /usr/bin/unattended-upgrade worker. Do not be fooled by ps, which truncates both names to unattended-upgr. To stop the race in golden images and CI runners, set APT::Periodic::Unattended-Upgrade "0" in /etc/apt/apt.conf.d/20auto-upgrades, as the Ubuntu docs describe, or keep the updates and put Persistent=false into systemctl edit apt-daily-upgrade.timer so a missed run waits for the next scheduled time instead of the next boot. Keep DPkg::Lock::Timeout in every scripted apt-get regardless; it costs nothing when the lock is free.
Adjacent errors: the lists lock during update and the interrupted-dpkg message
Could not get lock /var/lib/apt/lists/lock comes from apt-get update and means a list refresh, often apt-daily.service, is running; the same waiting rule applies. dpkg was interrupted is not a lock at all but the recovery step after one was broken. And Permission denied on the lock path means you forgot sudo, not that anything holds it.
Next time an install stops on a lock, walk these checks back in order: read the PID apt printed, see what it is doing, wait or give apt-get a timeout, and reach for dpkg --configure -a only when a run was actually interrupted.
Related questions
Why did apt-get fail instantly when apt would have waited?
Ubuntu configures the wait for the apt binary only: apt-config dump shows binary::apt::DPkg::Lock::Timeout "120". apt-get has no timeout by default, so pass -o DPkg::Lock::Timeout=<seconds> in scripts to get the same waiting behaviour.
Can I just delete /var/lib/dpkg/lock-frontend?
Don't. The lock is an fcntl lock on the open file descriptor, so removing the path leaves the holder running and lets a second package manager write /var/lib/dpkg/status at the same time. The kernel releases the lock when the holder exits; if no process holds it, the error does not appear at all.
How do I know whether the holder is unattended-upgrades?
The message names the process, and ps -p <pid> -o pid,ppid,etime,cmd shows /usr/bin/unattended-upgrade with its elapsed time. Its progress is in /var/log/unattended-upgrades/unattended-upgrades.log, and systemctl list-timers 'apt-daily*' shows when the timer fired.
Is it safe to stop unattended-upgrades in the middle of a run?
Use systemctl stop unattended-upgrades rather than kill -9. The service's monitor sends SIGTERM to the running unattended-upgrade, which logs that it received the signal and finishes the current package before exiting; the unit allows up to 1800 seconds for that.
When do I need dpkg --configure -a?
Only when apt says dpkg was interrupted. The dpkg manual defines --configure -a as configuring every package that is unpacked but not yet configured, which is the state a killed run leaves behind. Follow it with apt-get install -f to repair dependencies.
References
- Ubuntu Server docs — Automatic updates (unattended-upgrades, apt-daily / apt-daily-upgrade timers, Persistent catch-up at boot, /var/log/unattended-upgrades/, 20auto-upgrades)
- dpkg(1) man page — --configure package...|-a|--pending, DPKG_FRONTEND_LOCKED (since dpkg 1.19.1)
- Debian apt changelog — 1.9.11 "apt(8): Wait for lock", 2.0.0 lock-wait message with absolute time
Haneul Seo
Infrastructure engineer · 10+ years running Linux fleets
More in this category
systemd: Start request repeated too quickly
systemd refuses to start a unit that was started more than StartLimitBurst times (default 5) within StartLimitIntervalSec (default 10s), and Restart= counts against that limit. With the 100 ms default RestartSec a crashing service burns all five attempts in under a second. Find the real crash in the journal, fix it, run reset-failed, and give restarts room with RestartSec.
SSH: Received disconnect ... Too many authentication failures
Your agent is offering more keys than the server will let you try. Every public key sshd looks at burns one of the MaxAuthTries attempts — six by default, often three on a hardened host — so the right key never gets its turn and the server hangs up before you type anything. IdentitiesOnly=yes with an explicit IdentityFile pins the connection to one key and the attempt count drops to one.
Active Directory: replication fails with error 1722, The RPC server is unavailable
RPC reports 1722 (0x6ba, RPC_S_SERVER_UNAVAILABLE) when a lower layer fails to connect, so the real fault is almost never RPC itself — it is DNS, a blocked port, or a host-side setting on one of the two domain controllers. repadmin tells you which partner is failing, dcdiag /test:dns rules out name resolution, and Test-NetConnection plus the dynamic port range settle the firewall question. The most common miss is a rule that allows TCP 135 but not 49152–65535.
Windows Server RDS: The remote session was disconnected because there are no Remote Desktop License Servers available to provide a license
The 120-day RD Licensing grace period ended and the session host has no usable license server, so it refuses sessions. GetGracePeriodDays returning DaysLeft 0 and an empty SpecifiedLSList confirm it in seconds. The fix is a real, activated license server with CALs that are new enough for the host — a 2019 CAL cannot serve a 2022 session host — configured through the deployment or the Licensing policies, plus RPC ports open between the two.
Windows 11: "Your organization's security policies block unauthenticated guest access" when opening a NAS share (0x80070035)
The SMB client on Windows 10 Enterprise/Education/Pro for Workstations, Windows 11 Pro and Windows Server 2019+ refuses guest logons by default, and Windows 11 24H2 Enterprise/Pro/Education also requires SMB signing, which guest sessions can't do. A NAS share that only offers guest access therefore fails with the 'block unauthenticated guest access' dialog, Error code 0x80070035, or System error 3227320323, and Event ID 31017 'Rejected an insecure guest logon' lands in the SmbClient/Security log. The fix Microsoft recommends is a real account on the NAS and signing support in its firmware; Set-SmbClientConfiguration -EnableInsecureGuestLogons $true (plus -RequireSecuritySignature $false on 24H2) is the escape hatch, and it costs you signing and encryption on that client.
macOS: xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools)
git, make, clang and other /usr/bin developer commands on macOS are shims that hand off to the active developer directory, and xcrun is reporting that the directory xcode-select points at has no tools in it — most often because a major macOS upgrade left /Library/Developer/CommandLineTools empty, or Xcode was moved or deleted. Check xcode-select -p and the package receipt, then reinstall the Command Line Tools with xcode-select --install, or point xcode-select at the Xcode you actually have.