Ollama Pull Stuck or Slow? Fix Failed Downloads
Want to go deeper than this article?
Free account unlocks the first chapter of all 25 courses — RAG, agents, MCP, voice AI, MLOps, real GitHub repos.
Ollama’s running. Here’s what to build with it. Go from “ollama run” to RAG apps, agents, and fine-tuned models — structured and hands-on. First chapter free.
Run the same ollama pull again — it resumes, it does not start over. Ollama writes each blob to a -partial file with one small JSON file per chunk beside it, and Prepare() in server/download.go globs those -partial-* files at the start of every pull, restores each chunk's byte count, and skips chunks that already finished. The thing that turns a resume into a fresh 40GB download is not the pull, it is the server: on startup Ollama prunes any file in blobs/ whose name will not parse as a digest, which includes every -partial file, once it is more than an hour old. If you are coming back to an abandoned download tomorrow, start the daemon with OLLAMA_NOPRUNE=1.
That is the answer to the question most people are actually asking. The rest of this page is the part that saves you the second and third attempt: which of the half-dozen error strings you got, what each one is really telling you, and which of them are worth retrying versus which mean your network will never finish this download unattended. If you are still at the install stage rather than the debugging stage, start with our complete Ollama guide instead.
All constants, error strings and log lines below were read from ollama/ollama server/download.go on main, cross-checked against the log output pasted into the linked issues. The current release at the time of writing is v0.32.15 (19 August 2026). These values have changed before and will change again — the file on your own build is the authority, not this page.
Does re-running ollama pull resume, or start over?
It resumes, at chunk granularity. Understanding which chunk it resumes from is what tells you whether a flaky connection will ever converge.
A blob is not downloaded as one stream. Prepare() splits it into parts using three constants:
numDownloadParts = 16
minDownloadPartSize int64 = 100 * format.MegaByte
maxDownloadPartSize int64 = 1000 * format.MegaByte
The part size is Total / 16, then clamped into that 100MB–1000MB window. You can verify the formula against a real log without owning anything: the server log pasted into issue #17484 contains
msg="downloading 8440f2a076f1 in 19 1 GB part(s)"
msg="downloading eacf610d1ee4 in 10 100 MB part(s)"
for an 18 GB blob and a 927 MB blob. 18,000 / 16 = 1,125 MB, above the 1,000 MB ceiling, so it clamps to 1 GB parts — 19 of them. 927 / 16 = 58 MB, below the 100 MB floor, so it clamps up to 100 MB parts — 10 of them. The formula and the log agree.
All 16-plus parts are fetched concurrently (g.SetLimit(numDownloadParts)), each with its own HTTP Range: bytes=start-end request. That is why a slow link feels worse than a single-stream download: sixteen streams are competing for the same pipe.
What is on disk while a pull is running
| Path (under your models directory) | What it is |
|---|---|
blobs/sha256-<digest> | A finished, verified blob |
blobs/sha256-<digest>-partial | The download in progress. It is preallocated to the blob's full size (file.Truncate(b.Total) plus setSparse), so ls -l reports the final size within seconds — that number is not progress |
blobs/sha256-<digest>-partial-<N> | One tiny JSON file per chunk holding that chunk's offset, size and completed bytes. This is the resume state. Delete these and you restart |
What survives an interruption, and what does not
This is the table that explains the behaviour people find maddening. The two branches come from downloadChunk, which commits a chunk's progress for exactly two error types and rolls it back for everything else.
| How the download stopped | Does that chunk keep its progress? | Why |
|---|---|---|
| You pressed Ctrl-C | Yes | context.Canceled is one of two errors the copy path commits and writes out |
Remote closed the stream mid-body (unexpected EOF) | Yes | io.ErrUnexpectedEOF is the other. The source comment says so: "return nil or context.Canceled or UnexpectedEOF (resumable)" |
Stall watchdog fired (part stalled) | Yes | The watchdog cancels the chunk context, which surfaces as context.Canceled |
| Connection reset, DNS failure, timeout | No — that chunk rewinds | The generic branch runs b.Completed.Add(-n) and returns before the progress is written to the part file |
| You killed the daemon, or lost power | Only up to the last completed chunk attempt | The part file is written when a chunk attempt ends, not continuously |
| You restarted the daemon more than an hour later | No — the files are gone | Startup prune. See the section on protecting partial files |
The fourth row is the one worth internalising. On a connection that drops rather than stalls, every reset throws away the bytes that chunk had accumulated since its last successful attempt — which on a 1 GB part can be hundreds of megabytes.
Reading articles is good. Building is better.
Free account = 20+ free chapters across 25 courses, with a per-chapter AI tutor. No card. Cancel anytime if you ever upgrade.
Which error is it? The full error-string map
The single most common mistake is treating Error: max retries exceeded as the diagnosis. It is not. It is a wrapper — fmt.Errorf("%w: %w", errMaxRetriesExceeded, err) — and the real error is the text after the colon. Six failed attempts on one chunk produce it, with maxRetries = 6 and a sleep of 2^try seconds between attempts: 1s, 2s, 4s, 8s, 16s, 32s.
| Error string you see | Where it comes from | What it actually means | What to do |
|---|---|---|---|
Error: max retries exceeded: <anything> | errMaxRetriesExceeded in server/download.go | One chunk failed six times. Read the part after the colon — that is the real fault | Diagnose the suffix, using the rows below |
Error: context deadline exceeded | A Go context timeout — in a pull, usually the 30-second budget on resolving the blob's direct URL | A registry request hung long enough to burn the whole resolution window | Re-run the pull. If it lands at the end of an otherwise complete download, see the section below |
max retries exceeded: unexpected EOF | Remote closed the connection mid-body, six times | Resumable, and the progress was kept each time. Almost always the network, not Ollama | Re-run the pull. Repeat until it converges — it will, because progress is preserved |
max retries exceeded: ... read: connection reset by peer | TCP RST from something between you and the CDN | A middlebox, VPN, corporate firewall or ISP is killing long-lived TLS streams | Retry off the VPN; if it repeats, this is the one case where progress genuinely rewinds |
dial tcp: lookup <32-hex>.r2.cloudflarestorage.com: no such host | DNS failure on the redirect target, not the registry | The manifest fetch worked; the blob lives on a signed CDN URL at a different hostname your resolver cannot answer for | Fix DNS or the proxy — not a bandwidth problem |
dial tcp: lookup registry.ollama.ai on 10.x.x.x:53: no such host | Your internal resolver cannot resolve the registry at all | The daemon is not going through the proxy you configured in your shell | Set HTTPS_PROXY for the server process |
maximum redirects exceeded (10) for directURL | errMaxRedirectsExceeded | Something is intercepting and re-redirecting the CDN handoff — captive portal, TLS-inspecting proxy | Get off the intercepting network, or install the proxy CA as a system certificate |
<digest> part N stalled; retrying. If this persists, press ctrl-c to exit, then 'ollama pull' to find a faster connection. | The 30-second stall watchdog, logged at INFO | Not an error. One chunk received zero bytes for 30 seconds and is being reopened | Nothing, unless it loops forever — then see the stall section |
no space left on device | syscall.ENOSPC | Out of disk. One of only two errors that abort immediately with no retry | Free space, or move the models directory — see OLLAMA_MODELS not working |
unexpected status code <n> | The registry returned something that is neither 200 nor 307 | A gateway, WAF or proxy answered instead of the registry | Look at the number: 403 and 407 mean proxy auth; 5xx means try later |
Error: pull model manifest: <error> | images.go — the manifest fetch, before any blob starts | You never got as far as downloading weights. Auth, DNS or TLS, not bandwidth | Test with curl -I https://registry.ollama.ai/v2/ from the same machine |
Two of those deserve emphasis. unexpected EOF is the good failure — it keeps your progress, so brute-force retrying works. connection reset by peer is the bad one, because that path rewinds the chunk, which is how people end up watching a 40GB download oscillate for a day.
Why does the progress bar jump backwards?
Because on a generic chunk error, Ollama subtracts the bytes it had already counted. The rollback is explicit in the source:
n, err := io.CopyN(w, io.TeeReader(resp.Body, part), part.Size-part.Completed.Load())
if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, io.ErrUnexpectedEOF) {
// rollback progress
b.Completed.Add(-n)
return err
}
b.Completed is what the progress bar reads. So a chunk that got 800MB in and then hit a reset silently gives those 800MB back, and the percentage falls. Sixteen chunks doing that at random on a bad link produces the sawtooth in issue #17329, "Bug: Download keep resetting when the wifi is slow" — still open, with the reporter noting the useful detail that the server log shows nothing at all when it happens.
There is a proposed fix. PR #17389 would add bandwidth sampling, scale the stall timeout by measured speed, drop to four parallel parts on slow links, and require three consecutive stall cycles before retrying. Note the tense: it is open and unmerged, so none of that is in your build. Do not configure around it.
What you can do today is reduce the number of things that can go wrong at once, which mostly means not downloading over Wi-Fi, and not downloading through a VPN.
What does Error: context deadline exceeded actually mean?
It is a Go context timeout, and in ollama pull it is almost always the 30-second budget for resolving a blob's direct download URL — not the download itself.
Before fetching a blob, Ollama asks the registry for the real (signed, CDN-hosted) URL. That resolution runs inside a single wrapping context:
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
with a backoff-and-retry loop inside it, using the same context. The consequence, spelled out in PR #17551: "If the first registry request hangs until that context expires, the backoff sees an already-canceled context and returns immediately, so no retry actually occurs." One slow request eats the entire retry budget.
That explains the shape of issue #17484, which is what most people are searching when they land here: two large blobs complete at 100%, and then the pull dies on a 479-byte one.
pulling 8440f2a076f1: 100% ... 18 GB
pulling eacf610d1ee4: 100% ... 927 MB
Error: context deadline exceeded
The last blob was 479 bytes. Bandwidth had nothing to do with it. PR #17551 proposed bounding each individual request to 10 seconds while keeping the 30-second overall budget — it was closed without being merged on 17 August 2026, and main still has the shared 30-second context. Issue #17484 is still open.
What to do: re-run the pull. Because the big blobs completed, the resume is cheap — you are retrying a metadata fetch, not 18GB. If it fails repeatedly at the same point, the registry path is being slowed by something local (DNS, proxy, TLS inspection) rather than by your download speed.
Reading articles is good. Building is better.
Free account = 20+ free chapters across 25 courses, with a per-chapter AI tutor. No card. Cancel anytime if you ever upgrade.
Why does it stall at the same percentage every time?
Ollama has a per-chunk watchdog that fires after 30 seconds with no bytes written, and it logs a message people usually mistake for an error:
<digest> part 7 stalled; retrying. If this persists, press ctrl-c to exit,
then 'ollama pull' to find a faster connection.
That is an INFO-level log, and it is the system working. The important detail is in the retry loop: a stalled chunk is handled with try-- before continue, so a stall does not consume the six-attempt budget. A chunk can stall and reopen forever without ever producing max retries exceeded. That is why a genuinely dead connection presents as an infinite hang rather than a clean failure.
downloadStallTimeout is a package-level var set to 30 seconds. It is not exposed as an environment variable, so there is no supported way to raise it on a slow link — the only lever the tracker offers is the unmerged PR above. The historical version of this complaint is issue #1736, "Download slows to a crawl at 99%", which is the same watchdog interacting with a chunk that opens but never streams.
If you are stuck in that loop: Ctrl-C, wait, and pull again. Your progress is preserved (Ctrl-C is the good stop), and a new pull re-resolves the CDN URL, which frequently lands you on a different edge node.
Why does DNS fail on a random Cloudflare hostname?
Because the blob does not come from registry.ollama.ai. The registry answers with a redirect to a signed, time-limited storage URL on a different hostname, and server/download.go deliberately stops following at the first cross-host redirect and uses that URL directly:
// if the hostname is the same, allow the redirect
if req.URL.Hostname() == requestURL.Hostname() {
return nil
}
// stop at the first redirect that is not the same hostname
return http.ErrUseLastResponse
So a pull touches two hostnames, and your network has to be able to reach both. The classic failure, reported verbatim in issue #10151, is:
Error: max retries exceeded: Get "https://dd20bb891979d25aebc8bec07b2b3bbc.r2.cloudflarestorage.com/ollama/docker/registry/v2/blobs/sha256/dd/...":
dial tcp: lookup dd20bb891979d25aebc8bec07b2b3bbc.r2.cloudflarestorage.com: no such host
The manifest downloaded fine. The blob host did not resolve. Things that produce that, in the order worth checking:
- A resolver that only answers for internal names. Confirm with a plain lookup of the failing hostname from the same machine. If
nslookupfails too, it is not an Ollama bug. - A DNS-level blocker. Pi-hole, NextDNS and corporate filtering commonly have broad rules covering object-storage hostnames.
- IPv6 that resolves but does not route. If your resolver returns AAAA records for a path with no working IPv6 egress, connections hang rather than fail cleanly. Test by disabling IPv6 on the interface, or prefer IPv4 at the OS level, and pull again.
- A proxy configured for the shell but not the daemon. This is common enough that it gets its own section below. Issue #15708 is the open report of exactly this pattern: the manifest resolves through the proxy, the blob download then fails with
no such host.
How do I pull through a corporate proxy?
The proxy variable has to reach the Ollama server process. Exporting it in the terminal where you type ollama pull usually does nothing, because the CLI is a thin HTTP client — the daemon does the downloading.
The official FAQ is specific about which variable: "Use HTTPS_PROXY to redirect outbound requests through the proxy. Ensure the proxy certificate is installed as a system certificate." And it warns against the obvious-looking alternative: "Avoid setting HTTP_PROXY. Ollama does not use HTTP for model pulls, only HTTPS. Setting HTTP_PROXY may interrupt client connections to the server."
Where to set it depends entirely on how the server was started:
| How the server runs | Where HTTPS_PROXY must go |
|---|---|
| Linux, installed with the official script | The systemd unit — sudo systemctl edit ollama.service, add Environment="HTTPS_PROXY=http://proxy:port" under [Service], then systemctl daemon-reload && systemctl restart ollama. A shell export never reaches it |
| macOS desktop app | launchctl setenv HTTPS_PROXY "http://proxy:port", then restart the Ollama application. .zshrc is not read by a launchd-started app |
| Windows desktop app | Quit from the tray, set the variable under "Edit environment variables for your account", relaunch from the Start menu |
ollama serve in a terminal | Export in that terminal, before starting the server |
| Docker | -e HTTPS_PROXY=https://proxy.example.com on docker run, per the FAQ |
TLS-inspecting proxies need one more step: the proxy's CA has to be a system certificate, not just trusted by your browser. Go's HTTP client reads the system trust store, and a proxy that re-signs traffic without a trusted CA produces a TLS error rather than a clean proxy error.
The same "set it on the daemon, not the shell" rule is what breaks model relocation too — that is covered in detail in our guide to OLLAMA_MODELS not working per platform, and the full environment-variable surface is tabulated in our Ollama troubleshooting guide.
Where are the partial files, and how do I stop Ollama deleting them?
Ollama prunes unrecognised blob files at server startup, and every -partial file is unrecognised. This is the mechanism behind "I restarted my machine and my 40GB download started from zero."
PruneLayers() runs on startup (unless OLLAMA_NOPRUNE is set) and walks the blobs directory. For each file it converts - to : and tries to parse the result as a digest. sha256-abc123-partial becomes sha256:abc123:partial, which is not a valid digest, so the file is removed — the source comment is literally "remove invalid blobs (e.g. partial downloads)". The only thing standing between you and that deletion is a grace period:
const layerPruneGracePeriod = time.Hour
...
if time.Since(info.ModTime()) < layerPruneGracePeriod {
continue
}
So: a partial download survives a server restart for one hour after its last write, and not a minute longer.
| Situation | What happens to your partials |
|---|---|
| Pull dies, you re-run it immediately | Fine — resumes |
| Pull dies, you restart the daemon within the hour | Fine — the grace period protects them |
| Pull dies, you come back tomorrow and start the daemon normally | Deleted. Full re-download |
You start the daemon with OLLAMA_NOPRUNE=1 | Prune is skipped entirely; partials survive indefinitely |
Set OLLAMA_NOPRUNE=1 the same way you would set the proxy above — on the server process, not the shell. It disables all blob pruning, including cleanup of genuinely orphaned layers, so turn it off again once the download lands.
The directories themselves, per the official FAQ:
| OS | Models directory |
|---|---|
| macOS | ~/.ollama/models |
| Linux (standard installer) | /usr/share/ollama/.ollama/models |
| Windows | C:\Users\%username%\.ollama\models |
Partials live in blobs/ inside those. If you have moved the directory and are not sure Ollama agrees with you about where it is, that is a different failure — see the OLLAMA_MODELS guide.
What actually helps on a slow or flaky connection?
Honestly, less than you would like. There is no download-speed setting, no configurable part count, and no way to raise the stall timeout. OLLAMA_MAX_TRANSFER_STREAMS exists (default 4) but its own description in envconfig/config.go scopes it to "safetensors model pulls/pushes" — it does not change numDownloadParts = 16 for registry blob downloads.
What is left, ranked by how much difference it makes:
- Wire, not Wi-Fi. Sixteen concurrent range requests over a marginal wireless link is the exact condition in #17329. This is the single biggest lever.
- Turn the VPN off for the pull. VPN concentrators are a common source of the
connection reset by peervariant, which is the failure mode that rewinds progress. - Just re-run it, repeatedly. With
unexpected EOFand Ctrl-C both preserving progress, a loop of pull-fail-pull genuinely converges. Do it inside the one-hour prune window, or setOLLAMA_NOPRUNE=1first. - Pull a smaller quantisation. A Q4_K_M build is roughly 0.6GB per billion parameters, so a 70B is about 42GB and a 32B about 19GB — check what you actually need against our Ollama model RAM and VRAM table before committing to the biggest file on the page.
- Copy the blobs from a machine that already has them. If a colleague or a second machine has the model, moving
blobs/andmanifests/by hand is faster and more reliable than any retry strategy. Our guide to copying Ollama models to an offline PC covers the exact file layout, and air-gapped AI deployment covers doing it at scale. - Pull overnight, unattended, and accept it may need a second run. Given progress preservation, two overnight runs beat one supervised day.
Honest limitations
- Nothing here was reproduced on our own connection. Every constant and code excerpt comes from
ollama/ollamaonmain; every error string and log line comes from the linked issue where a user pasted it. We do not publish download-speed benchmarks we did not measure, and there are none on this page. - Constants drift.
maxRetries,numDownloadParts,downloadStallTimeoutandlayerPruneGracePeriodare compile-time values in a fast-moving repo. They are accurate formainat v0.32.15 (19 August 2026). Checkserver/download.goon your build before quoting them back at anyone. - #17484, #17329 and #15708 were open when this was written, and PRs #17551 and #17389 were unmerged. Click through before assuming a workaround is still needed — or still necessary.
- Non-registry sources behave differently. Pulling
hf.co/...GGUF models goes through the same downloader but a different host and auth path; #17484 is itself a Hugging Face pull. Errors that mention ahf.coURL are worth searching separately. - We are not going to tell you to edit
/etc/hosts. Pinning a signed, rotating CDN hostname to an IP you found on a forum is how you get a download that fails signature checks later. Fix DNS properly or use the proxy.
FAQ
Does ollama pull resume a partial download, or start over?
It resumes. Each chunk's progress lives in a small JSON file named sha256-<digest>-partial-<N> next to the partial blob, and Prepare() reads those back at the start of every pull, skipping chunks that already completed. The exception is the startup prune: partial files older than one hour are deleted when the server starts, unless OLLAMA_NOPRUNE is set.
What does Error: max retries exceeded mean on its own?
That one chunk failed six consecutive times, with a doubling sleep between attempts. It is a wrapper, not a diagnosis — the real cause is the error text printed after the colon. unexpected EOF means the remote hung up (retry, your progress is kept); connection reset by peer means something actively killed the connection (that chunk's progress rewinds); no such host means DNS, not bandwidth.
Why does ollama pull fail with context deadline exceeded after the download finished?
Because resolving a blob's direct URL runs under a single 30-second context shared across its own retry loop, so one hung request can consume the entire budget — including on a tiny final blob after gigabytes have already landed. That is issue #17484, still open; the proposed fix in PR #17551 was closed without merging. Re-running the pull is cheap, because the completed blobs are kept.
Can I make Ollama download with fewer parallel connections?
Not for registry blobs. numDownloadParts = 16 is a compile-time constant with no environment override. OLLAMA_MAX_TRANSFER_STREAMS (default 4) applies to safetensors pulls and pushes, not to this path. The open PR #17389 proposes dropping to four parts automatically on slow links, but it is unmerged.
Is there a way to make the 30-second stall timeout longer?
No supported one. downloadStallTimeout is a package-level variable in server/download.go with no environment binding — it is a var rather than a const so tests can shorten it, not so users can raise it. When a chunk stalls, Ollama reopens it and decrements the retry counter, so stalls loop indefinitely rather than failing.
Why did my half-finished 40GB download disappear overnight?
Startup prune. Ollama removes files in blobs/ whose names do not parse as digests — which includes every -partial file — once they are more than an hour old, and it does this every time the server starts. Start the daemon with OLLAMA_NOPRUNE=1 when you intend to resume a stale download.
Is part stalled; retrying something I need to fix?
Not by itself. It is an INFO log meaning one chunk received nothing for 30 seconds and is being reopened, and the retry counter is decremented so it does not count against the six-attempt budget. It only matters if it repeats indefinitely, at which point the message's own advice applies: Ctrl-C, then pull again.
Sources
- ollama/ollama —
server/download.go(maxRetries,numDownloadParts, part sizing,downloadStallTimeout, the rollback branch, redirect handling, error variables) - ollama/ollama —
server/images.go(PruneLayers,layerPruneGracePeriod, thepull model manifestwrapper) - Ollama FAQ (model storage paths,
HTTPS_PROXYguidance, per-platform environment variables) - Issues: #17484 (context deadline exceeded, open), #17329 (progress resets on slow Wi-Fi, open), #15708 (no such host behind a proxy, open), #10151 (r2.cloudflarestorage.com DNS failure), #1036 (unexpected EOF on pull), #8167 (connection reset by peer), #2155 and #1736 (slow-connection behaviour)
- Pull requests: #17551 (closed unmerged) and #17389 (open)
Ollama’s running. Here’s what to build with it.
Go from “ollama run” to RAG apps, agents, and fine-tuned models — structured and hands-on. First chapter free.
Stop piecing Ollama together from blog posts
Ollama Mastery is 15 chapters end to end — install, model choice, Modelfiles, GPU offload, the API, and the 20 errors that actually happen. Plus 24 more courses.
Liked this? 20 full AI courses are waiting.
From fundamentals to RAG, agents, MCP servers, voice AI, and production deployment with real GitHub repos. First chapter free, every course.
Build Real AI on Your Machine
RAG, agents, NLP, vision, and MLOps - chapters across 25 courses that take you from reading about AI to building AI.
Want structured AI education?
25 courses, 519+ chapters, from $9. Understand AI, don't just use it.
Continue Your Local AI Journey
- PILLARBest Ollama Models 2026: 15 Ranked (Coding, Reasoning, Chat)
- AI on QNAP & TrueNAS: Run Ollama with GPU Passthrough
- AI on Steam Deck: Run Local LLMs with Ollama on SteamOS
- Best Free AI Models to Run Locally With Ollama, No API Key
- Best Local LLMs for Tool & Function Calling (2026 Tested)
- Best Ollama Embedding Models: We Benchmarked All 6 for RAG
- Best Ollama Models for 8GB RAM 2026: 12 Tested Local Picks
- Best Ollama Models for AI Agents 2026: 9 Tested & Ranked
- Best Uncensored Local LLMs: Abliterated Ollama Models
- Build a Local AI Slack & Discord Bot with Ollama + Python
Comments (0)
No comments yet. Be the first to share your thoughts!