Enable SSL for NGINX reverse proxy using Let’s Encrypt on Docker

Overview of series “How to run Jira and Confluence behind NGINX reverse proxy on Docker”

  1. Run Atlassian Jira and Confluence with PostgreSQL on Docker
  2. NGINX as reverse proxy for Jira and Confluence on Docker
  3. Disable external access to PostgreSQL
  4. Enable SSL for NGINX reverse proxy using Let’s Encrypt on Docker

Introduction

In this article we will enable SSL for NGINX reverse proxy using Let’s Encrypt certificates on our Docker host. To do that we will use an additional container by using the image certbot from Docker Hub.

Certbot will need a place to store challenge files and make them public to validate your domain. Therefore NGINX and Certbot need a shared volume because the only way to access data through the web is our NGINX reverse proxy. Another shared volume will be used to store and read the certificates.

Prepare NGINX to serve challenge files and use the shared volume for certificates

First do a little change in it’s configuration (/var/lib/docker/volumes/nginxConf/_data/conf.d/Atlassian.conf). To the three server objects listening onto port 80 add the additional location /.well-known/acme-challenge/. The file should now look like this:

# Configuration for Jira - client_max_body_size must be at least the max allowed attachment size
server {
    listen 80;
    server_name jira.yourdomain.com;
    location / {
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://jira:8080;
        client_max_body_size 50M;
    }  

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }  
}


# Configuration for Confluence - client_max_body_size must be at least the max allowed attachment size
server {
    listen 80;
    server_name confluence.yourdomain.com;
    location / {
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://confluence:8090;
        client_max_body_size 50M;
    }  

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }  
}

# Configuration for pgAdmin
server {
    listen 80;
    server_name pgadmin.yourdomain.com;
    location / {
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://pgadmin;
    }

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }  
}

Restart the NGINX container with the additional volumes and the new configuration:

docker rm nginx -f
docker run -d -p 80:80 -p 443:443 \
    --name nginx \
    -v nginxConfig:/etc/nginx \
    -v certbotConf:/etc/letsencrypt \
    -v certbotWww:/var/www/certbot \
    --network atlassian \
    nginx:latest

Get Let’s Encrypt certificates for each subdomain

You will now need to get a Let’s Encrypt certificate for each application. Execute Certbot for each domain once.

docker run --rm -it \
    -v certbotConf:/etc/letsencrypt \
    -v certbotWww:/var/www/certbot \
    -v certbotLib:/var/lib/letsencrypt \
    -v certbotLog:/var/log/letsencrypt \
    certbot/certbot \
    certonly --webroot -w /var/www/certbot -d jira.yourdomain.com --force-renewal --agree-tos

docker run --rm -it \
    -v certbotConf:/etc/letsencrypt \
    -v certbotWww:/var/www/certbot \
    -v certbotLib:/var/lib/letsencrypt \
    -v certbotLog:/var/log/letsencrypt \
    certbot/certbot \
    certonly --webroot -w /var/www/certbot -d confluence.yourdomain.com --force-renewal --agree-tos

docker run --rm -it \
    -v certbotConf:/etc/letsencrypt \
    -v certbotWww:/var/www/certbot \
    -v certbotLib:/var/lib/letsencrypt \
    -v certbotLog:/var/log/letsencrypt \
    certbot/certbot \
    certonly --webroot -w /var/www/certbot -d pgadmin.yourdomain.com --force-renewal --agree-tos
The output of the above commands should look something like that.

Enable SSL for NGINX reverse proxy using our new Let’s Encrypt certificates

Let’s encrypt maintains best practices SSL configurations. We will use them. Download these configurations with curl and store them inside the certbotConf volume:

mkdir /var/lib/docker/volumes/certbotConf/_data/conf
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "/var/lib/docker/volumes/certbotConf/_data/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "/var/lib/docker/volumes/certbotConf/_data/conf/ssl-dhparams.pem"

It is necessary to adjust the NGINX configuration again to use the new certificates. Edit /var/lib/docker/volumes/nginxConf/_data/conf.d/Atlassian.conf again to the following (find each occurence of yourdomain.com and exchange it with your own):

# Configuration for Jira - client_max_body_size must be at least the max allowed attachment size
server {
    listen 80;
    server_name jira.yourdomain.com;
    location / {
        return 301 https://$host$request_uri;
    }  

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }  
}

