Docker-in-Docker jobs on GitLab.com hosted runners fail to reach NuGet API due to MTU mismatch
Description
CI/CD jobs that use Docker-in-Docker (docker:dind) on GitLab.com hosted runners (saas-linux-small-amd64) fail during dotnet restore, dotnet tool restore or nuget restore with:
error NU1301: Unable to load the service index for source https://api.nuget.org/v3/index.jsonThe HTTP request to 'GET https://api.nuget.org/v3/index.json' has timed out after 100000ms
The connection silently hangs rather than failing fast, and the job eventually times out. This is caused by an MTU mismatch between the inner Docker daemon and the outer runner network.
GitLab has set the outer Docker daemon MTU to 1360 on all saas-linux-small-amd64 runner shards. Standard jobs (without dind) inherit this automatically. However, the inner Docker daemon in a docker:dind service container defaults to MTU 1500, creating a mismatch that causes packets larger than 1360 bytes to be dropped silently.
Impacted Offerings
- GitLab.com
Impacted environments
This affects jobs running on saas-linux-small-amd64 GitLab-hosted runners using docker:dind. The issue was first observed in May 2026 after NuGet migrated api.nuget.org behind Microsoft Front Door.
Solution
Option 1: Set the MTU on the dind service (recommended)
Pass --mtu=1360 to the inner Docker daemon via the command directive in .gitlab-ci.yml:
services:
- name: docker:dind
command: ["--mtu=1360"]
Option 2: Enable per-job networking
Set the FF_NETWORK_PER_BUILD feature flag. This causes the runner to create an isolated bridge network per job instead of using the default bridge, which avoids the specific MTU inheritance path involved in this issue:
variables:
FF_NETWORK_PER_BUILD: "true"
See Create a network for each job for details.
Option 3: Remove custom MTU overrides above 1360
If your pipeline already sets a custom MTU on the dind service (for example --mtu=1400), lower it to 1360 or remove the override entirely.
Cause
An upstream Path MTU black hole was introduced when NuGet migrated api.nuget.org behind Microsoft Front Door. The new path has an effective MTU lower than 1500 bytes, and ICMP "fragmentation needed" messages are filtered along the way. Oversized packets are dropped silently, causing TCP connections to hang until they time out.
GitLab resolved this for standard jobs by lowering the runner Docker daemon MTU to 1360. With docker:dind, there are two stacked Docker daemons:
- The outer daemon on the runner VM creates its
docker0bridge at MTU 1360 and attaches the dind container'seth0at 1360. - The inner daemon inside the dind container defaults to MTU 1500. Build containers attached to this inner bridge also get MTU 1500.
A 1500-byte frame from a build container passes through the inner bridge but is dropped when it reaches the dind container's eth0 (MTU 1360):
build container (MTU 1500)
→ inner docker0 (MTU 1500)
→ dind eth0 (MTU 1360) ← packets > 1360 dropped here
→ outer docker0 (MTU 1360)
→ host eth0 (MTU 1460)
→ internet → Microsoft Front Door (api.nuget.org)
Additional Information
Standard CI/CD jobs that do not use docker:dind and do not set a custom MTU override above 1360 are unaffected and require no action.
GitLab Self-Managed and GitLab Dedicated runners are not affected by this specific infrastructure change. However, if runners on those platforms reach the same upstream MTU black hole, the same symptoms will occur. In that case, set "mtu": 1360 in the Docker daemon configuration (/etc/docker/daemon.json) on the runner host, and apply the dind workaround above for dind jobs.
While this issue was observed with api.nuget.org after NuGet migrated behind Microsoft Front Door, the same MTU mismatch could affect connections to any external endpoint behind infrastructure that filters ICMP "fragmentation needed" messages.