OpenResty (nginx) with auto generated SSL certificate from Let’s Encrypt

I started with a startssl.com free SSL certificate to use encrypted connections for my website. This works fine, but I have to update the SSL certificate every year manually. Let’s Encrypt offers auto (re)generate SSL certificates and there exists different implementations. The only option for me was Docker of course, but not with an extra Docker container. nginx has support for Lua, so the lua-resty-auto-ssl should be a perfect match. Unfortunatly, I was not able to get it running with nginx. If someone want to try it, you find my nginx Dockerfile here. OpenResty is a nginx drop-in replacement and has Lua built-in. That's pretty nifty. This blog post shows how to use OpenResty with the lua-resty-auto-ssl plugin to automatically and transparently issues SSL certificates from Let's Encrypt (a free certificate authority) as requests are received.

OpenResty lua-resty-auto-ssl Docker image

The openresty/openresty:alpine-fat Docker image is used as base image, because LuaRocks is already included and this makes the installation of lua-resty-auto-ssl plugin very easy. There are some additional libraries needed, to work properly. My OpenResty Dockerfile looks like this.

FROM openresty/openresty:alpine-fat

RUN apk add --no-cache --virtual .run-deps \
    bash \
    curl \
    diffutils \
    grep \
    sed \
    openssl \
    && mkdir -p /etc/resty-auto-ssl \
    && addgroup -S nginx \
    && adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx \
    && chown nginx /etc/resty-auto-ssl

RUN apk add --no-cache --virtual .build-deps \
        gcc \
        libc-dev \
        make \
        openssl-dev \
        pcre-dev \
        zlib-dev \
        linux-headers \
        gnupg \
        libxslt-dev \
        gd-dev \
        geoip-dev \
        perl-dev \
        tar \
        unzip \
        zip \
        unzip \
        g++ \
        cmake \
        lua \
        lua-dev \
        make \
        autoconf \
        automake \
    && /usr/local/openresty/luajit/bin/luarocks install lua-resty-auto-ssl \
    && apk del .build-deps \
    && rm -rf /usr/local/openresty/nginx/conf/* \
    && mkdir -p /var/cache/nginx

# use self signed ssl certifacte to start nginx
COPY ./ssl /etc/resty-auto-ssl

nginx config

The needed Docker image is ready so we have to configure nginx. I will only show important parts of the nginx config to use the lua-resty-auto-ssl plugin. Take also a look at the lua-resty-auto-ssl documentation to see which options are available. For instance, to test your config you can use the staging system of Let’s Encrypt to not run in rate limits.

# Run as a less privileged user for security reasons.
user nginx;

error_log  /usr/local/openresty/nginx/logs/error.log warn;

# ...

http {
  # The "auto_ssl" shared dict should be defined with enough storage space to
  # hold your certificate data. 1MB of storage holds certificates for
  # approximately 100 separate domains.
  lua_shared_dict auto_ssl 1m;

  # Initial setup tasks.
  init_by_lua_block {
    auto_ssl = (require "resty.auto-ssl").new()

    -- Define a function to determine which SNI domains to automatically handle
    -- and register new certificates for. Defaults to not allowing any domains,
    -- so this must be configured.
    auto_ssl:set("allow_domain", function(domain)
      return ngx.re.match(domain, "(sandro-keil.de)__aSyNcId_<_bCuzhqF___quot;, "ijo")
    end)

    auto_ssl:set("dir", "/etc/resty-auto-ssl")

    auto_ssl:init()
  }

  init_worker_by_lua_block {
    auto_ssl:init_worker()
  }

  access_log /usr/local/openresty/nginx/logs/access.log main;

  # ...
}

nginx server definition

The last thing is to configure your nginx server definitions to auto (re)generate the SSL certificate and allow Let’s Encrypt to access your server. This is also quite easy, but you should not expose the port 8999. Put ssl_certificate_by_lua_block to your HTTPS server definition like shown below.

server {
    listen 443 ssl http2;

    # Dynamic handler for issuing or returning certs for SNI domains.
    ssl_certificate_by_lua_block {
      auto_ssl:ssl_certificate()
    }
}

The endpoint which is used for performing domain verification with Let's Encrypt is put to your HTTP server definition and an extra server definition for handling certificate tasks is needed.

server {
    listen 80;
    server_name www.sandro-keil.de www.sandrokeil.de sandro-keil.de sandrokeil.de;

    # Endpoint used for performing domain verification with Let's Encrypt.
    location /.well-known/acme-challenge/ {
        content_by_lua_block {
            auto_ssl:challenge_server()
        }
    }

    location / {
        return 301 https://sandro-keil.de$request_uri;
    }
}

# Internal server running on port 8999 for handling certificate tasks.
server {
    listen 8999;
    location / {
        content_by_lua_block {
            auto_ssl:hook_server()
        }
    }
}

Conclusion

nginx with Lua is very powerful and OpenResty provides an easy way to use it. There are much more interesting OpenResty plugins based on lua. The SSL certificate is now auto regenerated and saves me some work every year. I hope this blog post helps you too. I'm happy to see your comment.

If the IP of the server has changed, you can flush the DNS cache via Google Developers DNS flush, because Let’s Encrypt uses Google's DNS. This was pretty handy for me. I've switched from an 1 vCore / 1 GB RAM to an 1 vCore / 512 MB RAM server.