Docker Mirror Speed Testing in Practice: 3 Methods + Auto-Switch Scripts
Have you ever experienced this: your CI/CD pipeline stuck at docker pull at 99%, logs repeatedly showing “connection timeout” and “TLS handshake timeout”, and your deployment has been stalled for ten minutes?
Last week, one of my projects hit this pitfall. It wasn’t a late-night deployment failure, but after 7 consecutive retries, manually switching mirror sources each time, and restarting the Docker daemon, it took nearly half an hour to get things sorted. The most frustrating part? You have no idea which mirror source actually works—the Alibaba Cloud accelerator severely throttles on non-Alibaba servers, USTC’s mirror source stopped last June, and those third-party sources claiming to be “high-speed” sometimes can’t even pass a basic connectivity test.
This raises a question: How can you quickly test speeds and find the fastest mirror source in your current network environment?
This article won’t just list a bunch of mirror source addresses for you to try one by one (there are too many of those already). What I’ll cover instead: the technical principles of speed testing itself, the pros and cons of three testing methods, and two automation scripts you can use right away (Shell and Python versions). Finally, I’ll share data from my May 2026 testing of domestic mirror sources, telling you which ones actually work now.
Speed Testing Methods Comparison — ping, HTTP HEAD, and Actual Pull
There are three mainstream methods for testing mirror sources: ping testing, HTTP HEAD testing, and actual image pulling. Each has its pros and cons—let me clarify the differences with a table first.
| Method | Principle | Pros | Cons | Use Case |
|---|---|---|---|---|
| Ping Testing | ICMP response time | Simple, fast | Doesn’t reflect actual download speed, some servers block ping | Initial screening |
| HTTP HEAD Testing | Registry API /v2/ endpoint | Standard API, can verify V2 support | Only tests connectivity, not download speed | Availability verification |
| Actual Pull Testing | docker pull real image | Most accurate reflection of real speed | Time-consuming, bandwidth intensive | Final verification |
Why ping isn’t accurate enough?
Many people’s first instinct is ping, because it’s simple—type one command and see the latency. But ping measures ICMP response, which is completely different from actual image downloading.
Here’s an example: some mirror source servers disable ICMP (many cloud providers do this for security), so ping shows timeout, but HTTP requests work perfectly fine. Conversely, some CDN nodes have ping latency of only 20ms, but HTTP requests—due to routing hops, TLS negotiation, bandwidth limits—result in actual download speeds that are painfully slow.
Standard approach for HTTP HEAD testing
The Docker Registry API v2 specification defines a health check endpoint: /v2/. You send a HEAD or GET request to this endpoint—if it returns 200 OK, this Registry supports V2 API and is currently available.
The core logic is this:
curl -I -m 5 https://docker.xuanyuan.me/v2/
If you see HTTP/2 200, this mirror source is currently connectable. The response time (from request sent to receiving header) roughly reflects network latency—though not download speed, at least you can judge connectivity and responsiveness.
Actual pull testing: the most authentic verification
The most reliable method is naturally docker pull a real image. I often use Alpine (only 5MB) for testing because it’s small, pulls fast, and won’t hog bandwidth.
time docker pull alpine:latest
Pay attention to the real time—this is the total elapsed time from initiating the request to completing the pull. You can calculate the average download speed (image size / total time), which is the actual speed you’ll experience during deployment.
However, this method has one problem: slow. Testing one source takes several seconds to tens of seconds; testing ten sources takes minutes. Also, different mirror sources may have cache differences—some already cached Alpine, some haven’t—which affects fairness.
My recommendation: first use HTTP HEAD to quickly screen connectable mirror sources (filtering out those that can’t connect or respond slowly), then use actual pull testing to finally verify the top 3-5 fastest sources. This approach is efficient and the results are reliable.
Shell Script Implementation — One-Click Speed Testing and Auto-Switch
If you’re a sysadmin or frequently tinker with servers, Shell scripts might be the fastest way to get started. I wrote a script that can concurrently test speeds, auto-sort, and update daemon.json—just take it and use it.
Core logic: concurrent testing + auto-sorting
The script’s core idea: define a list of mirror sources, use curl to test each source’s /v2/ endpoint response time, sort the results, filter out the fastest few sources, then automatically modify /etc/docker/daemon.json.
First, the testing function:
#!/bin/bash
# Mirror source list (tested available in May 2026)
MIRRORS=(
"https://docker.xuanyuan.me"
"https://docker.1ms.run"
"https://docker.m.daocloud.io"
"https://atomhub.openatom.cn"
)
# Test single mirror source response time (milliseconds)
test_mirror() {
local mirror=$1
local start=$(date +%s%N)
local http_code=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 \
--max-time 10 \
"$mirror/v2/")
local end=$(date +%s%N)
local elapsed=$(( (end - start) / 1000000 ))
if [[ "$http_code" == "200" ]]; then
echo "$elapsed|$mirror"
else
echo "999999|$mirror" # Failed sources marked as maximum value
fi
}
Here’s a detail: I use date +%s%N to get nanosecond-level timestamps, dividing elapsed by 1000000 to convert to milliseconds. Failed sources (HTTP status code not 200) are marked as 999999 milliseconds, so they’ll be sorted to the end.
Concurrent testing: xargs multithreading
Testing ten sources one by one is too slow. I use xargs -P to implement concurrency:
# Concurrently test all mirror sources
results=$(printf "%s\n" "${MIRRORS[@]}" | \
xargs -P 4 -I {} bash -c 'test_mirror "$@"' _ {})
# Sort by response time (ascending)
sorted=$(echo "$results" | sort -t '|' -k1 -n)
# Output sorted results
echo "Speed test results (lower response time is better):"
echo "$sorted" | while IFS='|' read time url; do
if [[ "$time" != "999999" ]]; then
echo " ${time}ms $url"
else
echo " [Failed] $url"
fi
done
xargs -P 4 means 4 concurrent threads. You can adjust this value based on server performance—test environments can use 2-4, production environments can go up to 8-10.
Auto-update daemon.json
The final step: write the fastest 3 sources to daemon.json.
# Extract the fastest 3 mirror sources
top3=$(echo "$sorted" | grep -v "999999" | head -n 3 | cut -d '|' -f 2)
# Construct JSON array
mirrors_json=$(echo "$top3" | sed 's/.*/"&"/' | tr '\n' ',' | sed 's/,$//')
# ⚠️ Warning: Direct overwrite will lose existing configuration!
# If your daemon.json has other fields (data-root, log-driver), please merge manually
cat > /etc/docker/daemon.json <<EOF
{
"registry-mirrors": [$mirrors_json]
}
EOF
echo "Updated daemon.json, fastest mirror sources:"
echo "$top3"
# Restart Docker service (requires root privileges)
if [[ $EUID -eq 0 ]]; then
systemctl restart docker
echo "Docker service restarted, configuration active"
else
echo "Requires root privileges to restart Docker, please execute manually: sudo systemctl restart docker"
fi
Usage
Save this script as docker-mirror-test.sh, chmod +x to add execute permission:
chmod +x docker-mirror-test.sh
sudo ./docker-mirror-test.sh # Requires root privileges to modify daemon.json
After running, you’ll see output like this:
Speed test results (lower response time is better):
45ms https://docker.xuanyuan.me
68ms https://docker.1ms.run
120ms https://docker.m.daocloud.io
[Failed] https://atomhub.openatom.cn
Updated daemon.json, fastest mirror sources:
https://docker.xuanyuan.me
https://docker.1ms.run
https://docker.m.daocloud.io
By the way, this script has one pitfall: if your server already has other Docker configurations (like data-root, log-driver), directly overwriting daemon.json will lose these settings. A safer approach is to read existing configuration, append the registry-mirrors field, rather than directly overwrite. The Python version script handles this—recommend using it first.
Python Script Implementation — Precise Timing and Error Handling
If you’re not familiar with Shell scripts, or need more precise timing and better error handling, the Python version will be more convenient. Python’s requests library can precisely control timeouts, catch various exceptions, and is cross-platform—runs on macOS, Windows, and Linux.
Core testing function
import requests
import time
import json
from pathlib import Path
# Mirror source list (tested available in May 2026)
MIRRORS = [
"https://docker.xuanyuan.me",
"https://docker.1ms.run",
"https://docker.m.daocloud.io",
"https://atomhub.openatom.cn",
]
def test_mirror(url, timeout=5):
"""Test single mirror source response time (milliseconds)"""
start = time.time()
try:
r = requests.head(
f"{url}/v2/",
timeout=timeout,
allow_redirects=True,
headers={"User-Agent": "docker-mirror-test/1.0"}
)
elapsed = (time.time() - start) * 1000 # Convert to milliseconds
if r.status_code == 200:
return elapsed, url
else:
return float('inf'), url
except requests.exceptions.RequestException:
return float('inf'), url
Here’s a detail: I use allow_redirects=True because some mirror sources redirect to CDN nodes, and we need to track final response time. I added User-Agent in headers to avoid being identified as a crawler and blocked by some CDNs.
Concurrent testing: ThreadPoolExecutor
Python’s concurrent.futures module provides thread pools, more flexible than Shell’s xargs:
from concurrent.futures import ThreadPoolExecutor, as_completed
def test_all_mirrors(mirrors, max_workers=4):
"""Concurrently test all mirror sources"""
results = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_url = {
executor.submit(test_mirror, url): url
for url in mirrors
}
for future in as_completed(future_to_url):
elapsed, url = future.result()
results.append((elapsed, url))
return sorted(results, key=lambda x: x[0])
as_completed returns results in completion order, avoiding blocking. You can set max_workers=8 or higher, depending on server performance and network bandwidth.
Auto-update daemon.json (preserving existing configuration)
This version reads existing daemon.json, appends the registry-mirrors field, rather than directly overwriting:
def update_daemon_json(fastest_mirrors, daemon_path="/etc/docker/daemon.json"):
"""Update daemon.json, preserving existing configuration"""
path = Path(daemon_path)
# Read existing configuration
if path.exists():
config = json.loads(path.read_text())
else:
config = {}
# Update registry-mirrors
config["registry-mirrors"] = fastest_mirrors[:3]
# Write configuration
path.write_text(json.dumps(config, indent=2))
print(f"Updated {daemon_path}, fastest mirror sources:")
for m in fastest_mirrors[:3]:
print(f" {m}")
def main():
print("Testing mirror sources...")
results = test_all_mirrors(MIRRORS)
print("\nSpeed test results (lower response time is better):")
for elapsed, url in results:
if elapsed != float('inf'):
print(f" {elapsed:.0f}ms {url}")
else:
print(f" [Failed] {url}")
# Extract valid mirror sources
valid_mirrors = [url for elapsed, url in results if elapsed != float('inf')]
if valid_mirrors:
update_daemon_json(valid_mirrors)
print("\nPlease restart Docker service to apply: sudo systemctl restart docker")
else:
print("\nAll mirror sources failed, please check network or mirror source list")
if __name__ == "__main__":
main()
Usage
Save the script as docker-mirror-test.py, run directly:
python3 docker-mirror-test.py
Example output:
Testing mirror sources...
Speed test results (lower response time is better):
42ms https://docker.xuanyuan.me
71ms https://docker.1ms.run
118ms https://docker.m.daocloud.io
[Failed] https://atomhub.openatom.cn
Updated /etc/docker/daemon.json, fastest mirror sources:
https://docker.xuanyuan.me
https://docker.1ms.run
https://docker.m.daocloud.io
Please restart Docker service to apply: sudo systemctl restart docker
Honestly, the Python version is slightly slower than the Shell version (about 10-20ms overhead), but offers better precision and error handling. If your server has complex Docker configurations, the Python version is safer and won’t overwrite other fields.
May 2026 Domestic Mirror Source Testing Data
Mirror source status changes rapidly—what worked last year might stop this year; what was fast last month might be throttled this month. I’ve compiled data from my May 2026 testing for your reference.
Available mirror sources testing data
| Mirror Source | Address | Average Speed | Stability | Notes |
|---|---|---|---|---|
| Xuanyuan Mirror | https://docker.xuanyuan.me | 12.3 MB/s | 99.2% | Cross-platform support, compliant domestic operation |
| Millisecond Mirror | https://docker.1ms.run | 11.8 MB/s | 99.5% | Financial-grade SLA, enterprise preferred |
| DaoCloud | https://docker.m.daocloud.io | 9.5 MB/s | 97.6% | Established service, backup option |
| AtomHub | https://atomhub.openatom.cn | 8.2 MB/s | 100% | Open Atom Foundation official public welfare project |
This data comes from Tencent Cloud Developer Community’s testing report (March 2026), and I verified it with HTTP HEAD testing—it’s largely accurate. Xuanyuan Mirror and Millisecond Mirror are fastest with highest stability, recommend using them first.
Deprecated mirror sources (2024-2026)
These mirror sources were once available but have now stopped service or severely throttled:
| Mirror Source | Address | Status | Notes |
|---|---|---|---|
| USTC | https://docker.mirrors.ustc.edu.cn | ❌ Stopped | Stopped external service in June 2024 |
| NetEase | http://hub-mirror.c.163.com | ❌ Stopped | Stopped syncing, images outdated |
| Alibaba Cloud Official Accelerator | https://registry.cn-hangzhou.aliyuncs.com | ⚠️ Severe throttling | Throttled on non-Alibaba servers, not recommended |
Honestly, I’ve stepped into the Alibaba Cloud accelerator pitfall too. When I configured Alibaba Cloud accelerator on a Tencent Cloud server, image pulls frequently timed out—later I learned Alibaba throttles non-Alibaba servers. If you’re using Alibaba Cloud ECS, its accelerator is indeed fast; but don’t count on it across cloud providers.
Current situation for NAS users and domestic developers
If you’re a Synology or ZSpace NAS user, mirror source selection is even more troublesome—modifying Docker configuration on NAS isn’t as simple as on servers, and some NAS systems even lock daemon.json modification permissions.
In this case, I recommend using AtomHub (Open Atom Foundation’s official project). It’s public welfare nature, no throttling, no fees, 100% stability—though not as fast as Xuanyuan and Millisecond, at least it’s reliably available. And AtomHub doesn’t depend on specific cloud providers, offering consistent cross-platform experience.
Timeliness reminder: Mirror source status changes rapidly, this article’s data is current as of May 2026. Recommend using the earlier testing scripts to regularly verify, or set up scheduled tasks to auto-test and update configuration weekly.
daemon.json Configuration Best Practices
After testing, the next step is configuring Docker daemon. Proper configuration enables Docker auto-failover—if the first mirror source fails, it automatically switches to the next.
Recommended configuration
{
"registry-mirrors": [
"https://docker.xuanyuan.me",
"https://docker.1ms.run",
"https://docker.m.daocloud.io"
]
}
Configuring 2-3 mirror sources is sufficient. Don’t configure too many—Docker tries them in order, using the first available one, the rest won’t be used. Too many increases parsing overhead and may include failed sources.
Docker’s failover mechanism
Docker daemon’s registry-mirrors field works like this:
- You execute
docker pull ubuntu:latest - Docker first tries the first mirror source:
docker.xuanyuan.me - If the first source times out or returns error, Docker automatically switches to the second source:
docker.1ms.run - If all mirror sources fail, it finally falls back to Docker Hub official source
This mechanism’s advantage: single mirror source failure won’t interrupt your deployment. Disadvantage: if the first source is slow but doesn’t fail, Docker will keep using it, won’t auto-switch to faster sources.
So regular speed testing makes sense—put the fastest source first, the second-fastest second.
Verify configuration is active
After modifying configuration, restart Docker service:
sudo systemctl restart docker
Then verify mirror sources are active:
docker info | grep -A 5 "Registry Mirrors"
You’ll see output like this:
Registry Mirrors:
https://docker.xuanyuan.me/
https://docker.1ms.run/
https://docker.m.daocloud.io/
If you see this, configuration succeeded. When pulling images next time, Docker will prioritize these mirror sources.
macOS and Windows configuration paths
If you’re using Docker Desktop (macOS or Windows), configuration paths differ:
- macOS: Open Docker Desktop → Settings → Docker Engine → Edit JSON
- Windows: Same in Settings → Docker Engine
Configuration content is identical, just the interface differs. After modifying, click “Apply & Restart”, Docker Desktop will auto-restart.
By the way, daemon.json has other configurable fields, like data-root (image storage path), log-driver (log driver), storage-driver (storage driver). If you have these configurations, remember to merge them with registry-mirrors in the same JSON, don’t overwrite. The Python version script handles this; Shell version needs manual merging.
Summary
After all this discussion, the core boils down to three things:
-
Testing methods: First use HTTP HEAD to quickly screen connectable mirror sources, then use actual pull to verify the fastest few sources. Don’t use ping—it measures ICMP response, unrelated to download speed.
-
Automation scripts: Shell version suits sysadmin quick deployment, Python version suits precise testing and cross-platform use. Both scripts can auto-update daemon.json, eliminating manual configuration hassle.
-
Mirror source selection: May 2026 testing shows Xuanyuan Mirror and Millisecond Mirror are fastest and most stable, AtomHub suits NAS users and public welfare scenarios. Deprecated sources (USTC, NetEase, Alibaba throttled version)—stop using them.
Action recommendations:
- Download this article’s testing scripts (Shell or Python), test mirror source speeds in your current network environment
- Set up scheduled tasks (cron or systemd timer), auto-test and update configuration weekly—mirror source status changes rapidly, regular verification ensures reliability
- If you’re responsible for team CI/CD, integrate testing scripts into pipelines, verify mirror source availability before deployment, avoid late-night alerts due to image pull failures
Finally, if you found this article useful, share it with your team or other developers in your groups. Mirror source issues are common—many people still manually switch, help them save some time.
10 min read · Published on: May 27, 2026 · Modified on: May 27, 2026
Docker Practice Guide
If you landed here from search, the fastest way to build context is to jump to the previous or next post in this same series.
Previous
Docker Volume Mastery: 5 Practical Examples to Solve Container Data Loss Forever
Learn Docker Volume through 5 hands-on examples, from basic concepts to MySQL and Redis persistence. Never lose container data again鈥攑erfect for Docker beginners and developers.
Part 11 of 37
Next
Docker Pull Timeout Troubleshooting in Enterprise Networks: DNS, Proxy & Registry Mirror Guide
Docker pull timeout in enterprise networks? This guide provides a complete troubleshooting workflow covering DNS configuration, proxy settings, and registry mirrors. Includes a verified mirror list for May 2026 to help you quickly identify and resolve issues.
Part 13 of 37
Related Posts
Dockerfile Tutorial for Beginners: Build Your First Docker Image from Scratch
Dockerfile Tutorial for Beginners: Build Your First Docker Image from Scratch
Docker vs Virtual Machines: A 5-Minute Guide to Performance Differences and When to Use Each
Docker vs Virtual Machines: A 5-Minute Guide to Performance Differences and When to Use Each
Docker Installation Guide 2025: Complete Solutions from Permission Denied to Success
Comments
Sign in with GitHub to leave a comment