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

Bash
# 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 update

If 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.

Bash
sudo apt -o Debug::Acquire::http=true -o Debug::Acquire::https=true update

Watch 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:

Bash
grep -R "^deb|^URIs:" /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null

And what addresses your resolver returns:

Bash
getent ahosts deb.debian.org

If 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:

Bash
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/InRelease

If -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.

Bash
printf 'Acquire::ForceIPv4 "true";n' | sudo tee /etc/apt/apt.conf.d/99force-ipv4

Option B — System-wide IPv4 preference (use gai.conf): make the whole resolver prefer IPv4 globally. Edit /etc/gai.conf and uncomment:

Bash
precedence ::ffff:0:0/96  100

Option C — Disable IPv6 entirely (nuclear): edit /etc/sysctl.d/99-disable-ipv6.conf:

Bash
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1

Apply with sudo sysctl --system. Verify:

Bash
cat /proc/sys/net/ipv6/conf/all/disable_ipv6   # should print 1
getent ahosts deb.debian.org                   # should show only IPv4

Root-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:

Bash
# 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:

Bash
# Find the right MTU from tracepath, then:
sudo ip link set dev eth0 mtu 1464

To 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:

Bash
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN 
    -j TCPMSS --clamp-mss-to-pmtu

For 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:

Bash
sudo sysctl -w net.ipv4.ip_no_pmtu_disc=1

Persist 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:

  1. A proxy got configured. Check apt-config dump | grep -i proxy and /etc/apt/apt.conf.d/*proxy*. If your environment has http_proxy set but APT isn’t honoring it, run with sudo -E apt update to pass the env through.
  2. 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.
  3. 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 returns 416 Range Not Satisfiable, and APT just… waits. The fix from a 2024 Server Fault report:

Bash
sudo apt-get install apt    # sometimes fixes a stale client-side cache
sudo apt-get update

If 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:

Bash
sudo tcpdump -n -i eth0 host deb.debian.org and port 80

You’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:

Bash
time curl -s -o /dev/null -w "%{http_code} %{time_total}sn" 
    http://deb.debian.org/debian/dists/bookworm/InRelease

Then try an alternate mirror from /etc/apt/sources.list:

Bash
# 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 contrib

If a different mirror responds instantly, you’ve got your answer. To pick a fast mirror automatically, install netselect-apt:

Bash
sudo apt install netselect-apt
sudo netselect-apt bookworm

Step 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:

Bash
sudo tee /etc/apt/apt.conf.d/99short-timeouts <<'EOF'
Acquire::http::Timeout "10";
Acquire::https::Timeout "10";
Acquire::Retries "3";
Acquire::ForceIPv4 "true";
EOF

This 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:

Bash
# 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; done

If 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 apt and re-run. The lock file is there for a reason. Use sudo fuser -vki /var/lib/dpkg/lock-frontend to 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 — your apt update will be 3-5× slower.
  • Don’t blame the mirror first. It’s almost always your network stack. Confirm with tcpdump before declaring the upstream broken.

Prevention Checklist

When you set up a new Debian/Ubuntu host, run this once:

Bash
# 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
EOF

Three 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

Last modified: 2026年7月13日

Author

Comments

Write a Reply or Comment

Your email address will not be published.