To run a Docker registry behind a load balancer, give every instance the same storage driver configuration and HTTP secret, forward the Host and X-Forwarded headers with client side values, and configure health checks to treat a 401 from /v2/ as healthy. Anything else should count as down.
What must match across instances
The deployment guide lists the shared pieces. Every instance behind the balancer needs:
- the same storage driver configuration,
- the same HTTP secret, which coordinates uploads across nodes,
- the same Redis cache, if you use one.
A mismatched secret shows up as odd upload failures that appear only sometimes, depending on which node a request lands on. That makes it painful to debug, so check it first.
Storage decides how many replicas you can run
With a distributed backend such as S3, you can run several replicas safely. With local bind mounts or volumes, each node holds its own data. The guide advises a single replica with a node constraint in that case, to avoid divergent data sets. Two nodes with separate disks look like one registry but return different images on different pulls.
Headers
The registry builds redirect URLs and decides whether a request arrived over TLS from the headers it receives. The guide says to forward Host, X-Forwarded-Proto and X-Forwarded-For with their client side values. If you do not, the registry may redirect clients to internal hostnames or downgrade to HTTP.
Both outcomes hurt. A redirect to an internal name breaks pulls from outside. A downgrade to HTTP sends layers, and possibly credentials, in clear text on the last hop or to the client.
Also remember who is allowed to set those headers. The balancer should overwrite them, not pass along whatever a client sent, otherwise clients can lie about their address in your logs.
The health check trap
An unauthenticated request to /v2/ should return 401 with a WWW-Authenticate challenge when authentication is on. The guide says to configure the health check to treat 401 as healthy and anything else as down.
This is a neat trick. If someone breaks authentication, the endpoint starts returning 200, and the health check fails. The misconfiguration gets noticed immediately instead of sitting open. If your balancer cannot do status matching that way, the guide says / returns 200 OK and can serve as a fallback, though you lose the auth signal.
| Response from /v2/ | Meaning | Health check |
|---|---|---|
| 401 with challenge | Auth working | Healthy |
| 200 | Auth possibly off | Down |
| 5xx or timeout | Registry problem | Down |
A short checklist
- Same config, secret and storage on every node.
- TLS terminated where you can verify it, with headers forwarded.
- Health check expects
401. - Access logs on the balancer and on each node, with the same time source.
- Test from outside: anonymous request returns
401, wrong password fails.
If you want to inventory what the registry serves, Safeguard supports container image scanning and SBOM generation, and the free scan is at gold.safeguard.sh/scan.
Quick check
Why is a 401 a healthy response?
It proves the registry is up and authentication is enforced. A 200 on an unauthenticated call would suggest auth is off.
Can I run two replicas with local volumes?
The guide advises against it, since each node would hold its own data. Use shared object storage or a single replica.
Sources
Last reviewed: 19 October 2026.