Debian apt Stuck on “Waiting for headers”: A Root-Cause Field Guide
You run sudo apt update and it freezes. The progress bar stops at 0% [Waiting for headers] or 23% [Waiting for headers], the cursor blinks for ten seconds, then thirty, then two minutes. Eventually either the connection times out and you get Connection failed, or — worse — the command just sits there until you Ctrl-C and walk away wondering what your system has against you.
The thing is: APT itself is not broken. The connection is open, the request was sent, the server received it. Headers just never come back. That detail — “TCP up, no response” — is the entire diagnostic frame. Everything below is the same five-layer network stack, and the cure is finding which layer is silently dropping your packets.
This guide is the field-tested ordering I use on every stuck host. It starts at the cheapest signal (a debug log) and walks through to the worst-case (a corp firewall rewriting your MTU). Run each step until one of them moves the needle.
TL;DR — If You Just Want It Working Now
# Force APT onto IPv4 — fixes ~70% of stuck-header cases on dual-stack hosts
printf 'Acquire::ForceIPv4 "true";n' | sudo tee /etc/apt/apt.conf.d/99force-ipv4
sudo apt clean && sudo apt updateIf that worked, your IPv6 is broken in a way DNS thinks it’s fine. Scroll down to “Layer 2: Broken IPv6 on a dual-stack host” for the proper fix.
If it didn’t work, keep reading.
Step 1: Find Out Where It’s Stuck
Before changing anything, run APT with debug output. This single command tells you which layer is the culprit — DNS, TCP connect, TLS, or the HTTP response itself.
sudo apt -o Debug::Acquire::http=true -o Debug::Acquire::https=true updateWatch for these signals:
| Symptom in debug output | Layer that’s broken |
|---|---|
Stalls before Connecting to ... |
DNS — your resolver can’t find the mirror |
Connecting to <ipv6-address> and then nothing |
IPv6 routing — see Step 2 |
Connected to ... but no GET /dists/.../InRelease follows |
TLS / SNI — corporate middlebox eating the handshake |
GET /dists/.../InRelease sent, no response |
MTU / PMTUD — see Step 3 |
| Some hosts work, some don’t | The mirror itself — see Step 5 |
Then check what mirrors you’re actually configured to use:
grep -R "^deb|^URIs:" /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/nullAnd what addresses your resolver returns:
getent ahosts deb.debian.orgIf you see both 2a04:4e42::... and 151.101.x.x, you’re on dual-stack. Keep that in mind.
Step 2: Broken IPv6 on a Dual-Stack Host (Most Common Cause)
The classic scenario: your VPS template enabled IPv6 at boot, the kernel happily brought up a link-local address, your /etc/resolv.conf returns AAAA records — but no IPv6 route actually reaches the public internet. Maybe the data center never finished the v6 rollout. Maybe your firewall drops ICMPv6. Maybe the upstream provider silently blackholes your prefix.
APT dutifully tries the AAAA address first, opens a TCP connection (which succeeds — IPv6 is “up” locally), sends the GET, and then waits forever because the packets fall into a routing black hole.
Quick test: compare a v6 fetch against a v4 fetch directly:
curl -6 -I --max-time 10 http://deb.debian.org/debian/dists/bookworm/InRelease
curl -4 -I --max-time 10 http://deb.debian.org/debian/dists/bookworm/InReleaseIf -6 hangs and -4 returns 200 OK, IPv6 is your problem. Pick one of these fixes:
Option A — APT-only workaround (fastest, safe): force APT to prefer IPv4 without touching the kernel.
printf 'Acquire::ForceIPv4 "true";n' | sudo tee /etc/apt/apt.conf.d/99force-ipv4Option B — System-wide IPv4 preference (use gai.conf): make the whole resolver prefer IPv4 globally. Edit /etc/gai.conf and uncomment:
precedence ::ffff:0:0/96 100Option C — Disable IPv6 entirely (nuclear): edit /etc/sysctl.d/99-disable-ipv6.conf:
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1Apply with sudo sysctl --system. Verify:
cat /proc/sys/net/ipv6/conf/all/disable_ipv6 # should print 1
getent ahosts deb.debian.org # should show only IPv4Root-cause option D — actually fix the IPv6 path: if you’re on a VPS where v6 is supposed to work, file a ticket. The fix is upstream (BGP route, firewall rule, MTU). Don’t paper over it permanently — you’ll hit this again the next time something tries AAAA.
Step 3: MTU and PMTUD (The One Nobody Believes Until They See It)
This is the silent killer. Small packets get through — DNS works, TCP opens, the GET leaves your box. The server’s response, though, is bigger than the path actually allows. The path MTU is lower than your interface MTU. PMTUD (Path MTU Discovery) is supposed to detect this, but it relies on ICMP frag-needed packets making it back to you.
Corporate firewalls routinely drop ICMP. Cloud load balancers do it. Some ISPs do it. The moment ICMP doesn’t come back, your host has no idea its packets are too big — it just keeps retransmitting at 1500 and waiting for a response that will never arrive.
How to verify: check the actual MTU on the path to your mirror, not your interface:
# Replace with the real mirror host your sources.list points to
tracepath -n deb.debian.org
# Or for a specific MTU probe:
ip route get $(getent ahostsv4 deb.debian.org | awk '{print $1}' | head -1)You should see lines like pmtu=1500 or pmtu=1464 (1464 = 1500 minus a typical tunnel/GRE overhead). If tracepath shows pmtu=1500 but you can curl small files fine and large ones hang, you have a PMTUD problem.
The fixes, in order of how disruptive they are:
Option A — Drop the interface MTU. If you’re on a host that tunnels through a VPN, GRE, VXLAN, or PPPoE, set the MTU to match the post-tunnel path:
# Find the right MTU from tracepath, then:
sudo ip link set dev eth0 mtu 1464To make it survive reboot, edit /etc/network/interfaces.d/eth0.cfg (Debian) or your NetworkManager connection profile (nmcli con mod ... 802-3-ethernet.mtu 1464).
Option B — TCP MSS clamping via iptables. If you can’t change the MTU (cloud VM, you don’t control the router), clamp TCP SYN MSS so connections negotiate a smaller segment size:
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN
-j TCPMSS --clamp-mss-to-pmtuFor your own host’s outgoing traffic (not forwarded), use OUTPUT instead of FORWARD.
Option C — Disable PMTUD on the host (last resort). Forces all packets to be fragmentable. It works, but it’s the network equivalent of throwing away a smoke detector because it kept beeping:
sudo sysctl -w net.ipv4.ip_no_pmtu_disc=1Persist in /etc/sysctl.d/99-no-pmtu.conf. The real fix is letting ICMP frag-needed through — talk to whoever runs the firewall.
Step 4: HTTP Proxy, Corporate Middleboxes, and the “It Worked Yesterday” Case
Three things turn a healthy APT into a hung one without changing anything on your machine:
- A proxy got configured. Check
apt-config dump | grep -i proxyand/etc/apt/apt.conf.d/*proxy*. If your environment hashttp_proxyset but APT isn’t honoring it, run withsudo -E apt updateto pass the env through. - An IDS/IPS is dropping your requests. Some corporate “security” middleboxes see the rapid succession of HTTP GETs APT does and rate-limit you silently. Try from a different network (phone hotspot, home) — if it works there, it’s your office network.
- The mirror’s HTTP/1.1 range support is broken. There’s a known class of bug where APT sends
Range:headers for partial content, the mirror returns416 Range Not Satisfiable, and APT just… waits. The fix from a 2024 Server Fault report:
sudo apt-get install apt # sometimes fixes a stale client-side cache
sudo apt-get updateIf that doesn’t help, switch mirrors — see Step 5.
To confirm a middlebox is the culprit, point tcpdump at the connection while the hang is happening:
sudo tcpdump -n -i eth0 host deb.debian.org and port 80You’ll see the SYN, the SYN-ACK, your GET — and then either nothing (packets blackholed) or a RST from somewhere unexpected (middlebox killing the connection). Compare to a working host on the same network; the difference tells you who’s lying.
Step 5: The Mirror Itself
Sometimes the simplest answer is the right one: the mirror you picked is having a bad day. Run this test:
time curl -s -o /dev/null -w "%{http_code} %{time_total}sn"
http://deb.debian.org/debian/dists/bookworm/InReleaseThen try an alternate mirror from /etc/apt/sources.list:
# Debian mirrors — pick one close to your data center
deb http://ftp.us.debian.org/debian/ bookworm main contrib
deb http://ftp.de.debian.org/debian/ bookworm main contrib
deb http://mirror.sg.debian.org/debian/ bookworm main contribIf a different mirror responds instantly, you’ve got your answer. To pick a fast mirror automatically, install netselect-apt:
sudo apt install netselect-apt
sudo netselect-apt bookwormStep 6: Useful APT Timeouts and Retries
If your environment is genuinely flaky (mobile, satellite, hotel wifi) and you can’t fix the upstream, tune APT to fail fast and retry instead of hanging:
sudo tee /etc/apt/apt.conf.d/99short-timeouts <<'EOF'
Acquire::http::Timeout "10";
Acquire::https::Timeout "10";
Acquire::Retries "3";
Acquire::ForceIPv4 "true";
EOFThis won’t fix a broken network, but it’ll keep APT from locking up your terminal for two minutes while it waits for a packet that will never arrive.
Verification: How to Know It’s Actually Fixed
A real fix shows all of these:
# 1. Debug output shows headers coming back
sudo apt -o Debug::Acquire::http=true update 2>&1 | grep -c 'InRelease.*200'
# 2. No "Waiting for headers" line in the progress output
sudo apt update 2>&1 | grep -c 'Waiting for headers' # should print 0
# 3. Timing is reasonable
time sudo apt update # should be under 30 seconds for a healthy mirror
# 4. Multiple retries all succeed
for i in 1 2 3; do sudo apt update; doneIf any of these still hangs, you’ve only patched one of the broken layers. Go back to Step 1 and look harder at the debug output.
What NOT to Do
- Don’t
pkill -9 aptand re-run. The lock file is there for a reason. Usesudo fuser -vki /var/lib/dpkg/lock-frontendto clean up safely. - Don’t clear
/var/lib/apt/lists/repeatedly. That triggers a full re-download of every mirror’s metadata, making the hang worse if it’s bandwidth-related. - Don’t set
Acquire::http::Pipeline-Depth "0"and call it a day. That disables HTTP pipelining entirely — yourapt updatewill be 3-5× slower. - Don’t blame the mirror first. It’s almost always your network stack. Confirm with
tcpdumpbefore declaring the upstream broken.
Prevention Checklist
When you set up a new Debian/Ubuntu host, run this once:
# Probe both stacks, pick the working one, lock it in
curl -6 -I --max-time 5 http://deb.debian.org/debian/ >/dev/null 2>&1
|| printf 'Acquire::ForceIPv4 "true";n' | sudo tee /etc/apt/apt.conf.d/99force-ipv4
# Make APT fail fast instead of hanging your shell
sudo tee /etc/apt/apt.conf.d/99short-timeouts <<'EOF'
Acquire::http::Timeout "10";
Acquire::https::Timeout "10";
Acquire::Retries "3";
EOF
# Document your MTU if you're behind a tunnel
sudo tee /etc/network/interfaces.d/eth0.cfg <<'EOF'
auto eth0
iface eth0 inet dhcp
mtu 1464
EOFThree lines. Done. Future-you will not thank present-you because you’ll never know this article existed.
Filed under: Linux Software · Debian · APT · Networking
Tested on: Debian 12 (bookworm), Debian 13 (trixie), Ubuntu 22.04/24.04 LTS
Diagnostic commands referenced: apt, getent, tracepath, tcpdump, ip, curl, sysctl
Comments