Install Nexus Repository Manager behind NGINX Reverse Proxy using Docker

Bilal Jammal
8 min readAug 10, 2020

--

What is Nexus?

Nexus is a software component management system developed by sonatype. It allows to manage different repositories to store builds, binaries, and other artifacts. It has a wide support of the most popular build tools like maven, npm, helm, docker, apt and many others. It also supports different IDEs and configuration management tools.

TL;DR This post is a step-by-step tutorial on how to install Nexus Repository Manager OSS (the free version) and how to secure it with HTTPs using NGINX reverse proxy. Finally, it shows how to create and host a private docker registry in Nexus and the extra step that should be taken during the installation.

Install Nexus

Nexus installation can be cumbersome, but with docker it is fairly easy. The only thing to take into consideration is how you want to achieve persistent storage. The Persistent Data section of Nexus3 docker hub image page explains the two possible options in a clear manner:

  1. Use Docker Volume
  2. Mount a host directory as a volume

I opted for option 2 because it can be easier to move data around hosts.

#create the directory for storing nexus data
$ mkdir /some/dir/nexus-data && chown -R 200 /some/dir/nexus-data
#start the container and mount the directory
#this will download the image if it doesn't already exist
$ docker run -d -p 8081:8081 --name nexus -v /some/dir/nexus-data:/nexus-data sonatype/nexus3

If you are to open your web browser and point it to the Nexus IP address on port 8081 you will see the Home page, but the communication between your browser and Nexus happens over HTTP. If it is enough for you, then congratulations you are done! But, if you need a secure communication with the Nexus server, the rest of this post is about configuring HTTPs access.

Secure Nexus with TLS

Nexus’ documentation says that secure communication with SSL/TLS can be inbound or outbound.

Outbound client communication may include integration with:

  • a remote proxy repository over HTTPS
  • SSL/TLS secured servers e.g. SMTP
  • LDAP servers configured to use LDAPS

Inbound client communication includes:

  • web browser HTTPS access to the user interface
  • tool access to repository content
  • and manual or scripted usage of the REST APIs

To secure inbound client traffic with TLS so that accessing the Nexus user interface is only available over HTTPs, there are two options: using a reverse proxy server, or serving HTTPs directly on Nexus. The latter method requires the modification of several file and restarting the Nexus server. I find that using a reverse proxy is much easier because configuring it requires only one file probably, and debugging it if it fails to restart would be easier and less worrying as the data is stored on Nexus.

Several reverse proxy servers are publicly available like Apache httpd and NGINX. I personally find NGINX more intuitive to configure, but you can go with httpd or any software you are familiar with.

The process of setting up the reverse proxy is the following:

  1. Obtain an SSL certificate fro a Certificate Authority (CA), or generate a self-signed one.
  2. Create/modify the reverse proxy configuration file.
  3. Start/restart the reverse proxy server.

Generating a self-signed Certificate

If this is a production setup, you should obtain a trusted certificate from a Certificate Authority. Otherwise, your best option is to use a self-signed certificate.

Let’s say that you want to store the certificate and private key in /etc/nginx/ssh/, to generate a certificate and a private keyyou can use the following command:

# create the directory and move into 
$ mkdir -p /etc/nginx/ssl && cd /etc/nginx/ssl
# generate a certificate and a private key
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nexus3.key -out nexus3.crt

If you reference the Reverse Proxy server using a DNS name, the generated certificate is sufficient. However, if you have multiple DNS names for the Reverse Proxy server, or you use the IP address instead of a DNS name, some clients may reject the certificate. For instance, the docker client daemon will not accept the certificate if you try to login to the registry.

$ docker login 192.168.0.17
Username: bilal
Password:
Error response from daemon: Get https://192.168.0.17/v2/: x509: cannot validate certificate for 192.168.0.17 because it does not contain any IP SANs

In this case, the certificate needs to be generated with Subject Alternative Name (SAN) extension.

The values of the different fields of this extension can be supplied through a configuration file. To save me from going through an interactive prompt I supply all the certificate values in a file that I will name req.conf in this example and which will look like the following:

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = country
ST = state name
L = Locality name
O = Organisation Name
OU = organisation unit
CN = common name
emailAddress = xxxxxxx@domain.com
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = IP:192.168.0.17

The important field here is the subjectAltName where I specify the IP address or additional DNS names of the server for which I am creating the certificate (This is the IP of the reverse proxy server, which happens to be the same server where the Nexus container runs in my case).

Next, generate the certificate by passing the configuration file to the openssl command as follows:

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nexus3.key -out nexus3.crt -config req.conf -extensions 'v3_req'

Notice the additional -config and -extensions parameters where we pass the name of the config file and the name of the section containing the SubjAltName field respectively.

Verify the Certificate:

