Nginx docker as reverse proxy and letsencrypt


Last week I bought the cheapest OVH VPS to test out self hosted services (owncloud, kanboard…). Most services provide docker containers which make it painless to build and run them from a new server. All I did was install docker, download the container images and run them.

It sounds simple and it is until it isn’t, which of course was due to my inexperience. This post is intended to record my progress.


My current stack contains a nginx docker acting as a reverse proxy to multiple subdomains; each service are run inside a separate docker. For example, is just a Kanboard docker. The nginx also forwards all http request to https.


I had my domain A record point to my server ip. I ran into some problems when using Cloudflare’s HTTP proxy with letsencrypt, so I had to disable the former.


letsencrypt has an official docker container but it isn’t advisable to use according to the document1, so I installed letsencrypt package from official ubuntu repo.

sudo apt-get install letsencrypt

Create strong 2048-bit or higher dhparams2

sudo openssl dhparam -out dhparam.pem 2048


Add a volume /etc/letsencrypt to nginx so nginx can serve challenges created by letsencrypt. My docker-compose.yml config is as following:

version: '2'
      context: ./nginx
    container_name: nginx
      - /etc/letsencrypt:/etc/letsencrypt
      - "80:80"
      - "443:443"

My Dockerfile for nginx is

FROM nginx:alpine

COPY nginx.conf /etc/nginx/nginx.conf
COPY dhparam.pem /etc/nginx/ssl/dhparam.pem

In my nginx.conf, I add a server block

server {
    location ~ /.well-known {
        root /etc/letsencrypt/webrootauth;
        default_type "text/plain";

Now /etc/letsencrypt/ can be read and modified by both nginx docker and letsencrypt.

Running letsencrypt

Create dir /etc/letsencrypt/webrootauth

sudo mkdir -p /etc/letsencrypt/webrootauth

Run letsencrypt

sudo letsencrypt certonly -c letsencrypt.ini

My letsencrypt.ini file

rsa-key-size = 2048
email =
domains =
text = True
agree-tos = True
authenticator = webroot
webroot-path = /etc/letsencrypt/webrootauth
keep-until-expiring = True

nginx.conf again

If everything go well, you will have your certificates at /etc/letsencrypt/live/

Modify nginx.conf, here I redirect http to https and forward traffic to kanboard docker

server {
    listen 80;
    listen [::]:80;

    location ~ /.well-known {
        root /etc/letsencrypt/webrootauth;
        default_type "text/plain";

    location / {
        return 301 https://$host$request_uri;

server {
    listen 443 ssl default deferred;

    ssl_certificate /etc/letsencrypt/live/;
    ssl_certificate_key /etc/letsencrypt/live/;

    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 180m;
    ssl_session_tickets off;

    ssl_dhparam /etc/nginx/ssl/dhparam.pem;

    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    ssl_stapling on;
    ssl_stapling_verify on;

    resolver valid=300s;
    resolver_timeout 5s;

    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";

    location ~ /.well-known {
        root /etc/letsencrypt/webrootauth;
        default_type "text/plain";

    location / {
        proxy_pass http://kanboard:80;
        proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-NginX-Proxy true;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
        proxy_read_timeout 900;

systemd service and timer

Create a job to run letsencrypt for renewing the ssl certificate on the first day of every month.

Description=letsencrypt renew

ExecStart=/usr/bin/letsencrypt certonly -c letsencrypt.ini
ExecStartPost=/usr/local/bin/docker-compose restart

Description=letsencrypt renew timer

OnCalendar=*-*-01 01:00:00