Private docker registry not validating JWT tokens

I’m having an issue setting up the authentication for my private docker registry. I’ve been looking around for quite a while, but I’m still not sure about what exactly is causing this issue.

Setup:

  • A docker registry 2 container
  • Nginx reverse proxy
  • FastAPI service that receives authentication tokens, validates them against a third party server, then issues a JWT token if the credentials are correct

Issue:

The authentication flow is mostly OK. The Auth service gets the request, validates the key, then issues the token only if it’s correct. But then, the registry does not accept the token.

time="2026-05-22T09:50:49.446097773Z" level=info msg="unable to get token signing key" 
time="2026-05-22T09:50:49.446139019Z" level=warning msg="error authorizing context: invalid token"

I don’t get more useful information with debug logging active.

To figure out what exactly was wrong with the token, I wrote a python script that makes the same validation. This script tells me that the audience doesn’t match. However, the REGISTRY_AUTH_TOKEN_SERVICE system variable in the registry matches the aud field in the token exactly, and this is the same as the CN field in the certificate being used to validate the token.

As a second verification I’ve uploaded the token and certificate to https://www.jwt.io/ , and in that tester I get no errors at all (Token is valid, all fields match, etc).

I have a few specific questions that may help, because right now I’m at a loss about what is going on in here.

  1. Does the docker registry use pyjwt for validation? Could the failure in my testing script and the error in the registry be related?
  2. Is there any way of making the registry output more useful messages, other than the debug log level?
  3. Is there anything I may have misconfigured that is not returning an error by itself, nor giving a proper warning?
  4. What would be the suggested next steps for debugging? I’m assuming the registry works for everyone else and that the problem is in my authentication API, but the implementation matches the requirements exactly and I don’t really have that many fields to change for a trial and error approach.

Here is the (anonymized) configuration file

services:
  registry:
    image: registry:2
    container_name: docker-registry
    restart: unless-stopped
    ports:
      - "127.0.0.1:4000:5000"
    environment:
      REGISTRY_HTTP_SECRET: <secret>
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
      REGISTRY_HTTP_ADDR: 0.0.0.0:5000
      REGISTRY_AUTH: token      
      REGISTRY_AUTH_TOKEN_REALM: https://registry.<domain>.com/auth
      REGISTRY_AUTH_TOKEN_SERVICE: registry.<domain>.com
      REGISTRY_AUTH_TOKEN_ISSUER: <domain>
      REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE: /certs/cert.pem
    volumes:
      - ./data:/data
      - ./auth-cert.cert:/certs/cert.pem:ro
      - ./config.yml:/etc/docker/registry/config.yml:ro
    network_mode: "host"
    extra_hosts:
      - "registry.aliasrobotics.com:127.0.0.1"

And a partial copy of the script that is generating the token:

# Create token
    exp = datetime.utcnow() + timedelta(hours=1)
    now = int(time.time())
    claims = {
      "iss": <domain>,
      "sub": user,
      "aud": "registry.<domain>.com",
      "iat": now,
      "nbf": now,
      "exp": now + 3600,
    }

    if scope:
      # Parse docker scope
      typ, name, actions = scope.split(":")

      claims["access"] = [{
        "type": typ,
        "name": name,
        "actions": actions.split(",")
      }]

    token = jwt.encode(
        claims,
        PRIVKEY,
        algorithm="RS256",
    )

    return {"token":token}

I’ve checked the reverse proxy configuration and tested it just in case it was stripping headers or modifying the token in any way, but as far as I’ve tested it the reverse proxy is completely transparent for the process.

Any help appreciated. Thanks!