$ openssl x509 -in nexus3.crt -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
64:c1:ff...
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = , ST = , L = , O = , OU = , CN = , emailAddress =
Validity
Not Before: Jul 21 10:47:03 2020 GMT
Not After : Jul 21 10:47:03 2021 GMT
Subject: C = , ST = , L = , O = , OU = , CN = , emailAddress =
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:de:53:d0...
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage:
Key Encipherment, Data Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Subject Alternative Name:
IP Address:192.168.0.17
Signature Algorithm: sha256WithRSAEncryption
3c:d8:59:a1...

Some output of my certificate is omitted for security reasons.

Once you have the certificate and private key, you can create an NGINX configuration file to terminate the SSL session and forward traffic in plain HTTP to Nexus. Here is how my /etc/nginx/conf.d/nexus3.conf looks like:

server {
listen 8080 default_server;
server_name 192.168.0.17;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name 192.168.0.17;
# Allow upload of large files,
# needed if Nexus is going to host large size artifacts
client_max_body_size 20G;
# Add the certificate and key generated earlier
ssl on;
ssl_certificate /etc/nginx/ssl/common-name.crt;
ssl_certificate_key /etc/nginx/ssl/common-name.key;
location / {
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 "https";
proxy_pass http://172.17.0.2:8081;
}
location /v2/ {
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 "https";
proxy_pass http://172.17.0.2:8082;
}
}

In this configuration, the first server block makes NGINX listen to HTTP requests on port 8080 and redirects it to HTTPs. The second server block declares a server on port 443 to listen to HTTPs traffic, it points to the certificate and key that the TLS endpoint must use, and it configures two URL locations:

  • The root ‘/’ corresponds to the Nexus3 UI which listens on TCP port 8081.
  • The ‘/v2/’ corresponds to the docker registry path and forwards traffic that comes from the docker command line client (docker login, pull or push) to the registry on port 8082. This is the port I will choose in the next step when creating a hosted docker repository using the Nexus web interface.

Note that 172.17.0.2 is the IP address of the container running Nexus.

Now all configuration, certificate and private key files are in place to start the NGINX docker container:

$ docker run -d --name nexus-proxy -p 8080:8080 -p 443:443 \
--mount type=bind,source=/etc/nginx/ssl,target=/etc/nginx/ssl,readonly \
--mount type=bind,source=/etc/nginx/conf.d/nexus.conf,target=/etc/nginx/conf.d/nexus3.conf,readonly \
--mount type=bind,source=/etc/nginx/nginx.conf,target=/etc/nginx/nginx.conf,readonly \
nginx

Access the Nexus Web User Interface

To access the web page of Nexus you can now open your browser and enter one of the following URLs:

Accept the non-trusted self-signed certificate and the web page should open.

img

Create a Hosted Docker Registry

To create a private docker registry on Nexus, you need to create a hosted docker repository. Here is how to do so:

Give it a name, choose a port and set your repository settings. Below is a screenshot of the registry I created.

img

Notice that I configured an HTTP — and not HTTPs — connecter on port 8082, the same port I used in NGINX configuration file. If you choose another port, make sure to reflect it in your NGINX server.

For more information on hosted docker repository check the documentation.

Configure the Docker Client Daemon to Trust the Certificate

The docker command line client points to the public docker hub by default. To pull and push images to a private registry you need to login before. Because we have configured HTTPs and we used a self-signed certificate, the docker client will not trust the certificate if we don’t configure it to do so.

For the docker engine to trust the self-signed certificate we used for NGINX, we must place a copy of this certificate in a special directory on the client host. Make a copy of the .crt file generated earlier, name it ca.crt and place it under /etc/docker/certs.d/192.168.0.17 (replace the name of the last directory in the past with your IP or DNS name) on your client host; you will need to create this directory.

After creating the repository, on the left panel, click on Roles, create a role and give it the access rights on the repository. Then go to Users , create a user and assign it to the role you created. Finally, do not forget to activate the Docker Bearer Token Realm as shown in the figure below.

Try to docker login with your Nexus admin user or the created user and verify that the login is successful.

$ docker login 192.168.0.17
Username: bilal
Password:
WARNING! Your password will be stored unencrypted in /home/bilal/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded

Notice that I only used the IP address or the DNS name in the docker login command. I didn’t use /v2/ that is automatically added by the docker command line client or the port 8082 that is handled by NGINX.

For more information on this check Docker’s documentation.

Summary

In this post we:

  1. Started a Nexus Repository Manager server,
  2. Generated SSL certificates and configured a NGINX reverse proxy server with HTTPs,
  3. Created a hosted docker repository and configured it,
  4. and configured the docker client to accept the repository’s self-signed certificate.

References

Nexus SSL Configuration: https://help.sonatype.com/repomanager3/system-configuration/configuring-ssl

SSL Subject Alternative Name (SAN) extension: https://support.citrix.com/article/CTX135602

--

--