This error occurs when Docker attempts to connect to a registry over HTTPS, but the registry is only serving plain HTTP. Docker defaults to secure HTTPS connections for all registries, so you must explicitly configure it to allow insecure HTTP connections to specific registries.
The "http: server gave HTTP response to HTTPS client" error appears when Docker's client tries to establish a secure TLS/HTTPS connection to a container registry, but the registry responds with unencrypted HTTP instead. Docker, by design, assumes all registries are secure and will only communicate over HTTPS unless explicitly configured otherwise. This commonly happens in several scenarios: - **Private registries** running without TLS certificates configured - **Development environments** where setting up certificates is unnecessary - **Local registries** running on localhost or internal networks - **Air-gapped environments** where external CA verification isn't possible Starting with Docker 1.3.2, the client enforces HTTPS for all registry communications except localhost. When the registry only supports HTTP, Docker receives an HTTP response when expecting HTTPS, causing this protocol mismatch error. The daemon requires explicit configuration via the `insecure-registries` option to allow HTTP connections.
The most common fix is to explicitly tell Docker to allow HTTP connections to your registry by adding it to the insecure-registries list:
# Create or edit the Docker daemon configuration
sudo nano /etc/docker/daemon.jsonAdd your registry to the insecure-registries array:
{
"insecure-registries": ["myregistry:5000"]
}For multiple registries or CIDR ranges:
{
"insecure-registries": [
"myregistry:5000",
"192.168.1.100:5000",
"10.0.0.0/8"
]
}Then restart Docker to apply the changes:
sudo systemctl restart docker
# Verify the configuration was applied
docker info | grep -A 10 "Insecure Registries"Note: The registry address must exactly match how you reference it in Docker commands, including the port number.
For Docker Desktop, configure insecure registries through the GUI:
Docker Desktop on Windows/macOS:
1. Click the Docker icon in the system tray/menu bar
2. Select Settings (or Preferences on macOS)
3. Go to Docker Engine tab
4. Add your registry to the JSON configuration:
{
"insecure-registries": ["myregistry:5000"]
}5. Click Apply & Restart
Alternative on Windows (daemon.json location):
C:\ProgramData\docker\config\daemon.jsonVerify configuration:
docker info | Select-String "Insecure"Starting with Docker Engine v23.0, BuildKit became the default builder and it ignores the insecure-registries setting in daemon.json. Use one of these workarounds:
Option 1: Disable BuildKit temporarily
DOCKER_BUILDKIT=0 docker build -t myregistry:5000/myimage .Option 2: Use BUILDKIT_NO_CLIENT_TOKEN
BUILDKIT_NO_CLIENT_TOKEN=true docker build -t myregistry:5000/myimage .Option 3: Configure buildx with insecure registry
# Create a new builder with host network access
docker buildx create --name mybuilder --driver-opt network=host --use
# Build with --allow option
docker buildx build --allow insecure --push -t myregistry:5000/myimage .Option 4: Configure buildx with registry config
Create ~/.docker/buildx/buildkitd.toml:
[registry."myregistry:5000"]
http = true
insecure = trueAfter making configuration changes, you must restart Docker and verify the settings took effect:
# Restart Docker daemon
sudo systemctl daemon-reload
sudo systemctl restart docker
# Verify Docker is running
sudo systemctl status docker
# Check insecure registries are configured
docker info 2>/dev/null | grep -A 5 "Insecure Registries"Expected output should show your registry:
Insecure Registries:
myregistry:5000
127.0.0.0/8If your registry doesn't appear, check the daemon.json syntax:
# Validate JSON syntax
cat /etc/docker/daemon.json | python3 -m json.tool
# Check Docker logs for configuration errors
sudo journalctl -u docker.service --since "5 minutes ago"After configuration, test that Docker can connect to your registry:
# Test pull from registry
docker pull myregistry:5000/test-image:latest
# Or test login if authentication is required
docker login myregistry:5000
# Test push capability
docker tag alpine:latest myregistry:5000/alpine:test
docker push myregistry:5000/alpine:testIf still failing, test the registry directly:
# Check if registry is reachable and responding
curl -v http://myregistry:5000/v2/
# Expected response for healthy registry:
# HTTP/1.1 200 OK
# {}If curl works but Docker doesn't, ensure the registry address in daemon.json exactly matches your Docker commands.
For production environments, the recommended solution is to configure TLS on your registry rather than using insecure connections:
Option 1: Add SSL certificates to Docker Registry
# docker-compose.yml for registry with TLS
version: '3'
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/registry.crt
REGISTRY_HTTP_TLS_KEY: /certs/registry.key
volumes:
- ./certs:/certs
- registry-data:/var/lib/registry
volumes:
registry-data:Option 2: Use Let's Encrypt with Traefik/nginx
Place a reverse proxy in front of your registry that handles TLS termination.
Option 3: Add self-signed cert to Docker
If using self-signed certificates, add them to Docker's trust store:
sudo mkdir -p /etc/docker/certs.d/myregistry:5000
sudo cp ca.crt /etc/docker/certs.d/myregistry:5000/ca.crt
sudo systemctl restart dockerThis is more secure than marking the registry as insecure, as it still validates the certificate.
Understanding the security implications:
Using insecure-registries disables TLS verification entirely for the specified registries. This means:
- No encryption of data in transit (images, credentials)
- No verification of registry identity (vulnerable to man-in-the-middle attacks)
- Should never be used in production or over untrusted networks
CIDR notation for multiple hosts:
You can configure entire subnets as insecure:
{
"insecure-registries": ["10.0.0.0/8", "192.168.0.0/16"]
}Linux service file override (alternative method):
If daemon.json doesn't work, modify the Docker service:
sudo systemctl edit docker.serviceAdd:
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --insecure-registry myregistry:5000Then reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart dockerSSH tunnel workaround:
Docker allows HTTP traffic to localhost. You can tunnel to your registry:
ssh -L 5000:myregistry:5000 user@jump-host
docker pull localhost:5000/myimagecontainerd/CRI-O configuration:
For Kubernetes nodes using containerd:
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".registry.configs."myregistry:5000".tls]
insecure_skip_verify = trueDebugging DOCKER_TLS_VERIFY:
Check if environment variables are overriding your configuration:
env | grep -i docker
# If DOCKER_TLS_VERIFY=1 is set, unset it:
unset DOCKER_TLS_VERIFYimage operating system "linux" cannot be used on this platform
How to fix 'image operating system linux cannot be used on this platform' in Docker
manifest unknown: manifest unknown
How to fix 'manifest unknown' in Docker
cannot open '/etc/passwd': Permission denied
How to fix 'cannot open: Permission denied' in Docker
Error response from daemon: failed to create the ipvlan port
How to fix 'failed to create the ipvlan port' in Docker
toomanyrequests: Rate exceeded for anonymous users
How to fix 'Rate exceeded for anonymous users' in Docker Hub