Safeguard
Developer Tools•

How do you put a Docker registry behind a load balancer safely?

Share storage config and the HTTP secret, forward Host and X-Forwarded headers, and treat a 401 from /v2/ as healthy so auth failures are not hidden.

Safeguard Security Team
3 min read

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/MeaningHealth check
401 with challengeAuth workingHealthy
200Auth possibly offDown
5xx or timeoutRegistry problemDown

A short checklist

  1. Same config, secret and storage on every node.
  2. TLS terminated where you can verify it, with headers forwarded.
  3. Health check expects 401.
  4. Access logs on the balancer and on each node, with the same time source.
  5. 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.