Guides: How to get a free certificate and use SSL with Ktor

    Table of contents:

    You can buy a certificate and configure Ktor to use it,or you can use Let’s Encrypt to automatically get a free certificate to serve and wss:// requestswith Ktor.In this page you will discover how to do it, by either configuring Ktor to directly serve the SSL certificatefor a single domain or by using Docker with nginx to serve different applications in different containers ona single machine easily.

    First of all, you have to configure your domain or subdomain to point to the IP of the machine thatyou are going to use for the certificate. You have to put the public IP of the machine here.If that machine is behind routers, you will need to configure the router to DMZ the machine with the host,or to redirect at least the port 80 (HTTP) to that machine, and later you will probably want to configure theport 443 (HTTPS) too.

    Let’s Encrypt will always access the PORT 80 of your public IP, even if you configure Ktor to bind to another port,you have to configure your routes to redirect the port 80 to the correct local IP and port of the machinehosting ktor.

    Generating a certificate

    The Ktor server must not be running, and you have to execute the following command(changing my.example.com, root@example.com and 8889).

    This command will start a HTTP web server in the specified port (that must be available as port 80 in thepublic network, or you can forward ports in your router to 80:8889, and the domain must point to your public IP),it will then request a challenge, expose the /.well-known/acme-challenge/file with the proper content, generate a domain private key, and retrieve the certificate chain:

    Converting the private key and certificate for Ktor

    The chain and private keys are stored in /etc/letsencrypt/live/$DOMAIN as fullchain.pem and privkey.pem.

    1. openssl pkcs12 -export -out /etc/letsencrypt/live/$DOMAIN/keystore.p12 -inkey /etc/letsencrypt/live/$DOMAIN/privkey.pem -in /etc/letsencrypt/live/$DOMAIN/fullchain.pem -name $ALIAS

    This will request a password for the export (you need to provide one for the next step to work):

    With th p12 file, we can use the keytool cli to generate a JKS file:

    1. keytool -importkeystore -alias $ALIAS -destkeystore /etc/letsencrypt/live/$DOMAIN/keystore.jks -srcstoretype PKCS12 -srckeystore /etc/letsencrypt/live/$DOMAIN/keystore.p12

    Now you have to update your application.conf HOCON file, to configure the SSL port, the keyStore, alias, and passwords.You have to set the correct values for your specific case:

    1. ktor {
    2. deployment {
    3. port = 8889
    4. port = ${?PORT}
    5. sslPort = 8890
    6. sslPort = ${?PORT_SSL}
    7. }
    8. application {
    9. modules = [ com.example.ApplicationKt.module ]
    10. }
    11. security {
    12. ssl {
    13. keyStore = /etc/letsencrypt/live/mydomain.com/keystore.jks
    14. keyAlias = myalias
    15. keyStorePassword = mypassword
    16. privateKeyPassword = mypassword
    17. }
    18. }
    19. }

    If everything went well, Ktor should be listening on port 8889 in HTTP and listening on port 8890 in HTTPS.

    Option2: With Docker and Nginx as reverse proxy

    When using Docker with multiple domains, you might want to use the image and the letsencrypt-nginx-proxy-companionimage to serve multiple domains/subdomains on a single machine/ip and to automatically provide HTTPS, using Let’s Encrypt.

    Creating a internal docker network

    The first step is to create a bridge network that we will use so nginx can connect to other containersto reverse proxy a user’s HTTP, HTTPS, WS, and WSS requests:

    1. docker network create --driver bridge reverse-proxy

    Creating an Nginx container

    Now we have to create a container running NGINX doing the reverse proxy:

    • —restart=always so the docker daemon restarts the container when the machine is restarted.
    • —network=reverse-proxy so NGINX is in that network and can connect to other containers in the same network.
    • -v certs:ro this volume will be shared with the letsencrypt-companion to access the certificates per domain.
    • -v conf, vhost so this configuration is persistent and accessible from outside in the case you have to do some tweaks.
    • -v /var/run/docker.sock this allows this image to get notified / introspect about new containers running in the daemon.
    • -e —label used by the companion by identify this image as NGINX.

    You can adjust /home/virtual/nginx* paths to the path you prefer.

    With the nginx-proxy container, now we can create a companion container,that will request and renew certificates:

    1. docker run -d \
    2. --name nginx-letsencrypt \
    3. --restart=always \
    4. --network=reverse-proxy \
    5. --volumes-from nginx \
    6. -v /var/run/docker.sock:/var/run/docker.sock:ro \
    7. jrcs/letsencrypt-nginx-proxy-companion
    • —restart=always as the NGINX image, to restart on boot.
    • —network=reverse-proxy it need to be on the same network as the NGINX proxy container to communicate with it.
    • —volumes-from nginx it makes accessible the same volumes as the NGINX container so it can write the .well-known challenge inside /usr/share/nginx/html.
    • -v certs:rw it requires write access to write the private key and certificates to be available from NGINX.
    • -v /var/run/docker.sock requires access to docker events and introspection to determine which certificates to request.

    Creating a service

    Now we have NGINX and Let’s Encrypt companion configured so they will automatically reverse-proxy your websites andrequest and serve certificates for them based on the environment variables VIRTUAL_HOST, VIRTUAL_PORT and LETSENCRYPT_HOST, LETSENCRYPT_EMAIL.

    Using docker-compose, you can create a docker-compose.yml file (without additional services) that could look like this:

    docker-compose.yml

    1. version: '2'
    2. services:
    3. web:
    4. build:
    5. context: ./
    6. dockerfile: Dockerfile
    7. expose:
    8. - 8080
    9. environment:
    10. - VIRTUAL_HOST=mydomain.com
    11. - VIRTUAL_PORT=8080
    12. - LETSENCRYPT_HOST=mydomain.com
    13. - LETSENCRYPT_EMAIL=myemail@mydomain.com
    14. networks:
    15. - reverse-proxy
    16. restart: always
    17. networks:
    18. backend:
    19. reverse-proxy:
    20. external:
    21. name: reverse-proxyv

    Dockerfile

    1. FROM openjdk:8-jre-alpine
    2. ENV APPLICATION_USER ktor
    3. RUN adduser -D -g '' $APPLICATION_USER
    4. RUN mkdir /app
    5. RUN chown -R $APPLICATION_USER /app
    6. USER $APPLICATION_USER
    7. COPY ./build/libs/my-application.jar /app/my-application.jar
    8. WORKDIR /app

    Simplified overview

    In this case you are using nginx acting as reverse-proxy for your requests. If you want to get information about the original requests,instead of the proxied nginx request, you will have to use the feature.