server {
    listen 443 ssl;
    server_name jira.yourdomain.com;
    proxy_read_timeout 600s;
    location / {
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://jira:8080;
        client_max_body_size 50M;
    }

    ssl_certificate /etc/letsencrypt/live/jira.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/jira.yourdomain.com/privkey.pem;
    include /etc/letsencrypt/conf/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/conf/ssl-dhparams.pem;
}
 
 
# Configuration for Confluence - client_max_body_size must be at least the max allowed attachment size
server {
    listen 80;
    server_name confluence.yourdomain.com;
    location / {
        return 301 https://$host$request_uri;
    }  

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }  
}

server {
    listen 443 ssl;
    server_name confluence.yourdomain.com;
    proxy_read_timeout 600s;
    location / {
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://confluence:8090;
        client_max_body_size 50M;
    }

    ssl_certificate /etc/letsencrypt/live/confluence.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/confluence.yourdomain.com/privkey.pem;
    include /etc/letsencrypt/conf/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/conf/ssl-dhparams.pem;
}

# Configuration for pgAdmin
server {
    listen 80;
    server_name pgadmin.yourdomain.com;
    location / {
        return 301 https://$host$request_uri;
    }  

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }  
}

server {
    listen 443 ssl;
    server_name pgadmin.yourdomain.com;
    proxy_read_timeout 600s;
    location / {
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://pgadmin;
    }

    ssl_certificate /etc/letsencrypt/live/pgadmin.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/pgadmin.yourdomain.com/privkey.pem;
    include /etc/letsencrypt/conf/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/conf/ssl-dhparams.pem;
}

Update proxy configuration for Jira and Confluence

You will need to tell Jira and Confluence the correct proxy port and scheme for Tomcat. Recreate both containers with updated startup parameters. The last command will load the new NGINX configuration:

docker rm jira confluence -f
docker run -d \
    --name jira \
    -e JVM_MINIMUM_MEMORY=2048m \
    -e JVM_MAXIMUM_MEMORY=8192m \
    -e ATL_PROXY_NAME=jira.yourdomain.com \
    -e ATL_PROXY_PORT=443 \
    -e ATL_TOMCAT_SCHEME=https \
    -v jiraApplicationData:/var/atlassian/application-data/jira \
    --network atlassian \
    atlassian/jira-software:latest

docker run -d \
    --name confluence \
    -e JVM_MINIMUM_MEMORY=2048m \
    -e JVM_MAXIMUM_MEMORY=8192m \
    -e ATL_PROXY_NAME=confluence.yourdomain.com \
    -e ATL_PROXY_PORT=443 \
    -e ATL_TOMCAT_SCHEME=https \
    -v confluenceApplicationData:/var/atlassian/application-data/confluence \
    --network atlassian \
    atlassian/confluence-server:latest

docker exec nginx nginx -s reload

If you now open Jira, Confluence or pgAdmin with Chrome you should see a lock in the address bar.

With SSL Labs you can test your certificate, you should receive an A rating there.

Automatically renew SSL certificates

SSL certificates issues by Let’s Encrypt are valid only for 3 months. It would be a mess to update them manually all the time. To automate this we will simply add a cron job to the server to do this for us. Edit the crontab file:

crontab -e

Add the following lines to renew all SSL certificates daily at 00:00 and reload NGINX configuration five minutes later.

0 0 * * * docker run --rm -v certbotConf:/etc/letsencrypt -v certbotWww:/var/www/certbot -v certbotLib:/var/lib/letsencrypt -v certbotLog:/var/log/letsencrypt certbot/certbot renew >> /var/log/ssl-renew.log 2>&1
5 0 * * * docker exec nginx nginx -s reload >> /var/log/ssl-renew.log 2>&1

Conclusion

In this article we secured our web applications with SSL certificates from Let’s Encrypt. They are renewed automatically by use of a cron job. Alternatively you could also take a look at the images nginx-proxy by jwilder in combination with letsencrypt-nginx-proxy-companion by jrcs.

One thing what we still could do is combining everything with Docker Compose. This will make everything more maintainable because it would combine all Docker run commands into a single YAML file. Another optimization to improve response times of Confluence and Jira could be to enable NGINX proxy caching but we are still testing the current cache configuration and will describe it if we are sure that it works without issues.

Resources

https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71

https://www.humankode.com/ssl/how-to-set-up-free-ssl-certificates-from-lets-encrypt-using-docker-and-nginx

Leave a Reply