Error message
http: server gave HTTP response to HTTPS client
Programs add their own prefix: Docker prints "Error response from daemon: Get \"https://<registry>/v2/\": http: server gave HTTP response to HTTPS client", Go programs "Get \"https://…\": http: server gave …". When the reply is not HTTP either, Go says "tls: first record does not look like a TLS handshake".
Go net/http — the TLS handshake received a plain HTTP response (also printed by Docker, Helm, Terraform)
What it means
Go's HTTP client started a TLS handshake, and the server answered with a plain-text HTTP response; Go recognises the "HTTP/" bytes and reports that the server speaks HTTP, not HTTPS. The most famous case is docker pull or docker login against a self-hosted registry running without TLS: Docker requires HTTPS for every registry that is not on a loopback address or explicitly marked insecure.
Why it happens
A container registry without TLS
commonregistry:2 (distribution), Harbor or Nexus started on plain HTTP for testing. Docker, containerd, Podman, Helm and Skopeo expect HTTPS for anything that is not localhost.
https:// with the HTTP port
commonA URL or config that combines https:// with port 80, 8080 or an internal service port.
TLS is terminated by a proxy on another port
occasionalThe backend speaks HTTP behind a load balancer; clients that bypass the balancer and hit the backend port with https:// get this error.
How to fix it
- 1.
Confirm the port speaks plain HTTP
An HTTP status line from http:// confirms it. Use the TLS endpoint (or http:// where that is acceptable).
bash curl -si http://registry.example.com:5000/v2/ | head -1 # answers over plain HTTP? - 2.
Serve the registry over TLS
Give the registry a certificate (public ACME or company CA) and configure it, or put a TLS-terminating proxy in front.
yaml # Docker registry (distribution) config.yml: serve TLS http: addr: :5000 tls: certificate: /certs/registry.example.com.fullchain.pem key: /certs/registry.example.com.key - 3.
Last resort: mark the registry as insecure (daemon.json)
Listing it under insecure-registries in /etc/docker/daemon.json (and restarting dockerd) lets Docker talk plain HTTP to it. Images and credentials then travel unencrypted and unauthenticated; use it only on isolated lab networks.
json { "insecure-registries": ["registry.example.com:5000"] }