I have a server hosting several staging and testing instances of various websites that are accessible via secured and unsecured connections.

I intend to set up a Jenkins CI server and make it accessible only via a secured connection. Of course, I would like the rest of the hosted websites to keep running like before.

The solution: use Nginx as a reverse proxy that passes requests either to the Jenkins server or to Apache.

Jenkins is configured to run at port 8080. I would like Nginx to listen to connections over port 443 on the Jenkins URL and pass them to the localhost:8080. Also, all incoming connection to the Jenkins URL will be redirected to port 443 and from there via the proxy to localhost:8080

The secure Jenkins connection will require a certificate. Since the CI server is not public, I can save some money and use a self-signed certificate (Or better yet, get a Let’s Encrypt certificate).

To keep the Apache hosts operational, I will have to configure Apache to listen to ports other than 80 and 443, which now will be taken by Nginx.

The following instructions are largely based on tutorials published at DigitalOcean How To Configure Nginx with SSL as a Reverse Proxy for Jenkins and How To Configure Nginx as a Web Server and Reverse Proxy for Apache on One Ubuntu 16.04 Server

Installing Nginx

The Nginx installation has been straightforward and unproblematic:

Nginx SSL Certificate

As said above, I am going to create and use a self-signed certificate. The command is as follows:

The command prompts me to enter a few details for the certificate and then creates the key and certificate files at the specified paths.

I have to change the permissions on the key file so:

Configuring the Proxy for Jenkins

At /etc/nginx/sites-available I create a host configuration file jenkins with the following content:

Configuration file explained

This explanation is based on the Nginx documentation.

The first configuration block listens to incoming requests on port 80 for the Jenkins server URL and redirects them permanently to the secure URL. The line with [::]:80is for IPv6 addresses that are specified in square brackets

The next block is the secure host configuration for the Jenkins server. It listens to port 443 with the sslparameter. I also have to specify the locations of the certificate and key files:

Next follows the configuration for the ngx_http_ssl_module.

The ssl on directive is made obsolete in version 1.15.0 in favor of the listen directive and can be omitted.

The setting ssl_session_cache sets the types and sizes of caches that store the session parameters. The parameter builtin specifies the use of the cache that is built-in OpenSSL and can be used by one worker process only. The number 1000 indicates the cache size in the session. The parameter sharedalso allows the use of a cache shared between all worker processes. Its size is set to 10 megabytes:

The directive ssl_protocols enables the specified protocols, and the directive ssl_ciphers enables the specified ciphers. The line with ciphers is presented in the format understood by OpenSSL. The ssl_preser_server_ciphers set to on indicates that the server ciphers should be preferred over the client ciphers when using SSLv3 and TLS protocols:

Next, a line to specify the path to the host access log:

Finally, the location block configures the proxy settings for the Jenkins server. The settings are for the module ngx_http_proxy_module.

First, several entries for the directiveproxy_set_header. This directive allows redefining or appending fields to the request header passed to the proxied server. The field name is the token that comes after the directive, e.g., Host, and the new value follows it.

A note to the $proxy_add_a_forwarded_for variable. Its value is the X-Forwarded-For client request header field with the $remote_addr variable appended to it, separated by a comma. If the X-Forwarded-For field is not present in the client request header, the $proxy_add_x_forwarded_for variable is equal to the $remote_addr variable.

The $scheme value in the X-Forwarded-Proto field is expected to be https

If Jenkins shows the It appears that your reverse proxy set up is broken error, we must make sure that the parameter value in the directiveproxy_pass matches the Jenkins URL. In my case, the localhost URL works fine.

Finally, this line:

Here, the first parameter is the redirect URL and the second is the replacement.

I make sure that the configuration file is symlinked to the sites-enabled directory:

After the restart, the new site configuration is enabled:

Configuring the Reverse Proxy for Apache Hosts

As mentioned in the beginning, I have several staging and testing sites running on the server. They must be accessible via https. Additionally, a few placeholder sites should be accessed unencrypted.

To start, I change the Apache ports.conf file where I change lines listening to ports 80 and 443 to 8081 and 8443 respectively:

Then, I go to the files containing the virtual host configuration and change the ports there as well:

I disable the files containing secure virtual hosts’ configurations because they will not be needed in the new setup.

Then, I create a new configuration file at /etc/nginx/sites-available/apachethat will contain the reverse proxy configurations for the Apache hosts.

First, I create entries for unsecure websites:

Now, I need to configure server entries for staging and testing sites that will be accessible both via secure and unsecure connections:

Now, in an incoming connection should be secure, the configuration can access the certificate and key files and encrypt it. The request is then forwarded to the port 8081 that Apache listens to.

This, however, is a problematic configuration. With it, the browser will receive the default server certificate, i.e., my-site.com.crt regardless of the requested server name. This is caused by SSL protocol behavior The SSL connection is established before the browser sends an HTTP request and Nginx does not know the name of the requested server. Therefore, it may only offer the default server’s certificate.

There are several solutions discussed in the documentation, and for a production setup, I will have to implement one of them. These, however, are testing instances that already use mismatched certificates (I did not order a wildcard or name-specific certificates for them), so this will do.

Configuring mod_rpaf

Source: Digital Ocean

For this configuration to work, we need an Apache module mod_rpaf. This module rewrites the values of REMOTE_ADDR, HTTPS, and HTTP_PORT based on the values provided by the reverse proxy. We will install the latest version by compiling it from the source.

First, we need to install packages required by this module:

Then, I download the latest stable release of the module from GitHub:

I change to the unzipped module directory and compile and install the module:

Next, I create the configuration loader file for the module:

I add this line to the configuration loader file :

Next, I create the configuration file in the same directory:

The content of the configuration file is this:

Here’s a brief description of each directive. See the mod_rpaf README file for more information.

  • RPAF_Header – The header to use for the client’s real IP address.
  • RPAF_ProxyIPs – The proxy IP to adjust HTTP requests for.
  • RPAF_SetHostName – Updates the vhost name so ServerName and ServerAlias work.
  • RPAF_SetHTTPS – Sets the HTTPS environment variable based on the value contained in X-Forwarded-Proto.
  • RPAF_SetPort – Sets the SERVER_PORT environment variable. Useful for when Apache is behind a SSL proxy.

Finally, I enable the module and restart Apache:

Possible problems

In the logfile at /var/log/nginx/error.log you might see entries like:

This indicates that these ports are currently in use. This command frees them, then restarts Nginx: