Free, automated SSL certs with Let's Encrypt

Let's Encrypt is a certificate authority that will grant you free SSL certificates if you can prove you own a domain name. The protocol for granting a cert in this way is called the ACME protocol, and with an optional Mu Server add-on you can automate SSL certificate creation and renewal with any ACME-based certificate authority.

Note that unlike many Let's Encrypt integrations, you do not need to install any cert-bots or pre-configure your server in anyway. Instead, just configure DNS and deploy your code.

Step one: configure your domain

Assuming you have a domain such as your-domain.example.org you need to config the DNS settings of that domain to point to your server.

Step two: use the mu-acme library to integrate the ACME server

Note that the ACME integration is built on top of the acme4j library, which uses other dependencies such as Bouncy Castle. The mu-acme dependency brings in all the extra dependencies needed.

<dependency>
    <groupId>io.muserver</groupId>
    <artifactId>mu-server</artifactId>
    <version>2.1.2</version>
</dependency>
<dependency>
    <groupId>io.muserver</groupId>
    <artifactId>mu-acme</artifactId>
    <version>1.0.0</version>
</dependency>

Note: the mu-acme source code and issue tracker is on Github.

Step three: configure the ACME Cert Manager

In your startup code, you need to create a cert manager telling it which ACME service to use, where to write the certs and other files, and which domain name you require.

The following example uses the Let's Encrypt staging server and starts a simple Hello World web server. If this is deployed on a server that the your-domain.example.org domain resolves to, then a free SSL cert will be acquired and automatically renewed every three months.

public class AcmeExample {

    public static void main(String[] args) throws Exception {

        AcmeCertManager certManager = AcmeCertManagerBuilder.letsEncryptStaging()
            .withDomain("your-domain.example.org")
            .withConfigDir("target/ssl-config")
            .build();

        MuServer server = MuServerBuilder.muServer()
            .withHttpPort(80)
            .withHttpsPort(443)
            .withHttpsConfig(certManager.createHttpsConfig())
            .addHandler(certManager.createHandler())
            .addHandler(Method.GET, "/", (req, resp, path) -> {
                resp.write("Hello, world");
            })
            .start();

        certManager.start(server);
        System.out.println("Started server at " + server.uri());

    }

}
(see full file)

More details

The AcmeCertManager needs to know the address of an ACME server. Convenience methods for the Let's Encrypt staging and production services are predefined, but any ACME service can be used:

AcmeCertManagerBuilder.acmeCertManager()
    .withAcmeServerURI(URI.create("ACME server URI"));

The config dir is where mu-acme will write various files. It is recommended that you back up this directory and keep it secure as it contains your ACME user key, domain private key, and the actual server certificate.

With the cert manager built, you can start a server. Note that most ACME providers require you to have an HTTP port open on port 80. You can directly open port 80 like in the example above or use something like IP Tables to redirect port 80 to another port.

The cert manager will provide the SSL context. The first time you start the server, there is no cert available, and a self-signed cert is temporarily used until one is acquired (which is typically within a few seconds). On subsequent server restarts, the cert from the configDir is used.

The first handler you add to your server should be the handler from the cert manager. This is used by the library to prove that you own the domain and will not have any other effect.

Finally, after starting the server, you need to start the cert manager. This will start a periodic check of the cert validity. If the cert is due to expire within 3 days, then a new cert is acquired and Mu Server will start using the new cert. No restart or manual intervention required.

HSTS and redirects

Mu Server provides a handler which can redirect all traffic to HTTPS and set HSTS headers. Just add the following handler AFTER the acme handler when building the server:

.addHandler(certManager.createHandler())
.addHandler(
    HttpsRedirectorBuilder.toHttpsPort(443)
        .withHSTSExpireTime(365, TimeUnit.DAYS)
        .includeSubDomains(true)
)

Running locally

The AcmeCertManagerBuilder has a disable(boolean) method. If you pass true to this method when building the manager, then a no-op manager will be returned. You can then use the cert manager as if it was a real one, however no certs will be requested and a self-signed cert will be used.

Packaging into an uber jar

If packaged as an uber jar, you will need to exclude some files. The following is an example using the maven-shade-plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.4.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>org.example.yourapp.App</mainClass>
                    </transformer>
                </transformers>
                <filters>
                    <filter>
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>META-INF/*.SF</exclude>
                            <exclude>META-INF/*.DSA</exclude>
                            <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                    </filter>
                </filters>
            </configuration>
        </execution>
    </executions>
</plugin>