Skip to content

Instantly share code, notes, and snippets.

@abdennour
Last active March 6, 2026 18:39
Show Gist options
  • Select an option

  • Save abdennour/74c5de79e57a47f3351217d674238da8 to your computer and use it in GitHub Desktop.

Select an option

Save abdennour/74c5de79e57a47f3351217d674238da8 to your computer and use it in GitHub Desktop.
Nginx Reverse Proxy for Nexus Docker Registries

Overview

This is a solution of a common problem with Nexus Docker repositories. The administrator has to expose port for "pull", another port for "push", other ports for each hosted repository. This solution is about leveraging Nginx reverse proxy to avoid using these ports.

How it works ?

Given :

  • Nexus hostname is "nexus.example.com"
  • Nexus web port is 8081
  • A hosted repository is named "docker-hosted"
  • A group repository is named "docker-group"
  • Your nginx (with the nginx.conf of this gist) will run for example under cregistry.example.com

The following Nginx configuration file is for a reverse proxy without the need to expose connector ports from nexus :

  • docker pull cregistry.example.com/myimage lets Nginx forward the request to "docker-group"
  • docker push cregistry.example.com/myimage lets Nginx forward the request to "docker-hosted"

Notes

  • If you have more than one hosted repository, create another Nginx reverse proxy for it, then aggregate them using a parent Nginx reverse proxy that forwards the request according to certain criteria (.i.e: Host header).

  • All Nexus repositories must have consistent configuration of authentication: Either all require authentication, or all don't.

  • If TLS is enabled with Nexus, change proxy_set_header X-Forwarded-Proto "http"; by proxy_set_header X-Forwarded-Proto "https";

version: "3"
services:
web:
image: nginx:1.15
hostname: cregistry.example.com
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
ports:
- "80:80"
nexus:
image: sonatype/nexus3
hostname: nexus.example.com
volumes:
- "nexus-data:/nexus-data"
ports:
- "8081:8081"
volumes:
nexus-data: {}
events {
worker_connections 1024;
}
http {
proxy_send_timeout 120;
proxy_read_timeout 300;
proxy_buffering off;
keepalive_timeout 5 5;
tcp_nodelay on;
# disable any limits to avoid HTTP 413 for large image uploads
client_max_body_size 0;
server {
listen *:80;
location ~ ^/(v1|v2)/[^/]+/?[^/]+/blobs/ {
if ($request_method ~* (POST|PUT|DELETE|PATCH|HEAD) ) {
rewrite ^/(.*)$ /repository/docker-hosted/$1 last;
}
rewrite ^/(.*)$ /repository/docker-group/$1 last;
}
location ~ ^/(v1|v2)/ {
if ($request_method ~* (POST|PUT|DELETE|PATCH) ) {
rewrite ^/(.*)$ /repository/docker-hosted/$1 last;
}
rewrite ^/(.*)$ /repository/docker-group/$1 last;
}
location / {
proxy_pass http://nexus.example.com:8081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto "http";
}
}
}
@dertdu
Copy link

dertdu commented Mar 6, 2026

nginx.ingress.kubernetes.io/server-snippet: |
  location ~ ^/v2/ {
    set $backend_url "";

    rewrite_by_lua_block {
      local uri = ngx.var.uri
      local upstream = "http://internalnexus.svc.k8s.local:8081"
      local repo_name

      if uri == "/v2/" then
        local path = uri:gsub("^/v2/", "/repository/docker-public/v2/")
        backend = upstream .. path
        repo_name = "docker-public"
        ngx.log(ngx.ERR, " Routed to Docker Login (public): ", backend)

      elseif uri:find("^/v2/private/") then
        local path = uri:gsub("^/v2/private/", "/repository/docker-private/v2/")
        backend = upstream .. path
        ngx.log(ngx.ERR, "Routed to Docker Private: ", backend)
        repo_name = "private"

      elseif uri:find("^/v2/autotest/") then
        local path = uri:gsub("^/v2/autotest/", "/repository/docker-autotest/v2/")
        backend = upstream .. path
        repo_name = "autotest"

      elseif uri:find("^/v2/") then
        local path = uri:gsub("^/v2/", "/repository/docker-public/v2/")
        backend = upstream .. path
        ngx.log(ngx.ERR, "Routed to Docker Public: ", backend)

      else
        backend = upstream .. uri
        ngx.log(ngx.ERR, "Fallback backend: ", backend)
      end

      ngx.log(ngx.ERR, "Proxying to: ", backend)
      ngx.var.backend_url = backend
      ngx.ctx.repo_name = repo_name
    }

    header_filter_by_lua_block {
      local location = ngx.header["Location"]
      local repo_name = ngx.ctx.repo_name

      if location and repo_name and repo_name ~= "public" then
        ngx.log(ngx.ERR, "Currnet Location", location)

        newLocation = location:gsub("^(/v2/)", "/v2/" .. repo_name .. "/")

        ngx.log(ngx.ERR, "NEW Location", newLocation)
        ngx.header["Location"] = newLocation
        ngx.log(ngx.ERR, "Rewritten Location: ", ngx.header["Location"])
      end
    }

    proxy_pass $backend_url$is_args$args;

    client_max_body_size 5G;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }

k8s ingress

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment